Files
PaperClipAI/server/src/services/issue-queued-comment-queue.test.ts
T
Nicky LeachandPaperclip 0d8bbf7cf4 refactor(server): move the queued-comment queue mutations into the wake-queue module (#13145)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The server coordinates issue execution and agent wake events
> - Queued comment mutations belong to the wake queue that owns their
state
> - Route-local database writes split queue rules across two layers
> - This pull request moves those mutations into the wake-queue module
and keeps route authorization and response mapping
> - The benefit is one transaction boundary with company-scoped writes
and a shared checked response contract

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The queued-comment edit, reorder, and discard endpoints write queue
state directly from the route layer.

**Subsystem affected**

server/ — REST API and orchestration services.

**Current behavior**

The route layer owns database transactions, locks, queue writes, and
wake-row writes for queued comments.

**Proposed behavior**

The wake-queue module owns these operations. The routes keep
authorization, input checks, error mapping, and response mapping.

**Reason and benefit**

The module gives all queued-comment callers one transaction boundary and
applies company predicates to every adapter read and write.

**Breaking changes**

None. The endpoints keep their existing paths and response behavior.

## What Changed

- Move queued-comment edit, reorder, and discard operations into the
wake-queue module.
- Add company predicates to seven queue writes.
- Use the shared queue contract type for mutation responses.
- Add module tests and route tests for the moved operations.

## Verification

- `server/src/modules/wake-queue`: 128 tests pass across 6 files.
- `server/src/__tests__/issue-queued-comments-routes.test.ts`: 19 tests
pass.
- The server TypeScript check reports the same 141 pre-existing errors
before and after this change.
- GitHub Actions must pass the required pull-request checks.

## Risks

The change moves transaction and lock ownership across module
boundaries. The new adapter, use-case, and route tests cover the moved
behavior. No database schema changes occur.

## Model Used

OpenAI Codex, GPT-5, current agent runtime, tool use and code review
support.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` /
`Closes: #` / `Refs: #` OR (b) described the issue in-PR following the
relevant issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-09-10 08:41:59 -07:00

118 lines
4.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { buildQueuedCommentQueueSnapshot, decideQueuedCommentQueueSteering } from "./issue-queued-comment-queue.js";
describe("decideQueuedCommentQueueSteering", () => {
it("answers unsupported on the legacy protocol", () => {
const decision = decideQueuedCommentQueueSteering({
state: "deferred",
queueRunRuntimeMode: null,
activeRun: { id: "run-1", runtimeMode: "legacy" },
assignedAgentAdapterType: "codex_local",
queuedCommentCount: 1,
});
expect(decision).toEqual({ protocol: "legacy", kind: "unsupported" });
});
it("answers temporarily_unavailable for a promoted native queue with no deferred run", () => {
const decision = decideQueuedCommentQueueSteering({
state: "queued",
queueRunRuntimeMode: "native",
activeRun: null,
assignedAgentAdapterType: "paperclip_runner",
queuedCommentCount: 1,
});
expect(decision).toEqual({ protocol: "paperclip_runner_v1", kind: "temporarily_unavailable" });
});
it("answers temporarily_unavailable when the queue holds no live comments", () => {
const decision = decideQueuedCommentQueueSteering({
state: "deferred",
queueRunRuntimeMode: null,
activeRun: { id: "run-1", runtimeMode: "native" },
assignedAgentAdapterType: "paperclip_runner",
queuedCommentCount: 0,
});
expect(decision).toEqual({ protocol: "paperclip_runner_v1", kind: "temporarily_unavailable" });
});
it("tells the caller it may probe a running deferred turn on the native protocol", () => {
const decision = decideQueuedCommentQueueSteering({
state: "deferred",
queueRunRuntimeMode: null,
activeRun: { id: "run-1", runtimeMode: "native" },
assignedAgentAdapterType: "paperclip_runner",
queuedCommentCount: 1,
});
expect(decision).toEqual({ protocol: "paperclip_runner_v1", kind: "probe", steeringRunId: "run-1" });
});
// Acceptance-criterion fact pattern: a deferred queue whose active run
// has not resolved a runtime mode yet, for an agent on the
// `paperclip_runner` adapter. The protocol resolves to
// `paperclip_runner_v1` through the adapter-type fallback, and the
// decision hands the run to the caller to probe live — it never answers
// the flat "unsupported" value a duplicated, unshared rule can drift to.
it("resolves the protocol through the adapter-type fallback and asks the caller to probe", () => {
const decision = decideQueuedCommentQueueSteering({
state: "deferred",
queueRunRuntimeMode: null,
activeRun: { id: "run-1", runtimeMode: null },
assignedAgentAdapterType: "paperclip_runner",
queuedCommentCount: 1,
});
expect(decision).toEqual({ protocol: "paperclip_runner_v1", kind: "probe", steeringRunId: "run-1" });
});
});
describe("buildQueuedCommentQueueSnapshot entry permissions", () => {
const baseFacts = {
issueId: "issue-1",
queueId: "queue-1",
state: "queued" as const,
activeRunId: null,
protocol: "legacy" as const,
steeringDisposition: "unsupported" as const,
};
it("grants edit and discard to the user who authored the queued comment", () => {
const queue = buildQueuedCommentQueueSnapshot({
...baseFacts,
actorType: "user",
actorId: "user-1",
comments: [{ id: "comment-1", updatedAt: new Date(), authorUserId: "user-1" }],
});
expect(queue.entries[0]?.canEdit).toBe(true);
expect(queue.entries[0]?.canDiscard).toBe(true);
});
it("denies edit and discard to a user who did not author the queued comment", () => {
const queue = buildQueuedCommentQueueSnapshot({
...baseFacts,
actorType: "user",
actorId: "user-1",
comments: [{ id: "comment-1", updatedAt: new Date(), authorUserId: "user-2" }],
});
expect(queue.entries[0]?.canEdit).toBe(false);
expect(queue.entries[0]?.canDiscard).toBe(false);
});
it("denies edit and discard to an agent actor even when the comment carries a matching author id", () => {
const queue = buildQueuedCommentQueueSnapshot({
...baseFacts,
actorType: "agent",
actorId: "user-1",
comments: [{ id: "comment-1", updatedAt: new Date(), authorUserId: "user-1" }],
});
expect(queue.entries[0]?.canEdit).toBe(false);
expect(queue.entries[0]?.canDiscard).toBe(false);
});
});