import 'reflect-metadata'; import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { Global, Module } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify'; import { HermesRuntimeProvider } from '@mosaicstack/agent'; import { AgentModule } from './agent.module.js'; import { AUTH } from '../auth/auth.tokens.js'; import { AuthGuard } from '../auth/auth.guard.js'; import { BRAIN } from '../brain/brain.tokens.js'; import { DB } from '../database/database.module.js'; import { CoordModule } from '../coord/coord.module.js'; import { McpClientModule } from '../mcp-client/mcp-client.module.js'; import { SkillsModule } from '../skills/skills.module.js'; import { GCModule } from '../gc/gc.module.js'; import { LogModule } from '../log/log.module.js'; import { CommandsModule } from '../commands/commands.module.js'; import { AGENT_RUNTIME_PROVIDER_REGISTRY, RUNTIME_APPROVAL_VERIFIER, RUNTIME_PROVIDER_AUDIT_SINK, RuntimeProviderAuditService, } from './runtime-provider-registry.service.js'; import { DurableSessionService } from './durable-session.service.js'; import { DurableSessionRepository } from './durable-session.repository.js'; import { AgentService } from './agent.service.js'; import { ProviderService } from './provider.service.js'; import { ProviderCredentialsService } from './provider-credentials.service.js'; import { RoutingService } from './routing.service.js'; import { RoutingEngineService } from './routing/routing-engine.service.js'; import { SkillLoaderService } from './skill-loader.service.js'; const authenticatedUser = { id: 'operator-1', tenantId: 'tenant-1' }; @Module({}) class EmptyAgentDependencyModule {} @Global() @Module({ providers: [ { provide: AUTH, useValue: { api: { getSession: vi.fn(async ({ headers }: { headers: Headers }) => headers.get('cookie') === 'session=trusted' ? { user: authenticatedUser, session: { id: 'session-1' } } : null, ), }, }, }, AuthGuard, { provide: BRAIN, useValue: {} }, { provide: DB, useValue: {} }, ], exports: [AUTH, AuthGuard, BRAIN, DB], }) class AuthenticatedRequestModule {} /** * This is deliberately an HTTP test rather than a controller unit test: it * exercises AgentModule's actual provider factory, Nest DI, and AuthGuard. */ describe('Hermes runtime provider reachability', (): void => { let app: NestFastifyApplication | undefined; beforeAll(async (): Promise => { process.env['MOSAIC_AGENT_NAME'] = 'Nova'; const moduleRef = await Test.createTestingModule({ imports: [AuthenticatedRequestModule, AgentModule], }) .overrideModule(CoordModule) .useModule(EmptyAgentDependencyModule) .overrideModule(McpClientModule) .useModule(EmptyAgentDependencyModule) .overrideModule(SkillsModule) .useModule(EmptyAgentDependencyModule) .overrideModule(GCModule) .useModule(EmptyAgentDependencyModule) .overrideModule(LogModule) .useModule(EmptyAgentDependencyModule) .overrideModule(CommandsModule) .useModule(EmptyAgentDependencyModule) .overrideProvider(RuntimeProviderAuditService) .useValue({ record: vi.fn().mockResolvedValue(undefined) }) .overrideProvider(RUNTIME_PROVIDER_AUDIT_SINK) .useValue({ record: vi.fn().mockResolvedValue(undefined) }) .overrideProvider(RUNTIME_APPROVAL_VERIFIER) .useValue({ consume: vi.fn().mockResolvedValue(false) }) .overrideProvider(DurableSessionService) .useValue({}) .overrideProvider(DurableSessionRepository) .useValue({}) .overrideProvider(AgentService) .useValue({}) .overrideProvider(ProviderService) .useValue({}) .overrideProvider(ProviderCredentialsService) .useValue({}) .overrideProvider(RoutingService) .useValue({}) .overrideProvider(RoutingEngineService) .useValue({}) .overrideProvider(SkillLoaderService) .useValue({}) .compile(); app = moduleRef.createNestApplication(new FastifyAdapter()); await app.init(); await app.getHttpAdapter().getInstance().ready(); }); afterAll(async (): Promise => { await app?.close(); }); it('returns gateway denial responses from the actual guarded interaction routes', async (): Promise => { if (!app) throw new Error('Nest application did not initialize'); const attachDenied = await app.inject({ method: 'POST', url: '/api/interaction/Nova/sessions/session-1/attach', headers: { 'x-correlation-id': 'correlation-1' }, payload: { mode: 'read' }, }); expect(attachDenied.statusCode).toBe(401); const sendDenied = await app.inject({ method: 'POST', url: '/api/interaction/Nova/sessions/session-1/send', headers: { cookie: 'session=trusted', 'x-correlation-id': 'correlation-1' }, payload: {}, }); expect(sendDenied.statusCode).toBe(403); expect(sendDenied.json()).toMatchObject({ message: 'Content and idempotency key are required', }); const stopDenied = await app.inject({ method: 'POST', url: '/api/interaction/Nova/sessions/session-1/stop', headers: { cookie: 'session=trusted', 'x-correlation-id': 'correlation-1' }, payload: {}, }); expect(stopDenied.statusCode).toBe(403); expect(stopDenied.json()).toMatchObject({ message: 'Exact-action approval is required' }); }); it('requires authentication and reaches the Hermes provider registered by AgentModule', async (): Promise => { if (!app) throw new Error('Nest application did not initialize'); const registry = app.get(AGENT_RUNTIME_PROVIDER_REGISTRY); expect(registry.get('runtime.hermes')).toBeInstanceOf(HermesRuntimeProvider); const denied = await app.inject({ method: 'GET', url: '/api/interaction/Nova/transitional-capabilities?provider=runtime.hermes', headers: { 'x-correlation-id': 'correlation-1' }, }); expect(denied.statusCode).toBe(401); const response = await app.inject({ method: 'GET', url: '/api/interaction/Nova/transitional-capabilities?provider=runtime.hermes', headers: { cookie: 'session=trusted', 'x-correlation-id': 'correlation-1' }, }); expect(response.statusCode).toBe(200); expect(response.json()).toEqual([ { capability: 'kanban', status: 'unsupported' }, { capability: 'skills', status: 'unsupported' }, { capability: 'memory', status: 'unsupported' }, { capability: 'tools', status: 'unsupported' }, { capability: 'cron', status: 'unsupported' }, ]); }); });