feat(#84): implement instance identity model for federation
Implemented the foundation of federation architecture with instance identity and connection management: Database Schema: - Added Instance model for instance identity with keypair generation - Added FederationConnection model for workspace-scoped connections - Added FederationConnectionStatus enum (PENDING, ACTIVE, SUSPENDED, DISCONNECTED) Service Layer: - FederationService with instance identity management - RSA 2048-bit keypair generation for signing - Public identity endpoint (excludes private key) - Keypair regeneration capability API Endpoints: - GET /api/v1/federation/instance - Returns public instance identity - POST /api/v1/federation/instance/regenerate-keys - Admin keypair regeneration Tests: - 11 tests passing (7 service, 4 controller) - 100% statement coverage, 100% function coverage - Follows TDD principles (Red-Green-Refactor) Configuration: - Added INSTANCE_NAME and INSTANCE_URL environment variables - Integrated FederationModule into AppModule Refs #84 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
114
apps/api/src/federation/federation.controller.spec.ts
Normal file
114
apps/api/src/federation/federation.controller.spec.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Federation Controller Tests
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { FederationController } from "./federation.controller";
|
||||
import { FederationService } from "./federation.service";
|
||||
import { AuthGuard } from "../auth/guards/auth.guard";
|
||||
import { PublicInstanceIdentity, InstanceIdentity } from "./types/instance.types";
|
||||
|
||||
describe("FederationController", () => {
|
||||
let controller: FederationController;
|
||||
let service: FederationService;
|
||||
|
||||
const mockPublicIdentity: PublicInstanceIdentity = {
|
||||
id: "123e4567-e89b-12d3-a456-426614174000",
|
||||
instanceId: "test-instance-id",
|
||||
name: "Test Instance",
|
||||
url: "https://test.example.com",
|
||||
publicKey: "-----BEGIN PUBLIC KEY-----\nMOCK\n-----END PUBLIC KEY-----",
|
||||
capabilities: {
|
||||
supportsQuery: true,
|
||||
supportsCommand: true,
|
||||
supportsEvent: true,
|
||||
protocolVersion: "1.0",
|
||||
},
|
||||
metadata: {},
|
||||
createdAt: new Date("2026-01-01T00:00:00Z"),
|
||||
updatedAt: new Date("2026-01-01T00:00:00Z"),
|
||||
};
|
||||
|
||||
const mockInstanceIdentity: InstanceIdentity = {
|
||||
...mockPublicIdentity,
|
||||
privateKey: "-----BEGIN PRIVATE KEY-----\nMOCK\n-----END PRIVATE KEY-----",
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [FederationController],
|
||||
providers: [
|
||||
{
|
||||
provide: FederationService,
|
||||
useValue: {
|
||||
getPublicIdentity: vi.fn(),
|
||||
regenerateKeypair: vi.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
.overrideGuard(AuthGuard)
|
||||
.useValue({ canActivate: () => true })
|
||||
.compile();
|
||||
|
||||
controller = module.get<FederationController>(FederationController);
|
||||
service = module.get<FederationService>(FederationService);
|
||||
});
|
||||
|
||||
describe("GET /instance", () => {
|
||||
it("should return public instance identity", async () => {
|
||||
// Arrange
|
||||
vi.spyOn(service, "getPublicIdentity").mockResolvedValue(mockPublicIdentity);
|
||||
|
||||
// Act
|
||||
const result = await controller.getInstance();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockPublicIdentity);
|
||||
expect(service.getPublicIdentity).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should not expose private key", async () => {
|
||||
// Arrange
|
||||
vi.spyOn(service, "getPublicIdentity").mockResolvedValue(mockPublicIdentity);
|
||||
|
||||
// Act
|
||||
const result = await controller.getInstance();
|
||||
|
||||
// Assert
|
||||
expect(result).not.toHaveProperty("privateKey");
|
||||
});
|
||||
|
||||
it("should return consistent identity across multiple calls", async () => {
|
||||
// Arrange
|
||||
vi.spyOn(service, "getPublicIdentity").mockResolvedValue(mockPublicIdentity);
|
||||
|
||||
// Act
|
||||
const result1 = await controller.getInstance();
|
||||
const result2 = await controller.getInstance();
|
||||
|
||||
// Assert
|
||||
expect(result1).toEqual(result2);
|
||||
expect(result1.instanceId).toEqual(result2.instanceId);
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /instance/regenerate-keys", () => {
|
||||
it("should regenerate keypair and return updated identity", async () => {
|
||||
// Arrange
|
||||
const updatedIdentity = {
|
||||
...mockInstanceIdentity,
|
||||
publicKey: "NEW_PUBLIC_KEY",
|
||||
};
|
||||
vi.spyOn(service, "regenerateKeypair").mockResolvedValue(updatedIdentity);
|
||||
|
||||
// Act
|
||||
const result = await controller.regenerateKeys();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(updatedIdentity);
|
||||
expect(service.regenerateKeypair).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user