Refine company package export format
This commit is contained in:
204
server/src/__tests__/company-portability.test.ts
Normal file
204
server/src/__tests__/company-portability.test.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const companySvc = {
|
||||
getById: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
};
|
||||
|
||||
const agentSvc = {
|
||||
list: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
};
|
||||
|
||||
const accessSvc = {
|
||||
ensureMembership: vi.fn(),
|
||||
};
|
||||
|
||||
const projectSvc = {
|
||||
list: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
};
|
||||
|
||||
const issueSvc = {
|
||||
list: vi.fn(),
|
||||
getById: vi.fn(),
|
||||
getByIdentifier: vi.fn(),
|
||||
create: vi.fn(),
|
||||
};
|
||||
|
||||
vi.mock("../services/companies.js", () => ({
|
||||
companyService: () => companySvc,
|
||||
}));
|
||||
|
||||
vi.mock("../services/agents.js", () => ({
|
||||
agentService: () => agentSvc,
|
||||
}));
|
||||
|
||||
vi.mock("../services/access.js", () => ({
|
||||
accessService: () => accessSvc,
|
||||
}));
|
||||
|
||||
vi.mock("../services/projects.js", () => ({
|
||||
projectService: () => projectSvc,
|
||||
}));
|
||||
|
||||
vi.mock("../services/issues.js", () => ({
|
||||
issueService: () => issueSvc,
|
||||
}));
|
||||
|
||||
const { companyPortabilityService } = await import("../services/company-portability.js");
|
||||
|
||||
describe("company portability", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
companySvc.getById.mockResolvedValue({
|
||||
id: "company-1",
|
||||
name: "Paperclip",
|
||||
description: null,
|
||||
brandColor: "#5c5fff",
|
||||
requireBoardApprovalForNewAgents: true,
|
||||
});
|
||||
agentSvc.list.mockResolvedValue([
|
||||
{
|
||||
id: "agent-1",
|
||||
name: "ClaudeCoder",
|
||||
status: "idle",
|
||||
role: "engineer",
|
||||
title: "Software Engineer",
|
||||
icon: "code",
|
||||
reportsTo: null,
|
||||
capabilities: "Writes code",
|
||||
adapterType: "claude_local",
|
||||
adapterConfig: {
|
||||
promptTemplate: "You are ClaudeCoder.",
|
||||
instructionsFilePath: "/tmp/ignored.md",
|
||||
cwd: "/tmp/ignored",
|
||||
command: "/Users/dotta/.local/bin/claude",
|
||||
model: "claude-opus-4-6",
|
||||
env: {
|
||||
ANTHROPIC_API_KEY: {
|
||||
type: "secret_ref",
|
||||
secretId: "secret-1",
|
||||
version: "latest",
|
||||
},
|
||||
GH_TOKEN: {
|
||||
type: "secret_ref",
|
||||
secretId: "secret-2",
|
||||
version: "latest",
|
||||
},
|
||||
PATH: {
|
||||
type: "plain",
|
||||
value: "/usr/bin:/bin",
|
||||
},
|
||||
},
|
||||
},
|
||||
runtimeConfig: {
|
||||
heartbeat: {
|
||||
intervalSec: 3600,
|
||||
},
|
||||
},
|
||||
budgetMonthlyCents: 0,
|
||||
permissions: {
|
||||
canCreateAgents: false,
|
||||
},
|
||||
metadata: null,
|
||||
},
|
||||
]);
|
||||
projectSvc.list.mockResolvedValue([]);
|
||||
issueSvc.list.mockResolvedValue([]);
|
||||
issueSvc.getById.mockResolvedValue(null);
|
||||
issueSvc.getByIdentifier.mockResolvedValue(null);
|
||||
});
|
||||
|
||||
it("exports a clean base package with sanitized Paperclip extension data", async () => {
|
||||
const portability = companyPortabilityService({} as any);
|
||||
|
||||
const exported = await portability.exportBundle("company-1", {
|
||||
include: {
|
||||
company: true,
|
||||
agents: true,
|
||||
projects: false,
|
||||
issues: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(exported.files["COMPANY.md"]).toContain('name: "Paperclip"');
|
||||
expect(exported.files["COMPANY.md"]).toContain('schema: "agentcompanies/v1"');
|
||||
expect(exported.files["agents/claudecoder/AGENTS.md"]).toContain("You are ClaudeCoder.");
|
||||
|
||||
const extension = exported.files[".paperclip.yaml"];
|
||||
expect(extension).toContain('schema: "paperclip/v1"');
|
||||
expect(extension).not.toContain("promptTemplate");
|
||||
expect(extension).not.toContain("instructionsFilePath");
|
||||
expect(extension).not.toContain("command:");
|
||||
expect(extension).not.toContain("secretId");
|
||||
expect(extension).not.toContain('type: "secret_ref"');
|
||||
expect(extension).toContain("inputs:");
|
||||
expect(extension).toContain("ANTHROPIC_API_KEY:");
|
||||
expect(extension).toContain('requirement: "optional"');
|
||||
expect(extension).toContain('default: ""');
|
||||
expect(extension).not.toContain("PATH:");
|
||||
expect(extension).not.toContain("requireBoardApprovalForNewAgents: true");
|
||||
expect(extension).not.toContain("budgetMonthlyCents: 0");
|
||||
expect(exported.warnings).toContain("Agent claudecoder command /Users/dotta/.local/bin/claude was omitted from export because it is system-dependent.");
|
||||
expect(exported.warnings).toContain("Agent claudecoder PATH override was omitted from export because it is system-dependent.");
|
||||
});
|
||||
|
||||
it("reads env inputs back from .paperclip.yaml during preview import", async () => {
|
||||
const portability = companyPortabilityService({} as any);
|
||||
|
||||
const exported = await portability.exportBundle("company-1", {
|
||||
include: {
|
||||
company: true,
|
||||
agents: true,
|
||||
projects: false,
|
||||
issues: false,
|
||||
},
|
||||
});
|
||||
|
||||
const preview = await portability.previewImport({
|
||||
source: {
|
||||
type: "inline",
|
||||
rootPath: exported.rootPath,
|
||||
files: exported.files,
|
||||
},
|
||||
include: {
|
||||
company: true,
|
||||
agents: true,
|
||||
projects: false,
|
||||
issues: false,
|
||||
},
|
||||
target: {
|
||||
mode: "new_company",
|
||||
newCompanyName: "Imported Paperclip",
|
||||
},
|
||||
agents: "all",
|
||||
collisionStrategy: "rename",
|
||||
});
|
||||
|
||||
expect(preview.errors).toEqual([]);
|
||||
expect(preview.envInputs).toEqual([
|
||||
{
|
||||
key: "ANTHROPIC_API_KEY",
|
||||
description: "Provide ANTHROPIC_API_KEY for agent claudecoder",
|
||||
agentSlug: "claudecoder",
|
||||
kind: "secret",
|
||||
requirement: "optional",
|
||||
defaultValue: "",
|
||||
portability: "portable",
|
||||
},
|
||||
{
|
||||
key: "GH_TOKEN",
|
||||
description: "Provide GH_TOKEN for agent claudecoder",
|
||||
agentSlug: "claudecoder",
|
||||
kind: "secret",
|
||||
requirement: "optional",
|
||||
defaultValue: "",
|
||||
portability: "portable",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user