import { describe, it, expect, vi, beforeAll, afterAll, beforeEach } from 'vitest';
import Fastify from 'fastify';
import type { FastifyInstance } from 'fastify';
import http from 'node:http';
import { McpServerService } from '../src/services/mcp-server.service.js';
import { InstanceService } from '../src/services/instance.service.js';
import { McpProxyService } from '../src/services/mcp-proxy-service.js';
import { AuditLogService } from '../src/services/audit-log.service.js';
import { errorHandler } from '../src/middleware/error-handler.js';
import { registerMcpServerRoutes } from '../src/routes/mcp-servers.js';
import { registerInstanceRoutes } from '../src/routes/instances.js';
import { registerMcpProxyRoutes } from '../src/routes/mcp-proxy.js';
import type {
  IMcpServerRepository,
  IMcpInstanceRepository,
  IAuditLogRepository,
} from '../src/repositories/interfaces.js';
import type { McpOrchestrator } from '../src/services/orchestrator.js';
import type { McpServer, McpInstance, InstanceStatus } from '@prisma/client';

// ---------------------------------------------------------------------------
// In-memory repository implementations (stateful mocks)
// ---------------------------------------------------------------------------

function createInMemoryServerRepo(): IMcpServerRepository {
  const servers = new Map<string, McpServer>();
  let nextId = 1;

  return {
    findAll: vi.fn(async () => [...servers.values()]),
    findById: vi.fn(async (id: string) => servers.get(id) ?? null),
    findByName: vi.fn(async (name: string) => [...servers.values()].find((s) => s.name === name) ?? null),
    create: vi.fn(async (data) => {
      const id = `srv-${nextId++}`;
      const server = {
        id,
        name: data.name,
        description: data.description ?? '',
        packageName: data.packageName ?? null,
        dockerImage: data.dockerImage ?? null,
        transport: data.transport ?? 'STDIO',
        repositoryUrl: data.repositoryUrl ?? null,
        externalUrl: data.externalUrl ?? null,
        command: data.command ?? null,
        containerPort: data.containerPort ?? null,
        replicas: data.replicas ?? 1,
        env: data.env ?? [],
        version: 1,
        createdAt: new Date(),
        updatedAt: new Date(),
      } as McpServer;
      servers.set(id, server);
      return server;
    }),
    update: vi.fn(async (id: string, data) => {
      const existing = servers.get(id);
      if (!existing) throw new Error(`Server ${id} not found`);
      const updated = { ...existing, ...data, updatedAt: new Date() } as McpServer;
      servers.set(id, updated);
      return updated;
    }),
    delete: vi.fn(async (id: string) => {
      servers.delete(id);
    }),
  };
}

function createInMemoryInstanceRepo(): IMcpInstanceRepository {
  const instances = new Map<string, McpInstance>();
  let nextId = 1;

  return {
    findAll: vi.fn(async (serverId?: string) => {
      const all = [...instances.values()];
      return serverId ? all.filter((i) => i.serverId === serverId) : all;
    }),
    findById: vi.fn(async (id: string) => instances.get(id) ?? null),
    findByContainerId: vi.fn(async (containerId: string) =>
      [...instances.values()].find((i) => i.containerId === containerId) ?? null,
    ),
    create: vi.fn(async (data) => {
      const id = `inst-${nextId++}`;
      const instance = {
        id,
        serverId: data.serverId,
        containerId: data.containerId ?? null,
        status: (data.status ?? 'STOPPED') as InstanceStatus,
        port: data.port ?? null,
        metadata: data.metadata ?? {},
        version: 1,
        createdAt: new Date(),
        updatedAt: new Date(),
      } as McpInstance;
      instances.set(id, instance);
      return instance;
    }),
    updateStatus: vi.fn(async (id: string, status: InstanceStatus, fields?) => {
      const existing = instances.get(id);
      if (!existing) throw new Error(`Instance ${id} not found`);
      const updated = {
        ...existing,
        status,
        ...(fields?.containerId !== undefined ? { containerId: fields.containerId } : {}),
        ...(fields?.port !== undefined ? { port: fields.port } : {}),
        ...(fields?.metadata !== undefined ? { metadata: fields.metadata } : {}),
        version: existing.version + 1,
        updatedAt: new Date(),
      } as McpInstance;
      instances.set(id, updated);
      return updated;
    }),
    delete: vi.fn(async (id: string) => {
      instances.delete(id);
    }),
  };
}

