Route existing-company CLI imports through safe routes

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
dotta 2026-03-23 07:23:34 -05:00
parent e6df9fa078
commit f23d611d0c
3 changed files with 77 additions and 7 deletions

View file

@ -449,7 +449,7 @@ describe("paperclipai company import/export e2e", () => {
);
expect(previewExisting.errors).toEqual([]);
expect(previewExisting.plan.companyAction).toBe("update");
expect(previewExisting.plan.companyAction).toBe("none");
expect(previewExisting.plan.agentPlans.some((plan) => plan.action === "create")).toBe(true);
expect(previewExisting.plan.projectPlans.some((plan) => plan.action === "create")).toBe(true);
expect(previewExisting.plan.issuePlans.some((plan) => plan.action === "create")).toBe(true);
@ -474,7 +474,7 @@ describe("paperclipai company import/export e2e", () => {
{ apiBase, configPath },
);
expect(importedExisting.company.action).toBe("updated");
expect(importedExisting.company.action).toBe("unchanged");
expect(importedExisting.agents.some((agent) => agent.action === "created")).toBe(true);
const twiceImportedAgents = await api<Array<{ id: string; name: string }>>(

View file

@ -0,0 +1,50 @@
import { describe, expect, it } from "vitest";
import { resolveCompanyImportApiPath } from "../commands/client/company.js";
describe("resolveCompanyImportApiPath", () => {
it("uses company-scoped preview route for existing-company dry runs", () => {
expect(
resolveCompanyImportApiPath({
dryRun: true,
targetMode: "existing_company",
companyId: "company-123",
}),
).toBe("/api/companies/company-123/imports/preview");
});
it("uses company-scoped apply route for existing-company imports", () => {
expect(
resolveCompanyImportApiPath({
dryRun: false,
targetMode: "existing_company",
companyId: "company-123",
}),
).toBe("/api/companies/company-123/imports/apply");
});
it("keeps global routes for new-company imports", () => {
expect(
resolveCompanyImportApiPath({
dryRun: true,
targetMode: "new_company",
}),
).toBe("/api/companies/import/preview");
expect(
resolveCompanyImportApiPath({
dryRun: false,
targetMode: "new_company",
}),
).toBe("/api/companies/import");
});
it("throws when an existing-company import is missing a company id", () => {
expect(() =>
resolveCompanyImportApiPath({
dryRun: true,
targetMode: "existing_company",
companyId: " ",
})
).toThrow(/require a companyId/i);
});
});

View file

@ -114,6 +114,24 @@ function parseCsvValues(input: string | undefined): string[] {
return Array.from(new Set(input.split(",").map((part) => part.trim()).filter(Boolean)));
}
export function resolveCompanyImportApiPath(input: {
dryRun: boolean;
targetMode: "new_company" | "existing_company";
companyId?: string | null;
}): string {
if (input.targetMode === "existing_company") {
const companyId = input.companyId?.trim();
if (!companyId) {
throw new Error("Existing-company imports require a companyId to resolve the API route.");
}
return input.dryRun
? `/api/companies/${companyId}/imports/preview`
: `/api/companies/${companyId}/imports/apply`;
}
return input.dryRun ? "/api/companies/import/preview" : "/api/companies/import";
}
export function isHttpUrl(input: string): boolean {
return /^https?:\/\//i.test(input.trim());
}
@ -576,17 +594,19 @@ export function registerCompanyCommands(program: Command): void {
agents,
collisionStrategy: collision,
};
const importApiPath = resolveCompanyImportApiPath({
dryRun: Boolean(opts.dryRun),
targetMode: targetPayload.mode,
companyId: targetPayload.mode === "existing_company" ? targetPayload.companyId : null,
});
if (opts.dryRun) {
const preview = await ctx.api.post<CompanyPortabilityPreviewResult>(
"/api/companies/import/preview",
payload,
);
const preview = await ctx.api.post<CompanyPortabilityPreviewResult>(importApiPath, payload);
printOutput(preview, { json: ctx.json });
return;
}
const imported = await ctx.api.post<CompanyPortabilityImportResult>("/api/companies/import", payload);
const imported = await ctx.api.post<CompanyPortabilityImportResult>(importApiPath, payload);
printOutput(imported, { json: ctx.json });
} catch (err) {
handleCommandError(err);