paperclip/packages/shared/src/issue-references.test.ts
Dotta d6bee62f02
Fix Cloud tenant issue identifier routes (#5196)
## Summary

- Allow Cloud tenant issue identifiers with alphanumeric prefixes, such
as `PC1897-1`, to normalize as issue references.
- Resolve those identifiers through issue detail/update routes, active
run/live run polling, activity, costs, and `issueService.getById`.
- Keep UI issue-link parsing aligned so tenant links normalize back to
`/issues/<IDENTIFIER>`.

## Root Cause

Cloud tenant issue prefixes include digits from the stack-id hash. The
app-side route normalization still accepted only all-letter prefixes, so
`/api/issues/PC1897-1` skipped identifier lookup and fell through as a
non-UUID id.

## Verification

- `pnpm exec vitest run packages/shared/src/issue-references.test.ts
ui/src/lib/issue-reference.test.ts
server/src/__tests__/issue-identifier-routes.test.ts
server/src/__tests__/activity-routes.test.ts
server/src/__tests__/costs-service.test.ts
server/src/__tests__/agent-live-run-routes.test.ts
server/src/__tests__/issues-service.test.ts`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `git diff --check`

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-04 13:20:58 -05:00

69 lines
2.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
buildIssueReferenceHref,
extractIssueReferenceIdentifiers,
findIssueReferenceMatches,
normalizeIssueIdentifier,
parseIssueReferenceHref,
} from "./issue-references.js";
describe("issue references", () => {
it("normalizes identifiers to uppercase", () => {
expect(normalizeIssueIdentifier("pap-123")).toBe("PAP-123");
expect(normalizeIssueIdentifier("pc1a2-7")).toBe("PC1A2-7");
expect(normalizeIssueIdentifier("not-an-issue")).toBeNull();
});
it("parses relative and absolute issue hrefs", () => {
expect(parseIssueReferenceHref("/issues/PAP-123")).toEqual({ identifier: "PAP-123" });
expect(parseIssueReferenceHref("/PAP/issues/pap-456")).toEqual({ identifier: "PAP-456" });
expect(parseIssueReferenceHref("https://paperclip.ing/PAP/issues/pap-789#comment-1")).toEqual({
identifier: "PAP-789",
});
expect(parseIssueReferenceHref("https://paperclip.ing/projects/PAP-789")).toBeNull();
});
it("builds canonical issue hrefs", () => {
expect(buildIssueReferenceHref("pap-123")).toBe("/issues/PAP-123");
});
it("finds identifiers and issue paths in plain text", () => {
expect(findIssueReferenceMatches("See PAP-1, /issues/PC1A2-2, and https://x.test/PAP/issues/pc1a2-3.")).toEqual([
{ index: 4, length: 5, identifier: "PAP-1", matchedText: "PAP-1" },
{ index: 11, length: 15, identifier: "PC1A2-2", matchedText: "/issues/PC1A2-2" },
{
index: 32,
length: 33,
identifier: "PC1A2-3",
matchedText: "https://x.test/PAP/issues/pc1a2-3",
},
]);
});
it("trims unmatched square brackets from issue path tokens", () => {
expect(findIssueReferenceMatches("See /issues/PAP-123] for context.")).toEqual([
{ index: 4, length: 15, identifier: "PAP-123", matchedText: "/issues/PAP-123" },
]);
});
it("extracts and dedupes references from markdown", () => {
expect(extractIssueReferenceIdentifiers("PAP-1 [again](/issues/pap-1) PAP-2")).toEqual(["PAP-1", "PAP-2"]);
});
it("ignores inline code and fenced code blocks", () => {
const markdown = [
"Use PAP-1 here.",
"",
"`PAP-2` should not count.",
"",
"```md",
"PAP-3",
"/issues/PAP-4",
"```",
"",
"Final /issues/PAP-5 mention.",
].join("\n");
expect(extractIssueReferenceIdentifiers(markdown)).toEqual(["PAP-1", "PAP-5"]);
});
});