function createInMemoryAuditLogRepo(): IAuditLogRepository {
  const logs: Array<{ id: string; userId: string; action: string; resource: string; resourceId: string | null; details: Record<string, unknown>; createdAt: Date }> = [];
  let nextId = 1;

  return {
    findAll: vi.fn(async () => logs as never[]),
    findById: vi.fn(async (id: string) => (logs.find((l) => l.id === id) as never) ?? null),
    create: vi.fn(async (data) => {
      const log = {
        id: `log-${nextId++}`,
        userId: data.userId,
        action: data.action,
        resource: data.resource,
        resourceId: data.resourceId ?? null,
        details: data.details ?? {},
        createdAt: new Date(),
      };
      logs.push(log);
      return log as never;
    }),
    count: vi.fn(async () => logs.length),
    deleteOlderThan: vi.fn(async () => 0),
  };
}

function createMockOrchestrator(): McpOrchestrator {
  let containerPort = 40000;
  return {
    ping: vi.fn(async () => true),
    pullImage: vi.fn(async () => {}),
    createContainer: vi.fn(async (spec) => ({
      containerId: `ctr-${spec.name}`,
      name: spec.name,
      state: 'running' as const,
      port: spec.containerPort ?? ++containerPort,
      createdAt: new Date(),
    })),
    stopContainer: vi.fn(async () => {}),
    removeContainer: vi.fn(async () => {}),
    inspectContainer: vi.fn(async (id) => ({
      containerId: id,
      name: 'test',
      state: 'running' as const,
      createdAt: new Date(),
    })),
    getContainerLogs: vi.fn(async () => ({ stdout: '', stderr: '' })),
  };
}

// ---------------------------------------------------------------------------
// Fake MCP server (streamable-http)
// ---------------------------------------------------------------------------

function createFakeMcpServer(): { server: http.Server; getPort: () => number; requests: Array<{ method: string; body: unknown }> } {
  const requests: Array<{ method: string; body: unknown }> = [];
  let sessionCounter = 0;

  const server = http.createServer((req, res) => {
    let body = '';
    req.on('data', (chunk) => (body += chunk));
    req.on('end', () => {
      let parsed: { method?: string; id?: number; params?: unknown } = {};
      try {
        parsed = JSON.parse(body);
      } catch {
        // notifications may not have id
      }

      requests.push({ method: parsed.method ?? 'unknown', body: parsed });

      if (parsed.method === 'initialize') {
        const sessionId = `session-${++sessionCounter}`;
        const response = {
          jsonrpc: '2.0',
          id: parsed.id,
          result: {
            protocolVersion: '2025-03-26',
            capabilities: { tools: {} },
            serverInfo: { name: 'fake-mcp', version: '1.0.0' },
          },
        };
        res.writeHead(200, {
          'Content-Type': 'text/event-stream',
          'Mcp-Session-Id': sessionId,
        });
        res.end(`event: message\ndata: ${JSON.stringify(response)}\n\n`);
        return;
      }

      if (parsed.method === 'notifications/initialized') {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end('');
        return;
      }

      if (parsed.method === 'tools/list') {
        const response = {
          jsonrpc: '2.0',
          id: parsed.id,
          result: {
            tools: [
              { name: 'ha_get_overview', description: 'Get Home Assistant overview', inputSchema: { type: 'object', properties: {} } },
              { name: 'ha_search_entities', description: 'Search HA entities', inputSchema: { type: 'object', properties: { query: { type: 'string' } } } },
            ],
          },
        };
        res.writeHead(200, { 'Content-Type': 'text/event-stream' });
        res.end(`event: message\ndata: ${JSON.stringify(response)}\n\n`);
        return;
      }

      if (parsed.method === 'tools/call') {
        const toolName = (parsed.params as { name?: string })?.name;
        const response = {
          jsonrpc: '2.0',
          id: parsed.id,
          result: {
            content: [{ type: 'text', text: `Result from ${toolName}` }],
          },
        };
        res.writeHead(200, { 'Content-Type': 'text/event-stream' });
        res.end(`event: message\ndata: ${JSON.stringify(response)}\n\n`);
        return;
      }

      // Default: echo back
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ jsonrpc: '2.0', id: parsed.id, result: {} }));
    });
  });

  let port = 0;
  return {
    server,
    getPort: () => port,
    requests,
    ...{
      listen: () =>
        new Promise<void>((resolve) => {
          server.listen(0, () => {
            const addr = server.address();
            if (addr && typeof addr === 'object') port = addr.port;
            resolve();
          });
        }),
      close: () => new Promise<void>((resolve) => server.close(() => resolve())),
    },
  } as ReturnType<typeof createFakeMcpServer> & { listen: () => Promise<void>; close: () => Promise<void> };
}

