Labels: tests, good first issue
Summary
The initial test suite covers webhook signatures, push tools, the session registry, the extension hooks contract, and manifest validity. Two paths still need direct tests:
- Idle thread close — the
idle-close job runs every 15 minutes and closes any open LINE thread whose lastActivityAt is older than idleCloseMinutes. Tested transitively in extensions.test.ts (close path) but not directly.
- Reply-token cache — when an inbound message arrives, the plugin caches the reply token against the resulting Paperclip comment. Agents call
line.ack_with_reply_token with a commentId and the plugin uses the cached token. Cache entries expire after replyTokenMaxAgeSeconds and are GC'd by the reply-token-gc job.
What to implement
tests/idle-close.test.ts
test("idle close: closes threads older than idleCloseMinutes", async () => {
const { ctx, harness } = createHarness({
config: { idleCloseMinutes: 30 },
});
// Seed a thread with lastActivityAt = 35 minutes ago
await harness.seed.threadSession({
issueId: "issue-1",
lastActivityAt: minutesAgo(35),
status: "open",
});
await harness.runJob("idle-close");
const thread = await harness.getState.threadSession("issue-1");
expect(thread.status).toBe("closed");
expect(thread.closeReason).toBe("idle");
expect(harness.metrics).toContainEqual({ name: "line.idle_close.closed", value: 1 });
});
test("idle close: leaves fresh threads alone", async () => {
// Seed thread with lastActivityAt = 10 minutes ago, idleCloseMinutes = 30
// Run idle-close. Expect thread still open.
});
test("idle close: invokes onCloseThread extension hook", async () => {
const onCloseThread = vi.fn();
setExtensions({ onCloseThread });
// Seed an idle thread, run idle-close, assert onCloseThread called with reason="idle"
});
tests/reply-token.test.ts
test("reply token: cached on inbound message, used by line.ack_with_reply_token", async () => {
// POST a signed webhook with an inbound text message and a reply token.
// Assert: reply-token cache populated, indexed by the resulting commentId.
// Call line.ack_with_reply_token with that commentId.
// Assert: LINE reply API called with the cached token.
});
test("reply token: expired tokens return token_expired", async () => {
// Seed a cached reply token with capturedAt = 90 seconds ago, replyTokenMaxAgeSeconds = 60.
// Call line.ack_with_reply_token. Assert error result with reason "reply_token_expired".
});
test("reply-token-gc: removes expired cache entries", async () => {
// Seed two reply tokens: one fresh, one expired.
// Run reply-token-gc job.
// Assert: only the fresh one remains.
});
The existing tests use a createHarness helper from @paperclipai/plugin-sdk/testing. Use the same.
Acceptance criteria
Labels:
tests,good first issueSummary
The initial test suite covers webhook signatures, push tools, the session registry, the extension hooks contract, and manifest validity. Two paths still need direct tests:
idle-closejob runs every 15 minutes and closes any open LINE thread whoselastActivityAtis older thanidleCloseMinutes. Tested transitively inextensions.test.ts(close path) but not directly.line.ack_with_reply_tokenwith acommentIdand the plugin uses the cached token. Cache entries expire afterreplyTokenMaxAgeSecondsand are GC'd by thereply-token-gcjob.What to implement
tests/idle-close.test.tstests/reply-token.test.tsThe existing tests use a
createHarnesshelper from@paperclipai/plugin-sdk/testing. Use the same.Acceptance criteria