Files
buzz/desktop/src/features/projects/projectWorkItems.test.mjs
thomaspblock e30db7028f feat(projects): support multiple repositories (#4671)
## Summary
- adopt the finalized NIP-MP project model so one project can enumerate
and switch between multiple NIP-34 repositories
- add project and repository navigation, activity summaries,
existing-repository attachment, and repository access-channel management
- preserve privacy-safe activation provenance for agent-authored
patches, pull requests, issues, and associated commits

## Test plan
- [x] Run desktop typecheck and unit tests
- [x] Run focused NIP-MP, repository access, and provenance tests
- [x] Run Rust formatting and desktop lint checks
- [x] Run the complete pre-push suite after merging current `main`
- [ ] Manually verify project creation, repository attachment,
switching, and access repair on staging
- [ ] Manually verify public-channel and private-agent origin labels on
newly created Git activity

Related: [#4695](https://github.com/block/buzz/pull/4695)

---------

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
2026-08-04 17:30:12 -04:00

140 lines
4.3 KiB
JavaScript

import assert from "node:assert/strict";
import test from "node:test";
import { fetchProjectsWorkItems } from "./projectWorkItems.ts";
// ── Work-item deduplication ─────────────────────────────────────────────────
//
// NIP-MP §Multiple membership: a repository may belong to several projects.
// When it does, global issue/PR lists must produce exactly one row per work
// item — not one row per project membership. These tests call the exported
// production function with a stubbed fetchEvents to verify the dedup contract
// end-to-end, not just the filter algorithm in isolation.
const REPO_OWNER = "a".repeat(64);
const REPO_DTAG = "relay";
const REPO_ADDRESS = `30617:${REPO_OWNER}:${REPO_DTAG}`;
const ISSUE_ID = "i".repeat(64);
const PR_ID = "p".repeat(64);
const PR_ID_2 = "q".repeat(64);
// Two projects that both contain the same repository.
const projectA = {
repositories: [{ repoAddress: REPO_ADDRESS }],
};
const projectB = {
repositories: [{ repoAddress: REPO_ADDRESS }],
};
// Minimal valid NIP-34 issue event for the shared repo.
function makeIssue(id, updatedAt = 100) {
return {
id,
kind: 1621,
pubkey: REPO_OWNER,
created_at: updatedAt,
content: "An issue",
tags: [
["a", REPO_ADDRESS],
["subject", "Fix the thing"],
],
};
}
// Minimal valid NIP-34 pull request event for the shared repo.
function makePR(id, updatedAt = 100) {
return {
id,
kind: 1618, // KIND_GIT_PULL_REQUEST
pubkey: REPO_OWNER,
created_at: updatedAt,
content: "A PR",
tags: [
["a", REPO_ADDRESS],
["subject", "Add a feature"],
],
};
}
// fetchEvents stub: returns the given root events (issues + PRs) and empty
// arrays for all other query kinds (updates, comments, statuses).
function makeFetchEvents(rootEvents) {
return async (filter) => {
const { kinds } = filter;
// Root issues (kind 1621) + PRs (kind 1618)
if (kinds?.includes(1621) || kinds?.includes(1618)) {
return rootEvents.filter((e) => kinds.includes(e.kind));
}
// Everything else (PR updates, comments, statuses) — empty
return [];
};
}
test("fetchProjectsWorkItems deduplicates issues from a shared repository", async () => {
const issue = makeIssue(ISSUE_ID);
const fetchEvents = makeFetchEvents([issue]);
const result = await fetchProjectsWorkItems(
[projectA, projectB],
fetchEvents,
);
assert.equal(
result.issues.items.length,
1,
"duplicate issue from shared repo must collapse to one row",
);
assert.equal(result.issues.items[0].issue.id, ISSUE_ID);
});
test("fetchProjectsWorkItems deduplicates pull requests from a shared repository", async () => {
const pr1 = makePR(PR_ID, 100);
const pr2 = makePR(PR_ID_2, 90);
const fetchEvents = makeFetchEvents([pr1, pr2]);
const result = await fetchProjectsWorkItems(
[projectA, projectB],
fetchEvents,
);
assert.equal(
result.pullRequests.items.length,
2,
"distinct PRs must survive dedup; only exact-id duplicates collapse",
);
const ids = result.pullRequests.items.map((item) => item.pullRequest.id);
assert.ok(ids.includes(PR_ID), "first PR must be present");
assert.ok(ids.includes(PR_ID_2), "second PR must be present");
});
test("fetchProjectsWorkItems returns a single row for a PR present in both project contexts", async () => {
// Same PR id returned twice (once per project's relay query).
const pr = makePR(PR_ID, 100);
// The stub returns the same event for every root query, simulating
// the relay returning the same PR for both projects' repo addresses.
let callCount = 0;
const fetchEvents = async (filter) => {
if (filter.kinds?.includes(1618)) {
callCount += 1;
return [pr];
}
return [];
};
const result = await fetchProjectsWorkItems(
[projectA, projectB],
fetchEvents,
);
// The relay was queried once per unique repo address — but even if it
// returned the same id twice across the two project contexts, dedupe fires.
assert.equal(
result.pullRequests.items.length,
1,
"same PR id appearing in both project contexts must produce one row",
);
// Sanity: the stub was actually called (proves we ran the production path).
assert.ok(callCount >= 1, "fetchEvents must have been called");
});