Implements FED-010: Agent Spawn via Federation feature that enables spawning and managing Claude agents on remote federated Mosaic Stack instances via COMMAND message type. Features: - Federation agent command types (spawn, status, kill) - FederationAgentService for handling agent operations - Integration with orchestrator's agent spawner/lifecycle services - API endpoints for spawning, querying status, and killing agents - Full command routing through federation COMMAND infrastructure - Comprehensive test coverage (12/12 tests passing) Architecture: - Hub → Spoke: Spawn agents on remote instances - Command flow: FederationController → FederationAgentService → CommandService → Remote Orchestrator - Response handling: Remote orchestrator returns agent status/results - Security: Connection validation, signature verification Files created: - apps/api/src/federation/types/federation-agent.types.ts - apps/api/src/federation/federation-agent.service.ts - apps/api/src/federation/federation-agent.service.spec.ts Files modified: - apps/api/src/federation/command.service.ts (agent command routing) - apps/api/src/federation/federation.controller.ts (agent endpoints) - apps/api/src/federation/federation.module.ts (service registration) - apps/orchestrator/src/api/agents/agents.controller.ts (status endpoint) - apps/orchestrator/src/api/agents/agents.module.ts (lifecycle integration) Testing: - 12/12 tests passing for FederationAgentService - All command service tests passing - TypeScript compilation successful - Linting passed Refs #93 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
6.5 KiB
6.5 KiB
Testing Requirements
Test-driven development standards and practices for Mosaic Stack.
Testing Philosophy
- Test-Driven Development (TDD) — Write tests before implementation
- Minimum 85% coverage for all new code
- All tests must pass before PR approval
- No untested code in production
Test Types
Unit Tests
Test individual functions and methods in isolation.
Location: *.spec.ts next to source file
Example:
// apps/api/src/auth/auth.service.spec.ts
describe("AuthService", () => {
it("should create a session for valid user", async () => {
const result = await authService.createSession(mockUser);
expect(result.session.token).toBeDefined();
});
});
Integration Tests
Test interactions between components.
Location: *.integration.spec.ts in module directory
Example:
// apps/api/src/auth/auth.integration.spec.ts
describe("Auth Integration", () => {
it("should complete full login flow", async () => {
const login = await request(app.getHttpServer())
.post("/auth/sign-in")
.send({ email, password });
expect(login.status).toBe(200);
});
});
E2E Tests
Test complete user flows across the entire stack.
Location: apps/web/tests/e2e/ (when implemented)
Framework: Playwright
Running Tests
# All tests
pnpm test
# Watch mode (re-run on changes)
pnpm test:watch
# Coverage report
pnpm test:coverage
# Specific file
pnpm test apps/api/src/auth/auth.service.spec.ts
# API tests only
pnpm test:api
# E2E tests (when implemented)
pnpm test:e2e
Writing Tests
Structure
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
describe("ComponentName", () => {
beforeEach(() => {
// Setup
});
afterEach(() => {
// Cleanup
});
describe("methodName", () => {
it("should handle normal case", () => {
// Arrange
const input = "test";
// Act
const result = component.method(input);
// Assert
expect(result).toBe("expected");
});
it("should handle error case", () => {
expect(() => component.method(null)).toThrow();
});
});
});
Mocking
// Mock service dependency
const mockPrismaService = {
user: {
findUnique: vi.fn(),
create: vi.fn(),
},
};
// Mock module
vi.mock("./some-module", () => ({
someFunction: vi.fn(() => "mocked"),
}));
Testing Async Code
it("should complete async operation", async () => {
const result = await asyncFunction();
expect(result).toBeDefined();
});
// Or with resolves/rejects
it("should resolve with data", async () => {
await expect(asyncFunction()).resolves.toBe("data");
});
it("should reject with error", async () => {
await expect(failingFunction()).rejects.toThrow("Error");
});
Coverage Requirements
Minimum Coverage
- Statements: 85%
- Branches: 80%
- Functions: 85%
- Lines: 85%
View Coverage Report
pnpm test:coverage
# Opens HTML report
open coverage/index.html
Exemptions
Some code types may have lower coverage requirements:
- DTOs/Interfaces: No coverage required (type checking sufficient)
- Constants: No coverage required
- Database migrations: Manual verification acceptable
Always document exemptions in PR description.
Best Practices
1. Test Behavior, Not Implementation
❌ Bad:
it("should call getUserById", () => {
service.login(email, password);
expect(mockService.getUserById).toHaveBeenCalled();
});
✅ Good:
it("should return session for valid credentials", async () => {
const result = await service.login(email, password);
expect(result.session.token).toBeDefined();
expect(result.user.email).toBe(email);
});
2. Use Descriptive Test Names
❌ Bad:
it('works', () => { ... });
it('test 1', () => { ... });
✅ Good:
it('should return 401 for invalid credentials', () => { ... });
it('should create session with 24h expiration', () => { ... });
3. Arrange-Act-Assert Pattern
it("should calculate total correctly", () => {
// Arrange - Set up test data
const items = [{ price: 10 }, { price: 20 }];
// Act - Execute the code being tested
const total = calculateTotal(items);
// Assert - Verify the result
expect(total).toBe(30);
});
4. Test Edge Cases
describe("validateEmail", () => {
it("should accept valid email", () => {
expect(validateEmail("user@example.com")).toBe(true);
});
it("should reject empty string", () => {
expect(validateEmail("")).toBe(false);
});
it("should reject null", () => {
expect(validateEmail(null)).toBe(false);
});
it("should reject invalid format", () => {
expect(validateEmail("notanemail")).toBe(false);
});
});
5. Keep Tests Independent
// ❌ Bad - Tests depend on order
let userId;
it("should create user", () => {
userId = createUser();
});
it("should get user", () => {
getUser(userId); // Fails if previous test fails
});
// ✅ Good - Each test is independent
it("should create user", () => {
const userId = createUser();
expect(userId).toBeDefined();
});
it("should get user", () => {
const userId = createUser(); // Create fresh data
const user = getUser(userId);
expect(user).toBeDefined();
});
CI/CD Integration
Tests run automatically on:
- Every push to feature branch
- Every pull request
- Before merge to
develop
Pipeline must pass before merging.
Debugging Tests
Run Single Test
it.only("should test specific case", () => {
// Only this test runs
});
Skip Test
it.skip("should test something", () => {
// This test is skipped
});
Verbose Output
pnpm test --reporter=verbose
Debug in VS Code
Add to .vscode/launch.json:
{
"type": "node",
"request": "launch",
"name": "Vitest",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["test", "--no-coverage"],
"console": "integratedTerminal"
}
Test Database
Use separate test database:
# .env.test
TEST_DATABASE_URL=postgresql://mosaic:mosaic@localhost:5432/mosaic_test
# Reset test DB before each run
pnpm test:db:reset
Next Steps
- Review Commit Guidelines — Committing
- Learn Database Testing — Database
- Check Code Style — Google TypeScript Style Guide