import { beforeEach, describe, expect, it, vi } from "vitest"; import { NotFoundException } from "@nestjs/common"; import { EncryptionService } from "../../security/encryption.service"; import { AgentProvidersService } from "./agent-providers.service"; import { PrismaService } from "../../prisma/prisma.service"; describe("AgentProvidersService", () => { let service: AgentProvidersService; let prisma: { agentProviderConfig: { findMany: ReturnType; findUnique: ReturnType; create: ReturnType; update: ReturnType; delete: ReturnType; }; }; let encryptionService: { encryptIfNeeded: ReturnType; }; beforeEach(() => { prisma = { agentProviderConfig: { findMany: vi.fn(), findUnique: vi.fn(), create: vi.fn(), update: vi.fn(), delete: vi.fn(), }, }; encryptionService = { encryptIfNeeded: vi.fn((value: string) => `enc:${value}`), }; service = new AgentProvidersService( prisma as unknown as PrismaService, encryptionService as unknown as EncryptionService ); }); it("lists all provider configs", async () => { const expected = [ { id: "cfg-1", workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369", name: "Primary", provider: "openai", gatewayUrl: "https://gateway.example.com", credentials: {}, isActive: true, createdAt: new Date("2026-03-07T18:00:00.000Z"), updatedAt: new Date("2026-03-07T18:00:00.000Z"), }, ]; prisma.agentProviderConfig.findMany.mockResolvedValue(expected); const result = await service.list(); expect(prisma.agentProviderConfig.findMany).toHaveBeenCalledWith({ orderBy: [{ createdAt: "desc" }, { id: "desc" }], }); expect(result).toEqual(expected); }); it("returns a single provider config", async () => { const expected = { id: "cfg-1", workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369", name: "Primary", provider: "openai", gatewayUrl: "https://gateway.example.com", credentials: { apiKeyRef: "vault:openai" }, isActive: true, createdAt: new Date("2026-03-07T18:00:00.000Z"), updatedAt: new Date("2026-03-07T18:00:00.000Z"), }; prisma.agentProviderConfig.findUnique.mockResolvedValue(expected); const result = await service.getById("cfg-1"); expect(prisma.agentProviderConfig.findUnique).toHaveBeenCalledWith({ where: { id: "cfg-1" }, }); expect(result).toEqual(expected); }); it("throws NotFoundException when provider config is missing", async () => { prisma.agentProviderConfig.findUnique.mockResolvedValue(null); await expect(service.getById("missing")).rejects.toBeInstanceOf(NotFoundException); }); it("creates a provider config with default credentials", async () => { const created = { id: "cfg-created", workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369", name: "New Provider", provider: "claude", gatewayUrl: "https://gateway.example.com", credentials: {}, isActive: true, createdAt: new Date("2026-03-07T18:00:00.000Z"), updatedAt: new Date("2026-03-07T18:00:00.000Z"), }; prisma.agentProviderConfig.create.mockResolvedValue(created); const result = await service.create({ workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369", name: "New Provider", provider: "claude", gatewayUrl: "https://gateway.example.com", }); expect(prisma.agentProviderConfig.create).toHaveBeenCalledWith({ data: { workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369", name: "New Provider", provider: "claude", gatewayUrl: "https://gateway.example.com", credentials: {}, }, }); expect(encryptionService.encryptIfNeeded).not.toHaveBeenCalled(); expect(result).toEqual(created); }); it("encrypts openclaw token credentials when creating provider config", async () => { const created = { id: "cfg-openclaw", workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369", name: "OpenClaw", provider: "openclaw", gatewayUrl: "https://openclaw.example.com", credentials: { apiToken: "enc:top-secret" }, isActive: true, createdAt: new Date("2026-03-07T18:00:00.000Z"), updatedAt: new Date("2026-03-07T18:00:00.000Z"), }; prisma.agentProviderConfig.create.mockResolvedValue(created); const result = await service.create({ workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369", name: "OpenClaw", provider: "openclaw", gatewayUrl: "https://openclaw.example.com", credentials: { apiToken: "top-secret" }, }); expect(encryptionService.encryptIfNeeded).toHaveBeenCalledWith("top-secret"); expect(prisma.agentProviderConfig.create).toHaveBeenCalledWith({ data: { workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369", name: "OpenClaw", provider: "openclaw", gatewayUrl: "https://openclaw.example.com", credentials: { apiToken: "enc:top-secret" }, }, }); expect(result).toEqual(created); }); it("updates a provider config", async () => { prisma.agentProviderConfig.findUnique.mockResolvedValue({ id: "cfg-1", workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369", name: "Primary", provider: "openai", gatewayUrl: "https://gateway.example.com", credentials: {}, isActive: true, createdAt: new Date("2026-03-07T18:00:00.000Z"), updatedAt: new Date("2026-03-07T18:00:00.000Z"), }); const updated = { id: "cfg-1", workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369", name: "Secondary", provider: "openai", gatewayUrl: "https://gateway2.example.com", credentials: { apiKeyRef: "vault:new" }, isActive: false, createdAt: new Date("2026-03-07T18:00:00.000Z"), updatedAt: new Date("2026-03-07T19:00:00.000Z"), }; prisma.agentProviderConfig.update.mockResolvedValue(updated); const result = await service.update("cfg-1", { name: "Secondary", gatewayUrl: "https://gateway2.example.com", credentials: { apiKeyRef: "vault:new" }, isActive: false, }); expect(prisma.agentProviderConfig.update).toHaveBeenCalledWith({ where: { id: "cfg-1" }, data: { name: "Secondary", gatewayUrl: "https://gateway2.example.com", credentials: { apiKeyRef: "vault:new" }, isActive: false, }, }); expect(encryptionService.encryptIfNeeded).not.toHaveBeenCalled(); expect(result).toEqual(updated); }); it("encrypts openclaw token credentials when updating provider config", async () => { prisma.agentProviderConfig.findUnique.mockResolvedValue({ id: "cfg-openclaw", workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369", name: "OpenClaw", provider: "openclaw", gatewayUrl: "https://openclaw.example.com", credentials: { apiToken: "enc:existing" }, isActive: true, createdAt: new Date("2026-03-07T18:00:00.000Z"), updatedAt: new Date("2026-03-07T18:00:00.000Z"), }); const updated = { id: "cfg-openclaw", workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369", name: "OpenClaw", provider: "openclaw", gatewayUrl: "https://openclaw.example.com", credentials: { apiToken: "enc:rotated-token" }, isActive: true, createdAt: new Date("2026-03-07T18:00:00.000Z"), updatedAt: new Date("2026-03-07T19:00:00.000Z"), }; prisma.agentProviderConfig.update.mockResolvedValue(updated); const result = await service.update("cfg-openclaw", { credentials: { apiToken: "rotated-token" }, }); expect(encryptionService.encryptIfNeeded).toHaveBeenCalledWith("rotated-token"); expect(prisma.agentProviderConfig.update).toHaveBeenCalledWith({ where: { id: "cfg-openclaw" }, data: { credentials: { apiToken: "enc:rotated-token" }, }, }); expect(result).toEqual(updated); }); it("throws NotFoundException when updating a missing provider config", async () => { prisma.agentProviderConfig.findUnique.mockResolvedValue(null); await expect(service.update("missing", { name: "Updated" })).rejects.toBeInstanceOf( NotFoundException ); expect(prisma.agentProviderConfig.update).not.toHaveBeenCalled(); }); it("deletes a provider config", async () => { prisma.agentProviderConfig.findUnique.mockResolvedValue({ id: "cfg-1", workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369", name: "Primary", provider: "openai", gatewayUrl: "https://gateway.example.com", credentials: {}, isActive: true, createdAt: new Date("2026-03-07T18:00:00.000Z"), updatedAt: new Date("2026-03-07T18:00:00.000Z"), }); const deleted = { id: "cfg-1", workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369", name: "Primary", provider: "openai", gatewayUrl: "https://gateway.example.com", credentials: {}, isActive: true, createdAt: new Date("2026-03-07T18:00:00.000Z"), updatedAt: new Date("2026-03-07T18:00:00.000Z"), }; prisma.agentProviderConfig.delete.mockResolvedValue(deleted); const result = await service.delete("cfg-1"); expect(prisma.agentProviderConfig.delete).toHaveBeenCalledWith({ where: { id: "cfg-1" }, }); expect(result).toEqual(deleted); }); it("throws NotFoundException when deleting a missing provider config", async () => { prisma.agentProviderConfig.findUnique.mockResolvedValue(null); await expect(service.delete("missing")).rejects.toBeInstanceOf(NotFoundException); expect(prisma.agentProviderConfig.delete).not.toHaveBeenCalled(); }); });