// ---------------------------------------------------------------------------
// Test app builder
// ---------------------------------------------------------------------------

async function buildTestApp(deps: {
  serverRepo: IMcpServerRepository;
  instanceRepo: IMcpInstanceRepository;
  auditLogRepo: IAuditLogRepository;
  orchestrator: McpOrchestrator;
}): Promise<FastifyInstance> {
  const app = Fastify({ logger: false });
  app.setErrorHandler(errorHandler);

  const serverService = new McpServerService(deps.serverRepo);
  const instanceService = new InstanceService(deps.instanceRepo, deps.serverRepo, deps.orchestrator);
  serverService.setInstanceService(instanceService);
  const proxyService = new McpProxyService(deps.instanceRepo, deps.serverRepo);
  const auditLogService = new AuditLogService(deps.auditLogRepo);

  registerMcpServerRoutes(app, serverService, instanceService);
  registerInstanceRoutes(app, instanceService);
  registerMcpProxyRoutes(app, {
    mcpProxyService: proxyService,
    auditLogService,
    authDeps: {
      findSession: async () => ({ userId: 'test-user', expiresAt: new Date(Date.now() + 3600_000) }),
    },
  });

  await app.ready();
  return app;
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe('MCP server full flow', () => {
  let fakeMcp: ReturnType<typeof createFakeMcpServer> & { listen: () => Promise<void>; close: () => Promise<void> };
  let fakeMcpPort: number;

  beforeAll(async () => {
    fakeMcp = createFakeMcpServer() as typeof fakeMcp;
    await fakeMcp.listen();
    fakeMcpPort = fakeMcp.getPort();
  });

  afterAll(async () => {
    await fakeMcp.close();
  });

  describe('external server flow (externalUrl)', () => {
    let app: FastifyInstance;
    let serverRepo: IMcpServerRepository;
    let instanceRepo: IMcpInstanceRepository;

    beforeEach(async () => {
      serverRepo = createInMemoryServerRepo();
      instanceRepo = createInMemoryInstanceRepo();
      app = await buildTestApp({
        serverRepo,
        instanceRepo,
        auditLogRepo: createInMemoryAuditLogRepo(),
        orchestrator: createMockOrchestrator(),
      });
    });

    afterAll(async () => {
      if (app) await app.close();
    });

    it('registers server (auto-creates instance via reconcile), and proxies tools/list', async () => {
      // 1. Register external MCP server (replicas defaults to 1 → auto-creates instance)
      const createRes = await app.inject({
        method: 'POST',
        url: '/api/v1/servers',
        payload: {
          name: 'ha-mcp',
          description: 'Home Assistant MCP',
          transport: 'STREAMABLE_HTTP',
          externalUrl: `http://localhost:${fakeMcpPort}`,
          containerPort: 3000,
          env: [
            { name: 'HOMEASSISTANT_TOKEN', value: 'placeholder' },
          ],
        },
      });

      expect(createRes.statusCode).toBe(201);
      const server = createRes.json<{ id: string; name: string; externalUrl: string }>();
      expect(server.name).toBe('ha-mcp');
      expect(server.externalUrl).toBe(`http://localhost:${fakeMcpPort}`);

      // 2. Verify server is listed
      const listRes = await app.inject({ method: 'GET', url: '/api/v1/servers' });
      expect(listRes.statusCode).toBe(200);
      const servers = listRes.json<Array<{ name: string }>>();
      expect(servers).toHaveLength(1);
      expect(servers[0]!.name).toBe('ha-mcp');

      // 3. Verify instance was auto-created (no Docker for external servers)
      const instancesRes = await app.inject({
        method: 'GET',
        url: `/api/v1/instances?serverId=${server.id}`,
      });
      expect(instancesRes.statusCode).toBe(200);
      const instances = instancesRes.json<Array<{ id: string; status: string; containerId: string | null }>>();
      expect(instances).toHaveLength(1);
      expect(instances[0]!.status).toBe('RUNNING');
      expect(instances[0]!.containerId).toBeNull();

      // 4. Proxy tools/list to the fake MCP server
      const proxyRes = await app.inject({
        method: 'POST',
        url: '/api/v1/mcp/proxy',
        headers: { authorization: 'Bearer test-token' },
        payload: {
          serverId: server.id,
          method: 'tools/list',
        },
      });

      expect(proxyRes.statusCode).toBe(200);
      const proxyBody = proxyRes.json<{ jsonrpc: string; result: { tools: Array<{ name: string }> } }>();
      expect(proxyBody.jsonrpc).toBe('2.0');
      expect(proxyBody.result.tools).toHaveLength(2);
      expect(proxyBody.result.tools.map((t) => t.name)).toContain('ha_get_overview');
      expect(proxyBody.result.tools.map((t) => t.name)).toContain('ha_search_entities');

      // 5. Verify the fake server received the protocol handshake + tools/list
      const methods = fakeMcp.requests.map((r) => r.method);
      expect(methods).toContain('initialize');
      expect(methods).toContain('notifications/initialized');
      expect(methods).toContain('tools/list');
    });

    it('proxies tools/call with parameters', async () => {
      // Register (auto-creates instance via reconcile)
      const createRes = await app.inject({
        method: 'POST',
        url: '/api/v1/servers',
        payload: {
          name: 'ha-mcp-call',
          description: 'HA MCP for call test',
          transport: 'STREAMABLE_HTTP',
          externalUrl: `http://localhost:${fakeMcpPort}`,
        },
      });
      const server = createRes.json<{ id: string }>();

      // Proxy tools/call (instance was auto-created)
      const proxyRes = await app.inject({
        method: 'POST',
        url: '/api/v1/mcp/proxy',
        headers: { authorization: 'Bearer test-token' },
        payload: {
          serverId: server.id,
          method: 'tools/call',
          params: { name: 'ha_get_overview' },
        },
      });

      expect(proxyRes.statusCode).toBe(200);
      const body = proxyRes.json<{ result: { content: Array<{ text: string }> } }>();
      expect(body.result.content[0]!.text).toBe('Result from ha_get_overview');
    });
  });

  describe('managed server flow (Docker)', () => {
    let app: FastifyInstance;
    let orchestrator: ReturnType<typeof createMockOrchestrator>;

    beforeEach(async () => {
      orchestrator = createMockOrchestrator();
      app = await buildTestApp({
        serverRepo: createInMemoryServerRepo(),
        instanceRepo: createInMemoryInstanceRepo(),
        auditLogRepo: createInMemoryAuditLogRepo(),
        orchestrator,
      });
    });

    afterAll(async () => {
      if (app) await app.close();
    });

    it('registers server with dockerImage, auto-creates container instance via reconcile', async () => {
      // 1. Register managed server (replicas: 1 → auto-creates container)
      const createRes = await app.inject({
        method: 'POST',
        url: '/api/v1/servers',
        payload: {
          name: 'ha-mcp-docker',
          description: 'HA MCP managed by Docker',
          dockerImage: 'ghcr.io/homeassistant-ai/ha-mcp:2.4',
          transport: 'STREAMABLE_HTTP',
          containerPort: 3000,
          command: ['python', '-c', 'print("hello")'],
          env: [
            { name: 'HOMEASSISTANT_URL', value: 'http://localhost:8123' },
            { name: 'HOMEASSISTANT_TOKEN', valueFrom: { secretRef: { name: 'ha-secrets', key: 'token' } } },
          ],
        },
      });

      expect(createRes.statusCode).toBe(201);
      const server = createRes.json<{ id: string; name: string; dockerImage: string; command: string[] }>();
      expect(server.name).toBe('ha-mcp-docker');
      expect(server.dockerImage).toBe('ghcr.io/homeassistant-ai/ha-mcp:2.4');
      expect(server.command).toEqual(['python', '-c', 'print("hello")']);

      // 2. Verify instance was auto-created with container
      const instancesRes = await app.inject({
        method: 'GET',
        url: `/api/v1/instances?serverId=${server.id}`,
      });
      expect(instancesRes.statusCode).toBe(200);
      const instances = instancesRes.json<Array<{ id: string; status: string; containerId: string }>>();
      expect(instances).toHaveLength(1);
      expect(instances[0]!.status).toBe('RUNNING');
      expect(instances[0]!.containerId).toBeTruthy();

      // 3. Verify orchestrator was called with correct spec
      expect(orchestrator.createContainer).toHaveBeenCalledTimes(1);
      const spec = vi.mocked(orchestrator.createContainer).mock.calls[0]![0];
      expect(spec.image).toBe('ghcr.io/homeassistant-ai/ha-mcp:2.4');
      expect(spec.containerPort).toBe(3000);
      expect(spec.command).toEqual(['python', '-c', 'print("hello")']);
    });

    it('marks instance as ERROR when Docker fails', async () => {
      vi.mocked(orchestrator.createContainer).mockRejectedValueOnce(new Error('Docker socket unavailable'));

      // Creating server triggers reconcile which tries to create container → fails
      const createRes = await app.inject({
        method: 'POST',
        url: '/api/v1/servers',
        payload: {
          name: 'failing-server',
          description: 'Will fail to start',
          dockerImage: 'some-image:latest',
          transport: 'STDIO',
        },
      });
      expect(createRes.statusCode).toBe(201);

      const server = createRes.json<{ id: string }>();
      const instancesRes = await app.inject({
        method: 'GET',
        url: `/api/v1/instances?serverId=${server.id}`,
      });
      const instances = instancesRes.json<Array<{ id: string; status: string }>>();
      expect(instances).toHaveLength(1);
      expect(instances[0]!.status).toBe('ERROR');
    });
  });

  describe('full lifecycle', () => {
    let app: FastifyInstance;
    let orchestrator: ReturnType<typeof createMockOrchestrator>;

    beforeEach(async () => {
      orchestrator = createMockOrchestrator();
      app = await buildTestApp({
        serverRepo: createInMemoryServerRepo(),
        instanceRepo: createInMemoryInstanceRepo(),
        auditLogRepo: createInMemoryAuditLogRepo(),
        orchestrator,
      });
    });

    afterAll(async () => {
      if (app) await app.close();
    });

    it('register → auto-create → list → delete instance (reconcile) → delete server (cascade)', async () => {
      // Register (auto-creates instance via reconcile)
      const createRes = await app.inject({
        method: 'POST',
        url: '/api/v1/servers',
        payload: {
          name: 'lifecycle-test',
          description: 'Full lifecycle',
          dockerImage: 'test:latest',
          transport: 'SSE',
          containerPort: 8080,
        },
      });
      expect(createRes.statusCode).toBe(201);
      const server = createRes.json<{ id: string }>();

      // List instances (auto-created)
      const listRes = await app.inject({
        method: 'GET',
        url: `/api/v1/instances?serverId=${server.id}`,
      });
      expect(listRes.statusCode).toBe(200);
      const instances = listRes.json<Array<{ id: string; status: string }>>();
      expect(instances).toHaveLength(1);
      expect(instances[0]!.status).toBe('RUNNING');
      const instanceId = instances[0]!.id;

      // Delete instance → triggers reconcile → new instance auto-created
      const removeRes = await app.inject({
        method: 'DELETE',
        url: `/api/v1/instances/${instanceId}`,
      });
      expect(removeRes.statusCode).toBe(204);

      // Verify a replacement instance was created (reconcile)
      const listAfter = await app.inject({
        method: 'GET',
        url: `/api/v1/instances?serverId=${server.id}`,
      });
      const afterInstances = listAfter.json<Array<{ id: string }>>();
      expect(afterInstances).toHaveLength(1);
      expect(afterInstances[0]!.id).not.toBe(instanceId); // New instance, not the old one

      // Delete server (cascade removes all instances)
      const deleteRes = await app.inject({
        method: 'DELETE',
        url: `/api/v1/servers/${server.id}`,
      });
      expect(deleteRes.statusCode).toBe(204);

      // Verify server is gone
      const serversAfter = await app.inject({ method: 'GET', url: '/api/v1/servers' });
      expect(serversAfter.json<unknown[]>()).toHaveLength(0);
    });

    it('external server lifecycle: register → auto-create → proxy → delete server (cascade)', async () => {
      // Register external (auto-creates virtual instance)
      const createRes = await app.inject({
        method: 'POST',
        url: '/api/v1/servers',
        payload: {
          name: 'external-lifecycle',
          transport: 'STREAMABLE_HTTP',
          externalUrl: `http://localhost:${fakeMcpPort}`,
        },
      });
      const server = createRes.json<{ id: string }>();

      // Verify auto-created instance
      const instancesRes = await app.inject({
        method: 'GET',
        url: `/api/v1/instances?serverId=${server.id}`,
      });
      const instances = instancesRes.json<Array<{ id: string; status: string; containerId: string | null }>>();
      expect(instances).toHaveLength(1);
      expect(instances[0]!.status).toBe('RUNNING');
      expect(instances[0]!.containerId).toBeNull();

      // Proxy tools/list
      const proxyRes = await app.inject({
        method: 'POST',
        url: '/api/v1/mcp/proxy',
        headers: { authorization: 'Bearer test-token' },
        payload: { serverId: server.id, method: 'tools/list' },
      });
      expect(proxyRes.statusCode).toBe(200);
      expect(proxyRes.json<{ result: { tools: unknown[] } }>().result.tools.length).toBeGreaterThan(0);

      // Docker orchestrator should NOT have been called (external server)
      expect(orchestrator.createContainer).not.toHaveBeenCalled();
      expect(orchestrator.stopContainer).not.toHaveBeenCalled();

      // Delete server (cascade)
      const deleteRes = await app.inject({
        method: 'DELETE',
        url: `/api/v1/servers/${server.id}`,
      });
      expect(deleteRes.statusCode).toBe(204);
    });
  });

  describe('proxy authentication', () => {
    let app: FastifyInstance;

    beforeEach(async () => {
      app = await buildTestApp({
        serverRepo: createInMemoryServerRepo(),
        instanceRepo: createInMemoryInstanceRepo(),
        auditLogRepo: createInMemoryAuditLogRepo(),
        orchestrator: createMockOrchestrator(),
      });
    });

    afterAll(async () => {
      if (app) await app.close();
    });

    it('rejects proxy calls without auth header', async () => {
      const res = await app.inject({
        method: 'POST',
        url: '/api/v1/mcp/proxy',
        payload: { serverId: 'srv-1', method: 'tools/list' },
      });
      // Auth middleware rejects with 401 (no Bearer token)
      expect(res.statusCode).toBe(401);
    });
  });

  describe('server update flow', () => {
    let app: FastifyInstance;

    beforeEach(async () => {
      app = await buildTestApp({
        serverRepo: createInMemoryServerRepo(),
        instanceRepo: createInMemoryInstanceRepo(),
        auditLogRepo: createInMemoryAuditLogRepo(),
        orchestrator: createMockOrchestrator(),
      });
    });

    afterAll(async () => {
      if (app) await app.close();
    });

    it('creates and updates server fields', async () => {
      // Create (with replicas: 0 to avoid creating instances in this test)
      const createRes = await app.inject({
        method: 'POST',
        url: '/api/v1/servers',
        payload: {
          name: 'updatable',
          description: 'Original desc',
          transport: 'STDIO',
          replicas: 0,
        },
      });
      expect(createRes.statusCode).toBe(201);
      const server = createRes.json<{ id: string; description: string }>();
      expect(server.description).toBe('Original desc');

      // Update
      const updateRes = await app.inject({
        method: 'PUT',
        url: `/api/v1/servers/${server.id}`,
        payload: {
          description: 'Updated desc',
          externalUrl: `http://localhost:${fakeMcpPort}`,
          transport: 'STREAMABLE_HTTP',
        },
      });
      expect(updateRes.statusCode).toBe(200);
      const updated = updateRes.json<{ description: string; externalUrl: string; transport: string }>();
      expect(updated.description).toBe('Updated desc');
      expect(updated.externalUrl).toBe(`http://localhost:${fakeMcpPort}`);
      expect(updated.transport).toBe('STREAMABLE_HTTP');

      // Fetch to verify persistence
      const getRes = await app.inject({
        method: 'GET',
        url: `/api/v1/servers/${server.id}`,
      });
      expect(getRes.json<{ description: string }>().description).toBe('Updated desc');
    });
  });
});
