All checks were successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
201 lines
5.5 KiB
TypeScript
201 lines
5.5 KiB
TypeScript
import { NotFoundException } from "@nestjs/common";
|
|
import { compare } from "bcryptjs";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { FleetSettingsService } from "./fleet-settings.service";
|
|
import type { PrismaService } from "../prisma/prisma.service";
|
|
import type { CryptoService } from "../crypto/crypto.service";
|
|
|
|
describe("FleetSettingsService", () => {
|
|
let service: FleetSettingsService;
|
|
|
|
const mockPrisma = {
|
|
llmProvider: {
|
|
findMany: vi.fn(),
|
|
findFirst: vi.fn(),
|
|
findUnique: vi.fn(),
|
|
create: vi.fn(),
|
|
update: vi.fn(),
|
|
delete: vi.fn(),
|
|
},
|
|
userAgentConfig: {
|
|
findUnique: vi.fn(),
|
|
upsert: vi.fn(),
|
|
},
|
|
systemConfig: {
|
|
findMany: vi.fn(),
|
|
upsert: vi.fn(),
|
|
deleteMany: vi.fn(),
|
|
},
|
|
breakglassUser: {
|
|
findUnique: vi.fn(),
|
|
update: vi.fn(),
|
|
},
|
|
};
|
|
|
|
const mockCrypto = {
|
|
encrypt: vi.fn((value: string) => `enc:${value}`),
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
service = new FleetSettingsService(
|
|
mockPrisma as unknown as PrismaService,
|
|
mockCrypto as unknown as CryptoService
|
|
);
|
|
});
|
|
|
|
it("listProviders returns only providers for the given userId", async () => {
|
|
mockPrisma.llmProvider.findMany.mockResolvedValue([
|
|
{
|
|
id: "prov-1",
|
|
name: "openai-main",
|
|
displayName: "OpenAI",
|
|
type: "openai",
|
|
baseUrl: "https://api.openai.com/v1",
|
|
isActive: true,
|
|
models: [{ id: "gpt-4.1" }],
|
|
},
|
|
]);
|
|
|
|
const result = await service.listProviders("user-1");
|
|
|
|
expect(mockPrisma.llmProvider.findMany).toHaveBeenCalledWith({
|
|
where: { userId: "user-1" },
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
displayName: true,
|
|
type: true,
|
|
baseUrl: true,
|
|
isActive: true,
|
|
models: true,
|
|
},
|
|
orderBy: { createdAt: "asc" },
|
|
});
|
|
expect(result).toEqual([
|
|
{
|
|
id: "prov-1",
|
|
name: "openai-main",
|
|
displayName: "OpenAI",
|
|
type: "openai",
|
|
baseUrl: "https://api.openai.com/v1",
|
|
isActive: true,
|
|
models: [{ id: "gpt-4.1" }],
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("createProvider encrypts apiKey", async () => {
|
|
mockPrisma.llmProvider.create.mockResolvedValue({
|
|
id: "prov-2",
|
|
});
|
|
|
|
const result = await service.createProvider("user-1", {
|
|
name: "zai-main",
|
|
displayName: "Z.ai",
|
|
type: "zai",
|
|
apiKey: "plaintext-key",
|
|
models: [],
|
|
});
|
|
|
|
expect(mockCrypto.encrypt).toHaveBeenCalledWith("plaintext-key");
|
|
expect(mockPrisma.llmProvider.create).toHaveBeenCalledWith({
|
|
data: {
|
|
userId: "user-1",
|
|
name: "zai-main",
|
|
displayName: "Z.ai",
|
|
type: "zai",
|
|
baseUrl: null,
|
|
apiKey: "enc:plaintext-key",
|
|
apiType: "openai-completions",
|
|
models: [],
|
|
},
|
|
select: {
|
|
id: true,
|
|
},
|
|
});
|
|
expect(result).toEqual({ id: "prov-2" });
|
|
});
|
|
|
|
it("updateProvider rejects if not owned by user", async () => {
|
|
mockPrisma.llmProvider.findFirst.mockResolvedValue(null);
|
|
|
|
await expect(
|
|
service.updateProvider("user-1", "provider-1", {
|
|
displayName: "New Name",
|
|
})
|
|
).rejects.toBeInstanceOf(NotFoundException);
|
|
|
|
expect(mockPrisma.llmProvider.update).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("deleteProvider rejects if not owned by user", async () => {
|
|
mockPrisma.llmProvider.findFirst.mockResolvedValue(null);
|
|
|
|
await expect(service.deleteProvider("user-1", "provider-1")).rejects.toBeInstanceOf(
|
|
NotFoundException
|
|
);
|
|
|
|
expect(mockPrisma.llmProvider.delete).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("getOidcConfig never returns clientSecret", async () => {
|
|
mockPrisma.systemConfig.findMany.mockResolvedValue([
|
|
{
|
|
key: "oidc.issuerUrl",
|
|
value: "https://issuer.example.com",
|
|
},
|
|
{
|
|
key: "oidc.clientId",
|
|
value: "client-id-1",
|
|
},
|
|
{
|
|
key: "oidc.clientSecret",
|
|
value: "enc:very-secret",
|
|
},
|
|
]);
|
|
|
|
const result = await service.getOidcConfig();
|
|
|
|
expect(result).toEqual({
|
|
issuerUrl: "https://issuer.example.com",
|
|
clientId: "client-id-1",
|
|
configured: true,
|
|
});
|
|
expect(result).not.toHaveProperty("clientSecret");
|
|
});
|
|
|
|
it("updateOidcConfig encrypts clientSecret", async () => {
|
|
await service.updateOidcConfig({
|
|
issuerUrl: "https://issuer.example.com",
|
|
clientId: "client-id-1",
|
|
clientSecret: "super-secret",
|
|
});
|
|
|
|
expect(mockCrypto.encrypt).toHaveBeenCalledWith("super-secret");
|
|
expect(mockPrisma.systemConfig.upsert).toHaveBeenCalledTimes(3);
|
|
expect(mockPrisma.systemConfig.upsert).toHaveBeenCalledWith({
|
|
where: { key: "oidc.clientSecret" },
|
|
update: { value: "enc:super-secret", encrypted: true },
|
|
create: { key: "oidc.clientSecret", value: "enc:super-secret", encrypted: true },
|
|
});
|
|
});
|
|
|
|
it("resetBreakglassPassword hashes new password", async () => {
|
|
mockPrisma.breakglassUser.findUnique.mockResolvedValue({
|
|
id: "bg-1",
|
|
username: "admin",
|
|
passwordHash: "old-hash",
|
|
});
|
|
|
|
await service.resetBreakglassPassword("admin", "new-password-123");
|
|
|
|
expect(mockPrisma.breakglassUser.update).toHaveBeenCalledOnce();
|
|
const updateCall = mockPrisma.breakglassUser.update.mock.calls[0]?.[0];
|
|
const newHash = updateCall?.data?.passwordHash;
|
|
expect(newHash).toBeTypeOf("string");
|
|
expect(newHash).not.toBe("new-password-123");
|
|
expect(await compare("new-password-123", newHash as string)).toBe(true);
|
|
});
|
|
});
|