clawhub/convex/githubBackups.test.ts
2026-04-29 23:33:25 -07:00

205 lines
6.1 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { getGitHubBackupPageInternal } from "./githubBackups";
const handler = (getGitHubBackupPageInternal as unknown as { _handler: Function })._handler;
describe("githubBackups page filtering", () => {
it("skips non-public digests (soft-deleted, hidden, removed)", async () => {
const activeDigest = {
_id: "skillSearchDigest:active",
skillId: "skills:active",
slug: "active-skill",
displayName: "Active Skill",
ownerUserId: "users:active",
ownerHandle: "alice",
latestVersionId: "skillVersions:active",
latestVersionSummary: {
version: "1.0.0",
createdAt: 1_700_000_000_000,
changelog: "init",
},
softDeletedAt: undefined,
moderationStatus: "active",
};
const hiddenDigest = {
_id: "skillSearchDigest:hidden",
skillId: "skills:hidden",
slug: "hidden-skill",
displayName: "Hidden Skill",
ownerUserId: "users:hidden",
ownerHandle: "bob",
latestVersionId: "skillVersions:hidden",
latestVersionSummary: {
version: "1.0.0",
createdAt: 1_700_000_000_000,
changelog: "init",
},
softDeletedAt: undefined,
moderationStatus: "hidden",
};
const removedDigest = {
_id: "skillSearchDigest:removed",
skillId: "skills:removed",
slug: "removed-skill",
displayName: "Removed Skill",
ownerUserId: "users:removed",
ownerHandle: "carol",
latestVersionId: "skillVersions:removed",
latestVersionSummary: {
version: "1.0.0",
createdAt: 1_700_000_000_000,
changelog: "init",
},
softDeletedAt: undefined,
moderationStatus: "removed",
};
const softDeletedDigest = {
_id: "skillSearchDigest:soft",
skillId: "skills:soft",
slug: "soft-skill",
displayName: "Soft Skill",
ownerUserId: "users:soft",
ownerHandle: "dave",
latestVersionId: "skillVersions:soft",
latestVersionSummary: {
version: "1.0.0",
createdAt: 1_700_000_000_000,
changelog: "init",
},
softDeletedAt: 1,
moderationStatus: "active",
};
const paginate = vi.fn().mockResolvedValue({
page: [activeDigest, hiddenDigest, removedDigest, softDeletedDigest],
isDone: true,
continueCursor: null,
});
const order = vi.fn().mockReturnValue({ paginate });
const query = vi.fn().mockReturnValue({ order });
const result = await handler(
{
db: { query },
} as never,
{ batchSize: 50 },
);
expect(query).toHaveBeenCalledWith("skillSearchDigest");
expect(result).toMatchObject({
isDone: true,
cursor: null,
items: [
{
kind: "ok",
slug: "active-skill",
ownerHandle: "alice",
version: "1.0.0",
},
],
});
});
it("keeps legacy digests with undefined moderationStatus eligible", async () => {
const legacyDigest = {
_id: "skillSearchDigest:legacy",
skillId: "skills:legacy",
slug: "legacy-skill",
displayName: "Legacy Skill",
ownerUserId: "users:legacy",
ownerHandle: "",
latestVersionId: "skillVersions:legacy",
latestVersionSummary: {
version: "2.0.0",
createdAt: 1_700_000_000_100,
changelog: "update",
},
softDeletedAt: undefined,
moderationStatus: undefined,
};
const paginate = vi.fn().mockResolvedValue({
page: [legacyDigest],
isDone: true,
continueCursor: null,
});
const order = vi.fn().mockReturnValue({ paginate });
const query = vi.fn().mockReturnValue({ order });
const result = await handler(
{
db: { query },
} as never,
{},
);
expect(result.items).toHaveLength(1);
expect(result.items[0]).toMatchObject({
kind: "ok",
slug: "legacy-skill",
ownerHandle: "users:legacy",
version: "2.0.0",
});
});
it("skips digests without ownerHandle or latestVersionSummary", async () => {
const noOwnerHandle = {
_id: "skillSearchDigest:no-owner",
skillId: "skills:no-owner",
slug: "no-owner",
displayName: "No Owner",
ownerUserId: "users:no-owner",
ownerHandle: undefined,
latestVersionId: "skillVersions:no-owner",
latestVersionSummary: { version: "1.0.0", createdAt: 1, changelog: "init" },
softDeletedAt: undefined,
moderationStatus: "active",
};
const noVersion = {
_id: "skillSearchDigest:no-version",
skillId: "skills:no-version",
slug: "no-version",
displayName: "No Version",
ownerUserId: "users:no-version",
ownerHandle: "frank",
latestVersionId: undefined,
latestVersionSummary: undefined,
softDeletedAt: undefined,
moderationStatus: "active",
};
const paginate = vi.fn().mockResolvedValue({
page: [noOwnerHandle, noVersion],
isDone: true,
continueCursor: null,
});
const order = vi.fn().mockReturnValue({ paginate });
const query = vi.fn().mockReturnValue({ order });
const result = await handler({ db: { query } } as never, {});
expect(result.items).toEqual([
{ kind: "missingOwner", skillId: "skills:no-owner", ownerUserId: "users:no-owner" },
{ kind: "missingLatestVersion", skillId: "skills:no-version" },
]);
});
it("resets stale skills-table cursors after switching to digest pagination", async () => {
const paginate = vi
.fn()
.mockRejectedValueOnce(new Error("cursor is from a different query"))
.mockResolvedValueOnce({ page: [], isDone: true, continueCursor: null });
const order = vi.fn().mockReturnValue({ paginate });
const query = vi.fn().mockReturnValue({ order });
const result = await handler({ db: { query } } as never, { cursor: "stale-cursor" });
expect(result).toMatchObject({ items: [], isDone: true, cursor: null });
expect(paginate).toHaveBeenNthCalledWith(1, { cursor: "stale-cursor", numItems: 50 });
expect(paginate).toHaveBeenNthCalledWith(2, { cursor: null, numItems: 50 });
});
});