Compare commits

..

1 Commits

Author SHA1 Message Date
a81668d4fe chore(ms22-p2): update mission docs — P2-008 complete
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:30:55 -06:00
2 changed files with 2 additions and 444 deletions

View File

@@ -1,8 +1,4 @@
import {
ServiceUnavailableException,
NotFoundException,
BadGatewayException,
} from "@nestjs/common";
import { ServiceUnavailableException } from "@nestjs/common";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ChatProxyService } from "./chat-proxy.service";
@@ -13,9 +9,6 @@ describe("ChatProxyService", () => {
userAgentConfig: {
findUnique: vi.fn(),
},
userAgent: {
findUnique: vi.fn(),
},
};
const containerLifecycle = {
@@ -23,17 +16,13 @@ describe("ChatProxyService", () => {
touch: vi.fn(),
};
const config = {
get: vi.fn(),
};
let service: ChatProxyService;
let fetchMock: ReturnType<typeof vi.fn>;
beforeEach(() => {
fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
service = new ChatProxyService(prisma as never, containerLifecycle as never, config as never);
service = new ChatProxyService(prisma as never, containerLifecycle as never);
});
afterEach(() => {
@@ -116,135 +105,4 @@ describe("ChatProxyService", () => {
);
});
});
describe("proxyChat with agent routing", () => {
it("includes agent config when agentName is specified", async () => {
const mockAgent = {
name: "jarvis",
displayName: "Jarvis",
personality: "Capable, direct, proactive.",
primaryModel: "opus",
isActive: true,
};
containerLifecycle.ensureRunning.mockResolvedValue({
url: "http://mosaic-user-user-123:19000",
token: "gateway-token",
});
containerLifecycle.touch.mockResolvedValue(undefined);
prisma.userAgent.findUnique.mockResolvedValue(mockAgent);
fetchMock.mockResolvedValue(new Response("event: token\ndata: hello\n\n"));
const messages = [{ role: "user", content: "Hello Jarvis" }];
await service.proxyChat(userId, messages, undefined, "jarvis");
const [, request] = fetchMock.mock.calls[0] as [string, RequestInit];
const parsedBody = JSON.parse(String(request.body));
expect(parsedBody).toEqual({
messages,
model: "opus",
stream: true,
agent: "jarvis",
agent_personality: "Capable, direct, proactive.",
});
});
it("throws NotFoundException when agent not found", async () => {
containerLifecycle.ensureRunning.mockResolvedValue({
url: "http://mosaic-user-user-123:19000",
token: "gateway-token",
});
containerLifecycle.touch.mockResolvedValue(undefined);
prisma.userAgent.findUnique.mockResolvedValue(null);
const messages = [{ role: "user", content: "Hello" }];
await expect(service.proxyChat(userId, messages, undefined, "nonexistent")).rejects.toThrow(
NotFoundException
);
});
it("throws NotFoundException when agent is not active", async () => {
containerLifecycle.ensureRunning.mockResolvedValue({
url: "http://mosaic-user-user-123:19000",
token: "gateway-token",
});
containerLifecycle.touch.mockResolvedValue(undefined);
prisma.userAgent.findUnique.mockResolvedValue({
name: "inactive-agent",
displayName: "Inactive",
personality: "...",
primaryModel: null,
isActive: false,
});
const messages = [{ role: "user", content: "Hello" }];
await expect(
service.proxyChat(userId, messages, undefined, "inactive-agent")
).rejects.toThrow(NotFoundException);
});
it("falls back to default model when agent has no primaryModel", async () => {
const mockAgent = {
name: "jarvis",
displayName: "Jarvis",
personality: "Capable, direct, proactive.",
primaryModel: null,
isActive: true,
};
containerLifecycle.ensureRunning.mockResolvedValue({
url: "http://mosaic-user-user-123:19000",
token: "gateway-token",
});
containerLifecycle.touch.mockResolvedValue(undefined);
prisma.userAgent.findUnique.mockResolvedValue(mockAgent);
prisma.userAgentConfig.findUnique.mockResolvedValue(null);
fetchMock.mockResolvedValue(new Response("event: token\ndata: hello\n\n"));
const messages = [{ role: "user", content: "Hello" }];
await service.proxyChat(userId, messages, undefined, "jarvis");
const [, request] = fetchMock.mock.calls[0] as [string, RequestInit];
const parsedBody = JSON.parse(String(request.body));
expect(parsedBody.model).toBe("openclaw:default");
});
});
describe("proxyGuestChat", () => {
it("uses environment variables for guest LLM configuration", async () => {
config.get.mockImplementation((key: string) => {
if (key === "GUEST_LLM_URL") return "http://10.1.1.42:11434/v1";
if (key === "GUEST_LLM_MODEL") return "llama3.2";
return undefined;
});
fetchMock.mockResolvedValue(new Response("event: token\ndata: hello\n\n"));
const messages = [{ role: "user", content: "Hello" }];
await service.proxyGuestChat(messages);
expect(fetchMock).toHaveBeenCalledWith(
"http://10.1.1.42:11434/v1/chat/completions",
expect.objectContaining({
method: "POST",
headers: {
"Content-Type": "application/json",
},
})
);
const [, request] = fetchMock.mock.calls[0] as [string, RequestInit];
const parsedBody = JSON.parse(String(request.body));
expect(parsedBody.model).toBe("llama3.2");
});
it("throws BadGatewayException on guest LLM errors", async () => {
config.get.mockReturnValue(undefined);
fetchMock.mockResolvedValue(new Response("Internal Server Error", { status: 500 }));
const messages = [{ role: "user", content: "Hello" }];
await expect(service.proxyGuestChat(messages)).rejects.toThrow(BadGatewayException);
});
});
});

