feat(#295): validate FederationCapabilities structure
Add DTO validation for FederationCapabilities to ensure proper structure. - Create FederationCapabilitiesDto with class-validator decorators - Validate boolean types for capability flags - Validate string type for protocolVersion - Update IncomingConnectionRequestDto to use validated DTO - Add comprehensive unit tests for DTO validation Fixes #295 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
80
apps/api/src/federation/dto/capabilities.dto.spec.ts
Normal file
80
apps/api/src/federation/dto/capabilities.dto.spec.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Capabilities DTO Tests
|
||||
*
|
||||
* Tests for FederationCapabilities validation.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { validate } from "class-validator";
|
||||
import { plainToInstance } from "class-transformer";
|
||||
import { FederationCapabilitiesDto } from "./capabilities.dto";
|
||||
|
||||
describe("FederationCapabilitiesDto", () => {
|
||||
it("should accept valid capabilities", async () => {
|
||||
const plain = {
|
||||
supportsQuery: true,
|
||||
supportsCommand: false,
|
||||
supportsEvent: true,
|
||||
supportsAgentSpawn: false,
|
||||
protocolVersion: "1.0",
|
||||
};
|
||||
|
||||
const dto = plainToInstance(FederationCapabilitiesDto, plain);
|
||||
const errors = await validate(dto);
|
||||
|
||||
expect(errors).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should accept minimal valid capabilities", async () => {
|
||||
const plain = {};
|
||||
|
||||
const dto = plainToInstance(FederationCapabilitiesDto, plain);
|
||||
const errors = await validate(dto);
|
||||
|
||||
expect(errors).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should reject invalid boolean for supportsQuery", async () => {
|
||||
const plain = {
|
||||
supportsQuery: "yes", // Should be boolean
|
||||
};
|
||||
|
||||
const dto = plainToInstance(FederationCapabilitiesDto, plain);
|
||||
const errors = await validate(dto);
|
||||
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
expect(errors[0].property).toBe("supportsQuery");
|
||||
});
|
||||
|
||||
it("should reject invalid type for protocolVersion", async () => {
|
||||
const plain = {
|
||||
protocolVersion: 1.0, // Should be string
|
||||
};
|
||||
|
||||
const dto = plainToInstance(FederationCapabilitiesDto, plain);
|
||||
const errors = await validate(dto);
|
||||
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
expect(errors[0].property).toBe("protocolVersion");
|
||||
});
|
||||
|
||||
it("should accept only specified fields", async () => {
|
||||
const plain = {
|
||||
supportsQuery: true,
|
||||
supportsCommand: true,
|
||||
supportsEvent: false,
|
||||
supportsAgentSpawn: true,
|
||||
protocolVersion: "1.0",
|
||||
};
|
||||
|
||||
const dto = plainToInstance(FederationCapabilitiesDto, plain);
|
||||
const errors = await validate(dto);
|
||||
|
||||
expect(errors).toHaveLength(0);
|
||||
expect(dto.supportsQuery).toBe(true);
|
||||
expect(dto.supportsCommand).toBe(true);
|
||||
expect(dto.supportsEvent).toBe(false);
|
||||
expect(dto.supportsAgentSpawn).toBe(true);
|
||||
expect(dto.protocolVersion).toBe("1.0");
|
||||
});
|
||||
});
|
||||
32
apps/api/src/federation/dto/capabilities.dto.ts
Normal file
32
apps/api/src/federation/dto/capabilities.dto.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Capabilities DTO
|
||||
*
|
||||
* Data Transfer Object for federation capabilities validation.
|
||||
*/
|
||||
|
||||
import { IsBoolean, IsOptional, IsString } from "class-validator";
|
||||
|
||||
/**
|
||||
* DTO for validating FederationCapabilities structure
|
||||
*/
|
||||
export class FederationCapabilitiesDto {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
supportsQuery?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
supportsCommand?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
supportsEvent?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
supportsAgentSpawn?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
protocolVersion?: string;
|
||||
}
|
||||
@@ -4,8 +4,10 @@
|
||||
* Data Transfer Objects for federation connection API.
|
||||
*/
|
||||
|
||||
import { IsString, IsUrl, IsOptional, IsObject, IsNumber } from "class-validator";
|
||||
import { IsString, IsUrl, IsOptional, IsObject, IsNumber, ValidateNested } from "class-validator";
|
||||
import { Type } from "class-transformer";
|
||||
import { Sanitize, SanitizeObject } from "../../common/decorators/sanitize.decorator";
|
||||
import { FederationCapabilitiesDto } from "./capabilities.dto";
|
||||
|
||||
/**
|
||||
* DTO for initiating a connection
|
||||
@@ -57,8 +59,9 @@ export class IncomingConnectionRequestDto {
|
||||
@IsString()
|
||||
publicKey!: string;
|
||||
|
||||
@IsObject()
|
||||
capabilities!: Record<string, unknown>;
|
||||
@ValidateNested()
|
||||
@Type(() => FederationCapabilitiesDto)
|
||||
capabilities!: FederationCapabilitiesDto;
|
||||
|
||||
@IsNumber()
|
||||
timestamp!: number;
|
||||
|
||||
@@ -339,5 +339,30 @@ describe("FederationController", () => {
|
||||
});
|
||||
expect(connectionService.handleIncomingConnectionRequest).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should validate capabilities structure with valid data", async () => {
|
||||
const dto = {
|
||||
instanceId: "remote-instance-456",
|
||||
instanceUrl: "https://remote.example.com",
|
||||
publicKey: "PUBLIC_KEY",
|
||||
capabilities: {
|
||||
supportsQuery: true,
|
||||
supportsCommand: false,
|
||||
supportsEvent: true,
|
||||
supportsAgentSpawn: false,
|
||||
protocolVersion: "1.0",
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
signature: "valid-signature",
|
||||
};
|
||||
vi.spyOn(connectionService, "handleIncomingConnectionRequest").mockResolvedValue(
|
||||
mockConnection
|
||||
);
|
||||
|
||||
const result = await controller.handleIncomingConnection(dto);
|
||||
|
||||
expect(result.status).toBe("pending");
|
||||
expect(connectionService.handleIncomingConnectionRequest).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user