115 lines
4.1 KiB
TypeScript
115 lines
4.1 KiB
TypeScript
import { mkdtemp, rm, writeFile } from "fs/promises";
|
|
import os from "os";
|
|
import path from "path";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const mockStateService = vi.hoisted(() => ({
|
|
records: { artifacts: [] as any[] },
|
|
recordDigitalEmployeeArtifactAccess: vi.fn((input: { status: string }) => ({ eventId: `event:${input.status}` })),
|
|
}));
|
|
|
|
const mockShell = vi.hoisted(() => ({
|
|
openPath: vi.fn(async () => ""),
|
|
showItemInFolder: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("electron", () => ({
|
|
shell: mockShell,
|
|
}));
|
|
|
|
vi.mock("./stateService", () => ({
|
|
readDigitalEmployeeRuntimeRecords: () => mockStateService.records,
|
|
recordDigitalEmployeeArtifactAccess: mockStateService.recordDigitalEmployeeArtifactAccess,
|
|
}));
|
|
|
|
describe("digital employee artifact access service", () => {
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
vi.clearAllMocks();
|
|
mockStateService.records = { artifacts: [] };
|
|
});
|
|
|
|
it("returns bounded text preview for a local artifact and audits allowed access", async () => {
|
|
const tempDir = await mkdtemp(path.join(os.tmpdir(), "qiming-artifact-access-"));
|
|
try {
|
|
const filePath = path.join(tempDir, "report.txt");
|
|
await writeFile(filePath, "hello artifact", "utf8");
|
|
mockStateService.records = {
|
|
artifacts: [{
|
|
id: "artifact-1",
|
|
planId: "plan-1",
|
|
taskId: "task-1",
|
|
runId: "run-1",
|
|
kind: "report",
|
|
name: "report.txt",
|
|
uri: filePath,
|
|
payload: { delivery: { status: "completed", workspace_id: "workspace-1" } },
|
|
syncStatus: "pending",
|
|
createdAt: "2026-06-07T08:00:00.000Z",
|
|
}],
|
|
};
|
|
const { accessDigitalEmployeeArtifact } = await import("./artifactAccessService");
|
|
|
|
const result = await accessDigitalEmployeeArtifact({ artifactId: "artifact-1", action: "preview", actor: "operator" });
|
|
|
|
expect(result).toMatchObject({
|
|
ok: true,
|
|
artifact_id: "artifact-1",
|
|
action: "preview",
|
|
status: "allowed",
|
|
preview: { name: "report.txt", text: "hello artifact", mime: "text/plain" },
|
|
});
|
|
expect(mockStateService.recordDigitalEmployeeArtifactAccess).toHaveBeenCalledWith(expect.objectContaining({
|
|
artifactId: "artifact-1",
|
|
status: "allowed",
|
|
uriSummary: expect.objectContaining({ basename: "report.txt", extension: ".txt" }),
|
|
deliverySnapshot: expect.objectContaining({ delivery_status: "completed", workspace_id: "workspace-1" }),
|
|
}));
|
|
} finally {
|
|
await rm(tempDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("rejects non-local artifact URIs without invoking shell", async () => {
|
|
mockStateService.records = {
|
|
artifacts: [{
|
|
id: "artifact-remote",
|
|
kind: "report",
|
|
name: "remote.txt",
|
|
uri: "https://example.com/remote.txt",
|
|
payload: {},
|
|
syncStatus: "pending",
|
|
createdAt: "2026-06-07T08:00:00.000Z",
|
|
}],
|
|
};
|
|
const { accessDigitalEmployeeArtifact } = await import("./artifactAccessService");
|
|
|
|
const result = await accessDigitalEmployeeArtifact({ artifactId: "artifact-remote", action: "download" });
|
|
|
|
expect(result).toMatchObject({ ok: false, status: "rejected", error: "local_uri_required" });
|
|
expect(mockShell.openPath).not.toHaveBeenCalled();
|
|
expect(mockShell.showItemInFolder).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("rejects unknown access actions before opening anything", async () => {
|
|
mockStateService.records = {
|
|
artifacts: [{
|
|
id: "artifact-1",
|
|
kind: "report",
|
|
name: "report.txt",
|
|
uri: "/tmp/report.txt",
|
|
payload: {},
|
|
syncStatus: "pending",
|
|
createdAt: "2026-06-07T08:00:00.000Z",
|
|
}],
|
|
};
|
|
const { accessDigitalEmployeeArtifact } = await import("./artifactAccessService");
|
|
|
|
const result = await accessDigitalEmployeeArtifact({ artifactId: "artifact-1", action: "delete" });
|
|
|
|
expect(result).toMatchObject({ ok: false, status: "rejected", error: "dangerous_action" });
|
|
expect(mockShell.openPath).not.toHaveBeenCalled();
|
|
expect(mockShell.showItemInFolder).not.toHaveBeenCalled();
|
|
});
|
|
});
|