Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
/**
* Sharing smoke test — `npm run check:sharing`.
*
* Pattern visibility (#255) and shared decks (#256), against a throwaway
* SQLite file. What this really guards: the feed and profile queries all read
* the same `patterns` table that now holds unlisted and private rows, so one
* missed filter is a leak — and the deck rules (two public slots, no private
* patterns in a shared deck, gaps instead of silent shortening) are the whole
* design, so they are pinned here rather than hoped for.
*/
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
// The database is a lazy singleton keyed off this variable, so it has to be set
// before anything imports it — hence the dynamic imports below.
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "pf-sharing-"));
process.env.COMMUNITY_DB_PATH = path.join(tmp, "test.db");
process.env.COMMUNITY_ENABLED = "1";
const at = (day: number) => new Date(Date.UTC(2026, 6, day, 12, 0, 0));
let failures = 0;
function check(label: string, actual: unknown, expected: unknown) {
const ok = JSON.stringify(actual) === JSON.stringify(expected);
if (!ok) failures += 1;
console.log(
`${ok ? " ok " : "FAIL "}${label}${ok ? "" : `\n got ${JSON.stringify(actual)}\n want ${JSON.stringify(expected)}`}`,
);
}
async function main() {
const { getDb } = await import("../src/lib/community/server/db");
const schema = await import("../src/lib/community/server/schema");
const queries = await import("../src/lib/community/server/queries");
const { canView, forkBlocked } = await import("../src/lib/community/visibility");
const { checkDeckPattern, cleanPatternIds } = await import("../src/lib/community/deckShare");
const { DECK_MAX, PUBLIC_DECKS_MAX } = await import("../src/lib/community/deck");
const db = getDb();
// ── Seed: two authors, one bystander ──────────────────────────────────────
const person = (id: string, n: number) => ({
id,
name: id,
email: `${id}@patternflow.local`,
emailVerified: false,
createdAt: at(n),
updatedAt: at(n),
username: id,
displayUsername: id,
});
await db.insert(schema.user).values([person("alice", 1), person("bob", 1), person("cara", 1)]);
const pattern = (
id: string,
userId: string,
visibility: string,
day: number,
codeCpp: string | null = null,
) => ({
id,
userId,
title: `Pattern ${id}`,
code: `// ${id}`,
codeCpp,
license: "CC-BY-SA-4.0",
visibility,
createdAt: at(day),
updatedAt: at(day),
});
await db.insert(schema.patterns).values([
pattern("p-pub", "alice", "public", 2, "#pragma once"),
pattern("p-unl", "alice", "unlisted", 3),
pattern("p-priv", "alice", "private", 4),
pattern("p-bob", "bob", "public", 5),
pattern("p-g1", "alice", "public", 6),
pattern("p-g2", "alice", "unlisted", 7),
]);
console.log("\n── the feed shows public only ──");
const feed = await queries.listFeed();
check(
"unlisted and private are not in the feed",
feed.map((item) => item.id).sort(),
["p-bob", "p-g1", "p-pub"],
);
check("the count agrees with the list", await queries.countFeed(), 3);
check(
"the hardware filter stays inside the visible set",
(await queries.listFeed({ hardwareOnly: true })).map((item) => item.id),
["p-pub"],
);
console.log("\n── a profile is the whole archive only to its owner ──");
check(
"the owner sees all three states",
(await queries.listPatternsByUser("alice", "alice")).map((i) => i.id).sort(),
["p-g1", "p-g2", "p-priv", "p-pub", "p-unl"].sort(),
);
check(
"a visitor sees public only",
(await queries.listPatternsByUser("alice", "bob")).map((i) => i.id).sort(),
["p-g1", "p-pub"],
);
check(
"signed-out sees public only",
(await queries.listPatternsByUser("alice", null)).map((i) => i.id).sort(),
["p-g1", "p-pub"],
);
console.log("\n── who may open what ──");
check("public opens for anyone", canView("public", "alice", null), true);
check("unlisted opens by link for anyone", canView("unlisted", "alice", null), true);
check("private is a 404 to a stranger", canView("private", "alice", "bob"), false);
check("private opens for its author", canView("private", "alice", "alice"), true);
check("a moderator keeps sight of everything", canView("private", "alice", "mod", true), true);
console.log("\n── forking respects visibility ──");
const priv = { visibility: "private", userId: "alice" };
check("nobody forks someone else's private work", forkBlocked(priv, "bob"), true);
check("the author may fork their own", forkBlocked(priv, "alice"), false);
check("unlisted is forkable — its credit link resolves", forkBlocked({ visibility: "unlisted", userId: "alice" }, "bob"), false);
console.log("\n── what a deck submission accepts ──");
// Written against DECK_MAX rather than a literal: these assertions used to
// spell the cap out ("eleven patterns"), so raising it turned a passing
// suite red and said nothing about what had actually broken.
check("no patterns is not a deck", cleanPatternIds([]), null);
check(
"a full deck is accepted",
cleanPatternIds(Array.from({ length: DECK_MAX }, (_, i) => `p${i}`))?.length,
DECK_MAX,
);
check(
"one past the cap is not a deck",
cleanPatternIds(Array.from({ length: DECK_MAX + 1 }, (_, i) => `p${i}`)),
null,
);
check("duplicates are refused", cleanPatternIds(["a", "a"]), null);
check("a real list passes through", cleanPatternIds(["a", "b"]), ["a", "b"]);
const stub = (visibility: string, userId: string) => ({ title: "T", userId, visibility });
check("a missing pattern is unavailable", checkDeckPattern(undefined, "public", "alice").ok, false);
check(
"someone else's private answers exactly like missing",
checkDeckPattern(stub("private", "alice"), "public", "bob"),
{ ok: false, reason: "unavailable" },
);
check(
"your own private cannot enter a shared deck",
checkDeckPattern(stub("private", "alice"), "public", "alice"),
{ ok: false, reason: "private", title: "T" },
);
check(
"…but sits fine in a private deck",
checkDeckPattern(stub("private", "alice"), "private", "alice").ok,
true,
);
check(
"unlisted in a public deck is the intended path",
checkDeckPattern(stub("unlisted", "alice"), "public", "bob").ok,
true,
);
// ── Seed decks ─────────────────────────────────────────────────────────────
const deck = (id: string, userId: string, visibility: string, day: number) => ({
id,
userId,
title: `Deck ${id}`,
visibility,
createdAt: at(day),
updatedAt: at(day),
});
await db.insert(schema.decks).values([
deck("d-alice", "alice", "public", 10),
deck("d-alice-priv", "alice", "private", 11),
deck("d-bob-1", "bob", "public", 12),
deck("d-bob-2", "bob", "public", 13),
deck("d-cara-unl", "cara", "unlisted", 14),
deck("d-gap", "alice", "public", 15),
]);
const slot = (deckId: string, patternId: string, position: number) => ({
deckId,
patternId,
position,
titleSnapshot: `Pattern ${patternId}`,
});
await db.insert(schema.deckPatterns).values([
slot("d-alice", "p-pub", 0),
slot("d-alice", "p-unl", 1),
slot("d-bob-1", "p-pub", 0),
slot("d-bob-2", "p-pub", 0),
slot("d-cara-unl", "p-pub", 0),
slot("d-gap", "p-g1", 0),
slot("d-gap", "p-g2", 1),
]);
console.log("\n── deck listings follow the same visibility rules ──");
check(
"the deck feed is public decks only",
(await queries.listPublicDecks()).map((d) => d.id).sort(),
["d-alice", "d-bob-1", "d-bob-2", "d-gap"],
);
check(
"a profile shows its owner everything",
(await queries.listDecksByUser("alice", "alice")).map((d) => d.id).sort(),
["d-alice", "d-alice-priv", "d-gap"],
);
check(
"and a visitor the public ones",
(await queries.listDecksByUser("alice", "bob")).map((d) => d.id).sort(),
["d-alice", "d-gap"],
);
const dAlice = (await queries.listPublicDecks()).find((d) => d.id === "d-alice");
check("pattern count rides along", dAlice?.patternCount, 2);
check(
"the preview strip keeps running order",
dAlice?.preview.map((p) => p.id),
["p-pub", "p-unl"],
);
console.log("\n── the public-slot arithmetic ──");
// These counted against PUBLIC_DECKS_MAX itself, which only held while the
// cap happened to equal the two decks bob is seeded with — raising it broke
// both. What is actually being tested is the counting, so count.
check("bob's public decks are counted", await queries.countPublicDecksByUser("bob"), 2);
check(
"editing an already-public deck does not count itself",
await queries.countPublicDecksByUser("bob", "d-bob-1"),
1,
);
check(
"a spent shelf is what blocks publishing, not the count alone",
(await queries.countPublicDecksByUser("bob")) >= PUBLIC_DECKS_MAX,
false,
);
check("private decks cost nothing", await queries.countPublicDecksByUser("alice"), 2);
console.log("\n── a deck shows the gap, not a shorter set ──");
const before = await queries.listDeckItems("d-gap", null);
check("both slots present before anything happens", before.map((i) => i.gap), [null, null]);
// p-g2's author withdraws it from view…
await db
.update(schema.patterns)
.set({ visibility: "private" })
.where((await import("drizzle-orm")).eq(schema.patterns.id, "p-g2"));
const afterPrivate = await queries.listDeckItems("d-gap", null);
check("…and its slot becomes a private gap", afterPrivate[1]?.gap, "private");
check("the gap keeps the position", afterPrivate[1]?.position, 1);
check("and the name it had", afterPrivate[1]?.titleSnapshot, "Pattern p-g2");
check(
"the pattern's own author still sees it in place",
(await queries.listDeckItems("d-gap", "alice"))[1]?.gap,
null,
);
// …and p-g1's author deletes it outright.
await db.delete(schema.patterns).where((await import("drizzle-orm")).eq(schema.patterns.id, "p-g1"));
const afterDelete = await queries.listDeckItems("d-gap", null);
check("a deleted pattern leaves a deleted gap", afterDelete[0]?.gap, "deleted");
check("its snapshot title survives the row", afterDelete[0]?.titleSnapshot, "Pattern p-g1");
check("the set is still two slots long", afterDelete.length, 2);
console.log("\n── the deck signal on feed cards ──");
// p-pub sits in alice's own deck (does not count), two decks by bob (one
// person, counts once), and cara's unlisted deck (not public, does not
// count) → 1. Shown on the card as DCK; not a sort while decks are few.
const feedNow = await queries.listFeed();
const pPub = feedNow.find((item) => item.id === "p-pub");
check("own decks and unlisted decks do not count, one person counts once", pPub?.deckCount, 1);
check(
"everything else sits at zero",
feedNow.filter((i) => i.id !== "p-pub").map((i) => i.deckCount),
[0],
);
}
main()
.then(() => {
console.log(failures === 0 ? "\nAll sharing checks passed.\n" : `\n${failures} check(s) FAILED.\n`);
process.exit(failures === 0 ? 0 : 1);
})
.catch((error: unknown) => {
console.error(error);
process.exit(1);
})
.finally(() => {
fs.rmSync(tmp, { recursive: true, force: true });
});
ea39f3ad2This file is pinned to the selected release. Download the original to open it in your design tool.