249 lines
6.7 KiB
TypeScript
249 lines
6.7 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
|
|
import { eq } from "drizzle-orm";
|
|
import {
|
|
agents,
|
|
companies,
|
|
createDb,
|
|
projects,
|
|
routines,
|
|
} from "@paperclipai/db";
|
|
import {
|
|
getEmbeddedPostgresTestSupport,
|
|
startEmbeddedPostgresTestDatabase,
|
|
} from "./helpers/embedded-postgres.js";
|
|
import { disableAllRoutinesInConfig } from "../commands/routines.js";
|
|
|
|
const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport();
|
|
const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip;
|
|
|
|
if (!embeddedPostgresSupport.supported) {
|
|
console.warn(
|
|
`Skipping embedded Postgres routines CLI tests on this host: ${embeddedPostgresSupport.reason ?? "unsupported environment"}`,
|
|
);
|
|
}
|
|
|
|
function writeTestConfig(configPath: string, tempRoot: string, connectionString: string) {
|
|
const config = {
|
|
$meta: {
|
|
version: 1,
|
|
updatedAt: new Date().toISOString(),
|
|
source: "doctor" as const,
|
|
},
|
|
database: {
|
|
mode: "postgres" as const,
|
|
connectionString,
|
|
embeddedPostgresDataDir: path.join(tempRoot, "embedded-db"),
|
|
embeddedPostgresPort: 54329,
|
|
backup: {
|
|
enabled: false,
|
|
intervalMinutes: 60,
|
|
retentionDays: 30,
|
|
dir: path.join(tempRoot, "backups"),
|
|
},
|
|
},
|
|
logging: {
|
|
mode: "file" as const,
|
|
logDir: path.join(tempRoot, "logs"),
|
|
},
|
|
server: {
|
|
deploymentMode: "local_trusted" as const,
|
|
exposure: "private" as const,
|
|
host: "127.0.0.1",
|
|
port: 3100,
|
|
allowedHostnames: [],
|
|
serveUi: false,
|
|
},
|
|
auth: {
|
|
baseUrlMode: "auto" as const,
|
|
disableSignUp: false,
|
|
},
|
|
storage: {
|
|
provider: "local_disk" as const,
|
|
localDisk: {
|
|
baseDir: path.join(tempRoot, "storage"),
|
|
},
|
|
s3: {
|
|
bucket: "paperclip",
|
|
region: "us-east-1",
|
|
prefix: "",
|
|
forcePathStyle: false,
|
|
},
|
|
},
|
|
secrets: {
|
|
provider: "local_encrypted" as const,
|
|
strictMode: false,
|
|
localEncrypted: {
|
|
keyFilePath: path.join(tempRoot, "secrets", "master.key"),
|
|
},
|
|
},
|
|
};
|
|
|
|
mkdirSync(path.dirname(configPath), { recursive: true });
|
|
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
}
|
|
|
|
describeEmbeddedPostgres("disableAllRoutinesInConfig", () => {
|
|
let db!: ReturnType<typeof createDb>;
|
|
let tempDb: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>> | null = null;
|
|
let tempRoot = "";
|
|
let configPath = "";
|
|
|
|
beforeAll(async () => {
|
|
tempDb = await startEmbeddedPostgresTestDatabase("paperclip-routines-cli-db-");
|
|
db = createDb(tempDb.connectionString);
|
|
tempRoot = mkdtempSync(path.join(os.tmpdir(), "paperclip-routines-cli-config-"));
|
|
configPath = path.join(tempRoot, "config.json");
|
|
writeTestConfig(configPath, tempRoot, tempDb.connectionString);
|
|
}, 20_000);
|
|
|
|
afterEach(async () => {
|
|
await db.delete(routines);
|
|
await db.delete(projects);
|
|
await db.delete(agents);
|
|
await db.delete(companies);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await tempDb?.cleanup();
|
|
if (tempRoot) {
|
|
rmSync(tempRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("pauses only non-archived routines for the selected company", async () => {
|
|
const companyId = randomUUID();
|
|
const otherCompanyId = randomUUID();
|
|
const projectId = randomUUID();
|
|
const otherProjectId = randomUUID();
|
|
const agentId = randomUUID();
|
|
const otherAgentId = randomUUID();
|
|
const activeRoutineId = randomUUID();
|
|
const pausedRoutineId = randomUUID();
|
|
const archivedRoutineId = randomUUID();
|
|
const otherCompanyRoutineId = randomUUID();
|
|
|
|
await db.insert(companies).values([
|
|
{
|
|
id: companyId,
|
|
name: "Paperclip",
|
|
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
|
|
requireBoardApprovalForNewAgents: false,
|
|
},
|
|
{
|
|
id: otherCompanyId,
|
|
name: "Other company",
|
|
issuePrefix: `T${otherCompanyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
|
|
requireBoardApprovalForNewAgents: false,
|
|
},
|
|
]);
|
|
|
|
await db.insert(agents).values([
|
|
{
|
|
id: agentId,
|
|
companyId,
|
|
name: "Coder",
|
|
adapterType: "process",
|
|
adapterConfig: {},
|
|
runtimeConfig: {},
|
|
permissions: {},
|
|
},
|
|
{
|
|
id: otherAgentId,
|
|
companyId: otherCompanyId,
|
|
name: "Other coder",
|
|
adapterType: "process",
|
|
adapterConfig: {},
|
|
runtimeConfig: {},
|
|
permissions: {},
|
|
},
|
|
]);
|
|
|
|
await db.insert(projects).values([
|
|
{
|
|
id: projectId,
|
|
companyId,
|
|
name: "Project",
|
|
status: "in_progress",
|
|
},
|
|
{
|
|
id: otherProjectId,
|
|
companyId: otherCompanyId,
|
|
name: "Other project",
|
|
status: "in_progress",
|
|
},
|
|
]);
|
|
|
|
await db.insert(routines).values([
|
|
{
|
|
id: activeRoutineId,
|
|
companyId,
|
|
projectId,
|
|
assigneeAgentId: agentId,
|
|
title: "Active routine",
|
|
status: "active",
|
|
},
|
|
{
|
|
id: pausedRoutineId,
|
|
companyId,
|
|
projectId,
|
|
assigneeAgentId: agentId,
|
|
title: "Paused routine",
|
|
status: "paused",
|
|
},
|
|
{
|
|
id: archivedRoutineId,
|
|
companyId,
|
|
projectId,
|
|
assigneeAgentId: agentId,
|
|
title: "Archived routine",
|
|
status: "archived",
|
|
},
|
|
{
|
|
id: otherCompanyRoutineId,
|
|
companyId: otherCompanyId,
|
|
projectId: otherProjectId,
|
|
assigneeAgentId: otherAgentId,
|
|
title: "Other company routine",
|
|
status: "active",
|
|
},
|
|
]);
|
|
|
|
const result = await disableAllRoutinesInConfig({
|
|
config: configPath,
|
|
companyId,
|
|
});
|
|
|
|
expect(result).toMatchObject({
|
|
companyId,
|
|
totalRoutines: 3,
|
|
pausedCount: 1,
|
|
alreadyPausedCount: 1,
|
|
archivedCount: 1,
|
|
});
|
|
|
|
const companyRoutines = await db
|
|
.select({
|
|
id: routines.id,
|
|
status: routines.status,
|
|
})
|
|
.from(routines)
|
|
.where(eq(routines.companyId, companyId));
|
|
const statusById = new Map(companyRoutines.map((routine) => [routine.id, routine.status]));
|
|
|
|
expect(statusById.get(activeRoutineId)).toBe("paused");
|
|
expect(statusById.get(pausedRoutineId)).toBe("paused");
|
|
expect(statusById.get(archivedRoutineId)).toBe("archived");
|
|
|
|
const otherCompanyRoutine = await db
|
|
.select({
|
|
status: routines.status,
|
|
})
|
|
.from(routines)
|
|
.where(eq(routines.id, otherCompanyRoutineId));
|
|
expect(otherCompanyRoutine[0]?.status).toBe("active");
|
|
});
|
|
});
|