forked from farhoodlabs/paperclip
feat: polish inbox and issue list workflows
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import express from "express";
|
||||
import request from "supertest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { issueRoutes } from "../routes/issues.js";
|
||||
import { errorHandler } from "../middleware/index.js";
|
||||
|
||||
const issueId = "11111111-1111-4111-8111-111111111111";
|
||||
const companyId = "22222222-2222-4222-8222-222222222222";
|
||||
@@ -50,11 +52,7 @@ vi.mock("../services/index.js", () => ({
|
||||
workProductService: () => ({}),
|
||||
}));
|
||||
|
||||
async function createApp() {
|
||||
const [{ issueRoutes }, { errorHandler }] = await Promise.all([
|
||||
import("../routes/issues.js"),
|
||||
import("../middleware/index.js"),
|
||||
]);
|
||||
function createApp() {
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use((req, _res, next) => {
|
||||
@@ -74,7 +72,6 @@ async function createApp() {
|
||||
|
||||
describe("issue document revision routes", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
vi.resetAllMocks();
|
||||
mockIssueService.getById.mockResolvedValue({
|
||||
id: issueId,
|
||||
@@ -125,10 +122,9 @@ describe("issue document revision routes", () => {
|
||||
});
|
||||
|
||||
it("returns revision snapshots including title and format", async () => {
|
||||
const res = await request(await createApp()).get(`/api/issues/${issueId}/documents/plan/revisions`);
|
||||
const res = await request(createApp()).get(`/api/issues/${issueId}/documents/plan/revisions`);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(mockDocumentsService.listIssueDocumentRevisions).toHaveBeenCalledWith(issueId, "plan");
|
||||
expect(res.body).toEqual([
|
||||
expect.objectContaining({
|
||||
revisionNumber: 2,
|
||||
@@ -140,7 +136,7 @@ describe("issue document revision routes", () => {
|
||||
});
|
||||
|
||||
it("restores a revision through the append-only route and logs the action", async () => {
|
||||
const res = await request(await createApp())
|
||||
const res = await request(createApp())
|
||||
.post(`/api/issues/${issueId}/documents/plan/revisions/revision-1/restore`)
|
||||
.send({});
|
||||
|
||||
@@ -172,7 +168,7 @@ describe("issue document revision routes", () => {
|
||||
});
|
||||
|
||||
it("rejects invalid document keys before attempting restore", async () => {
|
||||
const res = await request(await createApp())
|
||||
const res = await request(createApp())
|
||||
.post(`/api/issues/${issueId}/documents/INVALID KEY/revisions/revision-1/restore`)
|
||||
.send({});
|
||||
|
||||
|
||||
@@ -26,56 +26,53 @@ import {
|
||||
getEmbeddedPostgresTestSupport,
|
||||
startEmbeddedPostgresTestDatabase,
|
||||
} from "./helpers/embedded-postgres.js";
|
||||
import { errorHandler } from "../middleware/index.js";
|
||||
import { accessService } from "../services/access.js";
|
||||
|
||||
function registerServiceMocks() {
|
||||
vi.doMock("../services/index.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("../services/index.js")>("../services/index.js");
|
||||
vi.mock("../services/index.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("../services/index.js")>("../services/index.js");
|
||||
|
||||
return {
|
||||
...actual,
|
||||
routineService: (db: any) =>
|
||||
actual.routineService(db, {
|
||||
heartbeat: {
|
||||
wakeup: async (agentId: string, wakeupOpts: any) => {
|
||||
const issueId =
|
||||
(typeof wakeupOpts?.payload?.issueId === "string" && wakeupOpts.payload.issueId) ||
|
||||
(typeof wakeupOpts?.contextSnapshot?.issueId === "string" && wakeupOpts.contextSnapshot.issueId) ||
|
||||
null;
|
||||
if (!issueId) return null;
|
||||
return {
|
||||
...actual,
|
||||
routineService: (db: any) =>
|
||||
actual.routineService(db, {
|
||||
heartbeat: {
|
||||
wakeup: async (agentId: string, wakeupOpts: any) => {
|
||||
const issueId =
|
||||
(typeof wakeupOpts?.payload?.issueId === "string" && wakeupOpts.payload.issueId) ||
|
||||
(typeof wakeupOpts?.contextSnapshot?.issueId === "string" && wakeupOpts.contextSnapshot.issueId) ||
|
||||
null;
|
||||
if (!issueId) return null;
|
||||
|
||||
const issue = await db
|
||||
.select({ companyId: issues.companyId })
|
||||
.from(issues)
|
||||
.where(eq(issues.id, issueId))
|
||||
.then((rows: Array<{ companyId: string }>) => rows[0] ?? null);
|
||||
if (!issue) return null;
|
||||
const issue = await db
|
||||
.select({ companyId: issues.companyId })
|
||||
.from(issues)
|
||||
.where(eq(issues.id, issueId))
|
||||
.then((rows: Array<{ companyId: string }>) => rows[0] ?? null);
|
||||
if (!issue) return null;
|
||||
|
||||
const queuedRunId = randomUUID();
|
||||
await db.insert(heartbeatRuns).values({
|
||||
id: queuedRunId,
|
||||
companyId: issue.companyId,
|
||||
agentId,
|
||||
invocationSource: wakeupOpts?.source ?? "assignment",
|
||||
triggerDetail: wakeupOpts?.triggerDetail ?? null,
|
||||
status: "queued",
|
||||
contextSnapshot: { ...(wakeupOpts?.contextSnapshot ?? {}), issueId },
|
||||
});
|
||||
await db
|
||||
.update(issues)
|
||||
.set({
|
||||
executionRunId: queuedRunId,
|
||||
executionLockedAt: new Date(),
|
||||
})
|
||||
.where(eq(issues.id, issueId));
|
||||
return { id: queuedRunId };
|
||||
},
|
||||
const queuedRunId = randomUUID();
|
||||
await db.insert(heartbeatRuns).values({
|
||||
id: queuedRunId,
|
||||
companyId: issue.companyId,
|
||||
agentId,
|
||||
invocationSource: wakeupOpts?.source ?? "assignment",
|
||||
triggerDetail: wakeupOpts?.triggerDetail ?? null,
|
||||
status: "queued",
|
||||
contextSnapshot: { ...(wakeupOpts?.contextSnapshot ?? {}), issueId },
|
||||
});
|
||||
await db
|
||||
.update(issues)
|
||||
.set({
|
||||
executionRunId: queuedRunId,
|
||||
executionLockedAt: new Date(),
|
||||
})
|
||||
.where(eq(issues.id, issueId));
|
||||
return { id: queuedRunId };
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport();
|
||||
const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip;
|
||||
@@ -95,11 +92,6 @@ describeEmbeddedPostgres("routine routes end-to-end", () => {
|
||||
db = createDb(tempDb.connectionString);
|
||||
}, 20_000);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
registerServiceMocks();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await db.delete(activityLog);
|
||||
await db.delete(routineRuns);
|
||||
@@ -123,8 +115,15 @@ describeEmbeddedPostgres("routine routes end-to-end", () => {
|
||||
await tempDb?.cleanup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
async function createApp(actor: Record<string, unknown>) {
|
||||
const { routineRoutes } = await import("../routes/routines.js");
|
||||
const [{ routineRoutes }, { errorHandler }] = await Promise.all([
|
||||
import("../routes/routines.js"),
|
||||
import("../middleware/index.js"),
|
||||
]);
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use((req, _res, next) => {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import express from "express";
|
||||
import request from "supertest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { errorHandler } from "../middleware/index.js";
|
||||
import { routineRoutes } from "../routes/routines.js";
|
||||
|
||||
const companyId = "22222222-2222-4222-8222-222222222222";
|
||||
const agentId = "11111111-1111-4111-8111-111111111111";
|
||||
@@ -83,28 +85,22 @@ const mockLogActivity = vi.hoisted(() => vi.fn());
|
||||
const mockTrackRoutineCreated = vi.hoisted(() => vi.fn());
|
||||
const mockGetTelemetryClient = vi.hoisted(() => vi.fn());
|
||||
|
||||
function registerRouteMocks() {
|
||||
vi.doMock("@paperclipai/shared/telemetry", () => ({
|
||||
trackRoutineCreated: mockTrackRoutineCreated,
|
||||
trackErrorHandlerCrash: vi.fn(),
|
||||
}));
|
||||
vi.mock("@paperclipai/shared/telemetry", () => ({
|
||||
trackRoutineCreated: mockTrackRoutineCreated,
|
||||
trackErrorHandlerCrash: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.doMock("../telemetry.js", () => ({
|
||||
getTelemetryClient: mockGetTelemetryClient,
|
||||
}));
|
||||
vi.mock("../telemetry.js", () => ({
|
||||
getTelemetryClient: mockGetTelemetryClient,
|
||||
}));
|
||||
|
||||
vi.doMock("../services/index.js", () => ({
|
||||
accessService: () => mockAccessService,
|
||||
logActivity: mockLogActivity,
|
||||
routineService: () => mockRoutineService,
|
||||
}));
|
||||
}
|
||||
vi.mock("../services/index.js", () => ({
|
||||
accessService: () => mockAccessService,
|
||||
logActivity: mockLogActivity,
|
||||
routineService: () => mockRoutineService,
|
||||
}));
|
||||
|
||||
async function createApp(actor: Record<string, unknown>) {
|
||||
const [{ routineRoutes }, { errorHandler }] = await Promise.all([
|
||||
import("../routes/routines.js"),
|
||||
import("../middleware/index.js"),
|
||||
]);
|
||||
function createApp(actor: Record<string, unknown>) {
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use((req, _res, next) => {
|
||||
@@ -118,9 +114,7 @@ async function createApp(actor: Record<string, unknown>) {
|
||||
|
||||
describe("routine routes", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
registerRouteMocks();
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
mockGetTelemetryClient.mockReturnValue({ track: vi.fn() });
|
||||
mockRoutineService.create.mockResolvedValue(routine);
|
||||
mockRoutineService.get.mockResolvedValue(routine);
|
||||
@@ -136,7 +130,7 @@ describe("routine routes", () => {
|
||||
});
|
||||
|
||||
it("requires tasks:assign permission for non-admin board routine creation", async () => {
|
||||
const app = await createApp({
|
||||
const app = createApp({
|
||||
type: "board",
|
||||
userId: "board-user",
|
||||
source: "session",
|
||||
@@ -158,7 +152,7 @@ describe("routine routes", () => {
|
||||
});
|
||||
|
||||
it("requires tasks:assign permission to retarget a routine assignee", async () => {
|
||||
const app = await createApp({
|
||||
const app = createApp({
|
||||
type: "board",
|
||||
userId: "board-user",
|
||||
source: "session",
|
||||
@@ -179,7 +173,7 @@ describe("routine routes", () => {
|
||||
|
||||
it("requires tasks:assign permission to reactivate a routine", async () => {
|
||||
mockRoutineService.get.mockResolvedValue(pausedRoutine);
|
||||
const app = await createApp({
|
||||
const app = createApp({
|
||||
type: "board",
|
||||
userId: "board-user",
|
||||
source: "session",
|
||||
@@ -199,7 +193,7 @@ describe("routine routes", () => {
|
||||
});
|
||||
|
||||
it("requires tasks:assign permission to create a trigger", async () => {
|
||||
const app = await createApp({
|
||||
const app = createApp({
|
||||
type: "board",
|
||||
userId: "board-user",
|
||||
source: "session",
|
||||
@@ -221,7 +215,7 @@ describe("routine routes", () => {
|
||||
});
|
||||
|
||||
it("requires tasks:assign permission to update a trigger", async () => {
|
||||
const app = await createApp({
|
||||
const app = createApp({
|
||||
type: "board",
|
||||
userId: "board-user",
|
||||
source: "session",
|
||||
@@ -241,7 +235,7 @@ describe("routine routes", () => {
|
||||
});
|
||||
|
||||
it("requires tasks:assign permission to manually run a routine", async () => {
|
||||
const app = await createApp({
|
||||
const app = createApp({
|
||||
type: "board",
|
||||
userId: "board-user",
|
||||
source: "session",
|
||||
@@ -260,7 +254,7 @@ describe("routine routes", () => {
|
||||
|
||||
it("allows routine creation when the board user has tasks:assign", async () => {
|
||||
mockAccessService.canUser.mockResolvedValue(true);
|
||||
const app = await createApp({
|
||||
const app = createApp({
|
||||
type: "board",
|
||||
userId: "board-user",
|
||||
source: "session",
|
||||
|
||||
Reference in New Issue
Block a user