mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 00:05:49 +02:00
test(mcp): cover event lifetime bounds and late activity commits
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
56f6c416af
commit
fed6d7bab8
2 files changed
+36
-1
No files matched your search
@@ -363,7 +363,7 @@ and retrieve results later. [The runbook](../public-mcp.md) includes commands.
|
||||
The shared production Paperclip skill also gains generic document-creation and
|
||||
read-back guidance.
|
||||
|
||||
Hosted use additionally requires Cloud migration 0054, a secret-store encryption
|
||||
Hosted use additionally requires Cloud migration 0055, a secret-store encryption
|
||||
key, the Cloud feature flag, and a compatible tenant release/fleet rollout.
|
||||
Signup eligibility, execution credentials and spending setup remain explicit.
|
||||
Merging does not create a company, launch paid agents, expose a live public
|
||||
|
||||
@@ -446,6 +446,41 @@ describe.skipIf(!support.supported)("public MCP OAuth and tool boundary", () =>
|
||||
}
|
||||
});
|
||||
|
||||
it("bounds lifetimes and retries without claiming unsupported replay", async () => {
|
||||
const f = await eventFixture();
|
||||
const short = await f.service.subscribe(f.principal, { ...f.input, ttlMs: 1 });
|
||||
expect(Date.parse(short.refreshBefore)).toBe(f.now() + 30_000);
|
||||
const unlimited = await f.service.subscribe(f.principal, { ...f.input, ttlMs: null });
|
||||
expect(Date.parse(unlimited.refreshBefore)).toBe(f.now() + 24 * 3600_000);
|
||||
expect(unlimited.cursor).toBeNull();
|
||||
await expect(f.service.subscribe(f.principal, { ...f.input, cursor: "pretend-replay" })).rejects.toThrow();
|
||||
await f.activity(); f.setStatus(503);
|
||||
for (let attempt = 0; attempt < 8; attempt++) { await f.service.tick(); f.advance(3600_000); }
|
||||
expect(f.received.filter(r => r.body.eventId)).toHaveLength(6);
|
||||
expect(new Set(f.received.filter(r => r.body.eventId).map(r => r.body.eventId)).size).toBe(1);
|
||||
await f.service.unsubscribe(f.principal, f.input);
|
||||
});
|
||||
|
||||
it("does not skip an older activity that commits after a newer event was delivered", async () => {
|
||||
const f = await eventFixture(); await f.service.subscribe(f.principal, f.input);
|
||||
let release!: () => void;
|
||||
let inserted!: () => void;
|
||||
const gate = new Promise<void>(resolve => { release = resolve; });
|
||||
const ready = new Promise<void>(resolve => { inserted = resolve; });
|
||||
const lateId = randomUUID();
|
||||
const transaction = db.transaction(async tx => {
|
||||
await tx.insert(activityLog).values({ id: lateId, companyId: f.company.id, actorType: "user", actorId: f.actor.userId!, action: "issue.updated", entityType: "issue", entityId: f.task.id, details: { status: "done" }, createdAt: new Date(f.now() + 1) });
|
||||
inserted(); await gate;
|
||||
});
|
||||
try {
|
||||
await ready; const newer = await f.activity(); await f.service.tick();
|
||||
expect(f.received.filter(r => r.body.eventId).map(r => r.body.eventId)).toEqual(["evt_" + newer.id]);
|
||||
} finally { release(); await transaction; }
|
||||
await f.service.tick();
|
||||
expect(f.received.filter(r => r.body.eventId).at(-1)!.body.eventId).toBe("evt_" + lateId);
|
||||
await f.service.unsubscribe(f.principal, f.input);
|
||||
});
|
||||
|
||||
it("sends only comment/document references and checks hosted membership on each delivery", async () => {
|
||||
const f = await eventFixture("https://cloud.example");
|
||||
const authority = { token: "fixture-cloud-proof", expiresAt: f.now() + 60_000 };
|
||||
|
||||
Reference in new issue
Block a user