View File

@@ -1,300 +0,0 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { UserAgentService } from "./user-agent.service";
import { PrismaService } from "../prisma/prisma.service";
import { NotFoundException, ConflictException, ForbiddenException } from "@nestjs/common";
describe("UserAgentService", () => {
let service: UserAgentService;
let prisma: PrismaService;
const mockPrismaService = {
userAgent: {
findMany: vi.fn(),
findUnique: vi.fn(),
create: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
},
agentTemplate: {
findUnique: vi.fn(),
},
};
const mockUserId = "550e8400-e29b-41d4-a716-446655440001";
const mockAgentId = "550e8400-e29b-41d4-a716-446655440002";
const mockTemplateId = "550e8400-e29b-41d4-a716-446655440003";
const mockAgent = {
id: mockAgentId,
userId: mockUserId,
templateId: null,
name: "jarvis",
displayName: "Jarvis",
role: "orchestrator",
personality: "Capable, direct, proactive.",
primaryModel: "opus",
fallbackModels: ["sonnet"],
toolPermissions: ["all"],
discordChannel: "jarvis",
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
};
const mockTemplate = {
id: mockTemplateId,
name: "builder",
displayName: "Builder",
role: "coding",
personality: "Focused, thorough.",
primaryModel: "codex",
fallbackModels: ["sonnet"],
toolPermissions: ["exec", "read", "write"],
discordChannel: "builder",
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserAgentService,
{
provide: PrismaService,
useValue: mockPrismaService,
},
],
}).compile();
service = module.get<UserAgentService>(UserAgentService);
prisma = module.get<PrismaService>(PrismaService);
vi.clearAllMocks();
});
it("should be defined", () => {
expect(service).toBeDefined();
});
describe("findAll", () => {
it("should return all agents for a user", async () => {
mockPrismaService.userAgent.findMany.mockResolvedValue([mockAgent]);
const result = await service.findAll(mockUserId);
expect(result).toEqual([mockAgent]);
expect(mockPrismaService.userAgent.findMany).toHaveBeenCalledWith({
where: { userId: mockUserId },
orderBy: { createdAt: "asc" },
});
});
it("should return empty array if no agents", async () => {
mockPrismaService.userAgent.findMany.mockResolvedValue([]);
const result = await service.findAll(mockUserId);
expect(result).toEqual([]);
});
});
describe("findOne", () => {
it("should return an agent by id", async () => {
mockPrismaService.userAgent.findUnique.mockResolvedValue(mockAgent);
const result = await service.findOne(mockUserId, mockAgentId);
expect(result).toEqual(mockAgent);
});
it("should throw NotFoundException if agent not found", async () => {
mockPrismaService.userAgent.findUnique.mockResolvedValue(null);
await expect(service.findOne(mockUserId, mockAgentId)).rejects.toThrow(NotFoundException);
});
it("should throw ForbiddenException if agent belongs to different user", async () => {
mockPrismaService.userAgent.findUnique.mockResolvedValue({
...mockAgent,
userId: "different-user-id",
});
await expect(service.findOne(mockUserId, mockAgentId)).rejects.toThrow(ForbiddenException);
});
});
describe("findByName", () => {
it("should return an agent by name", async () => {
mockPrismaService.userAgent.findUnique.mockResolvedValue(mockAgent);
const result = await service.findByName(mockUserId, "jarvis");
expect(result).toEqual(mockAgent);
expect(mockPrismaService.userAgent.findUnique).toHaveBeenCalledWith({
where: { userId_name: { userId: mockUserId, name: "jarvis" } },
});
});
it("should throw NotFoundException if agent not found", async () => {
mockPrismaService.userAgent.findUnique.mockResolvedValue(null);
await expect(service.findByName(mockUserId, "nonexistent")).rejects.toThrow(
NotFoundException
);
});
});
describe("create", () => {
it("should create a new agent", async () => {
const createDto = {
name: "jarvis",
displayName: "Jarvis",
role: "orchestrator",
personality: "Capable, direct, proactive.",
};
mockPrismaService.userAgent.findUnique.mockResolvedValue(null);
mockPrismaService.userAgent.create.mockResolvedValue(mockAgent);
const result = await service.create(mockUserId, createDto);
expect(result).toEqual(mockAgent);
});
it("should throw ConflictException if agent name already exists", async () => {
const createDto = {
name: "jarvis",
displayName: "Jarvis",
role: "orchestrator",
personality: "Capable, direct, proactive.",
};
mockPrismaService.userAgent.findUnique.mockResolvedValue(mockAgent);
await expect(service.create(mockUserId, createDto)).rejects.toThrow(ConflictException);
});
it("should throw NotFoundException if templateId is invalid", async () => {
const createDto = {
name: "custom",
displayName: "Custom",
role: "custom",
personality: "Custom agent",
templateId: "nonexistent-template",
};
mockPrismaService.userAgent.findUnique.mockResolvedValue(null);
mockPrismaService.agentTemplate.findUnique.mockResolvedValue(null);
await expect(service.create(mockUserId, createDto)).rejects.toThrow(NotFoundException);
});
});
describe("createFromTemplate", () => {
it("should create an agent from a template", async () => {
mockPrismaService.agentTemplate.findUnique.mockResolvedValue(mockTemplate);
mockPrismaService.userAgent.findUnique.mockResolvedValue(null);
mockPrismaService.userAgent.create.mockResolvedValue({
...mockAgent,
templateId: mockTemplateId,
name: mockTemplate.name,
displayName: mockTemplate.displayName,
role: mockTemplate.role,
});
const result = await service.createFromTemplate(mockUserId, mockTemplateId);
expect(result.name).toBe(mockTemplate.name);
expect(result.displayName).toBe(mockTemplate.displayName);
});
it("should throw NotFoundException if template not found", async () => {
mockPrismaService.agentTemplate.findUnique.mockResolvedValue(null);
await expect(service.createFromTemplate(mockUserId, mockTemplateId)).rejects.toThrow(
NotFoundException
);
});
it("should throw ConflictException if agent name already exists", async () => {
mockPrismaService.agentTemplate.findUnique.mockResolvedValue(mockTemplate);
mockPrismaService.userAgent.findUnique.mockResolvedValue(mockAgent);
await expect(service.createFromTemplate(mockUserId, mockTemplateId)).rejects.toThrow(
ConflictException
);
});
});
describe("update", () => {
it("should update an agent", async () => {
const updateDto = { displayName: "Updated Jarvis" };
const updatedAgent = { ...mockAgent, ...updateDto };
mockPrismaService.userAgent.findUnique.mockResolvedValue(mockAgent);
mockPrismaService.userAgent.update.mockResolvedValue(updatedAgent);
const result = await service.update(mockUserId, mockAgentId, updateDto);
expect(result.displayName).toBe("Updated Jarvis");
});
it("should throw ConflictException if new name already exists", async () => {
const updateDto = { name: "existing-name" };
mockPrismaService.userAgent.findUnique.mockResolvedValue(mockAgent);
// Second call checks for existing name
mockPrismaService.userAgent.findUnique.mockResolvedValue({ ...mockAgent, id: "other-id" });
await expect(service.update(mockUserId, mockAgentId, updateDto)).rejects.toThrow(
ConflictException
);
});
});
describe("remove", () => {
it("should delete an agent", async () => {
mockPrismaService.userAgent.findUnique.mockResolvedValue(mockAgent);
mockPrismaService.userAgent.delete.mockResolvedValue(mockAgent);
const result = await service.remove(mockUserId, mockAgentId);
expect(result).toEqual(mockAgent);
});
});
describe("getStatus", () => {
it("should return agent status", async () => {
mockPrismaService.userAgent.findUnique.mockResolvedValue(mockAgent);
const result = await service.getStatus(mockUserId, mockAgentId);
expect(result).toEqual({
id: mockAgentId,
name: "jarvis",
displayName: "Jarvis",
role: "orchestrator",
isActive: true,
});
});
});
describe("getAllStatuses", () => {
it("should return all agent statuses", async () => {
mockPrismaService.userAgent.findMany.mockResolvedValue([mockAgent]);
const result = await service.getAllStatuses(mockUserId);
expect(result).toHaveLength(1);
expect(result[0]).toEqual({
id: mockAgentId,
name: "jarvis",
displayName: "Jarvis",
role: "orchestrator",
isActive: true,
});
});
});
});