diff --git a/fixtures/bitvid/characterization-cases.json b/fixtures/bitvid/characterization-cases.json new file mode 100644 index 0000000..10f5c10 --- /dev/null +++ b/fixtures/bitvid/characterization-cases.json @@ -0,0 +1,21 @@ +[ + "personal-block-precedence", + "user-deny", + "event-deny", + "address-deny", + "allowlist-miss", + "protected-target", + "trusted-report-threshold-reached", + "duplicate-reporter-dedupe", + "blocked-reporter-ignored", + "trusted-mute-below-threshold", + "trusted-mute-at-threshold", + "expired-trusted-mute", + "viewer-override", + "author-override", + "cache-fallback-effective", + "report-category-thresholds", + "mute-aggregation-rules", + "hide-vs-restrict-vs-downrank", + "moderation-decorator-fields" +] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e55c673..7291384 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1894,11 +1894,7 @@ } }, "packages/testing": { - "name": "@nostr-governance/testing", - "version": "0.1.0", - "dependencies": { - "@nostr-governance/core": "*" - } + "name": "@nostr-governance/testing" } } } diff --git a/packages/testing/package.json b/packages/testing/package.json index 1c6db95..9d7584b 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -1,20 +1,8 @@ { "name": "@nostr-governance/testing", - "version": "0.1.0", "type": "module", - "main": "src/index.js", - "types": "dist/types/src/index.d.ts", - "files": ["src", "dist"], - "exports": { - ".": { - "types": "./dist/types/src/index.d.ts", - "import": "./src/index.js" - } - }, + "private": true, "scripts": { "test": "vitest run" - }, - "dependencies": { - "@nostr-governance/core": "*" } -} +} \ No newline at end of file diff --git a/packages/testing/src/conformance.js b/packages/testing/src/conformance.js new file mode 100644 index 0000000..37b8637 --- /dev/null +++ b/packages/testing/src/conformance.js @@ -0,0 +1,24 @@ +// Shared test utilities for conformance harness +export function createFixture(name, state, target, expectation) { + return { + name, + state: { + adminState: state.adminState || {}, + trust: state.trust || {}, + reports: state.reports || [], + trustedMutes: state.trustedMutes || [], + overrides: state.overrides || {}, + }, + target, + expectation, + }; +} + +export function runConformanceTest(fixture, evaluator) { + const decision = evaluator(fixture.state, fixture.target); + return { + passed: JSON.stringify(decision) === JSON.stringify(fixture.expectation), + actual: decision, + expected: fixture.expectation, + }; +} \ No newline at end of file diff --git a/packages/testing/src/index.js b/packages/testing/src/index.js index ade09ff..8ef9179 100644 --- a/packages/testing/src/index.js +++ b/packages/testing/src/index.js @@ -1,2 +1,2 @@ -// Placeholder for testing module exports +// Testing package exports export {}; \ No newline at end of file diff --git a/packages/testing/test/conformance.test.js b/packages/testing/test/conformance.test.js new file mode 100644 index 0000000..86924a0 --- /dev/null +++ b/packages/testing/test/conformance.test.js @@ -0,0 +1,42 @@ +// Test the conformance utilities +import { describe, it, expect } from "vitest"; +import { createFixture, runConformanceTest } from "../src/conformance.js"; + +describe("conformance utilities", () => { + it("creates valid fixture objects", () => { + const fixture = createFixture( + "test-fixture", + { + adminState: { editors: [] }, + trust: {}, + reports: [], + trustedMutes: [], + overrides: {}, + }, + { type: "user", pubkey: "test" }, + { visibility: "allow" } + ); + + expect(fixture.name).toBe("test-fixture"); + expect(fixture.state.adminState.editors).toEqual([]); + expect(fixture.target).toEqual({ type: "user", pubkey: "test" }); + expect(fixture.expectation).toEqual({ visibility: "allow" }); + }); + + it("runs conformance tests", () => { + const fixture = createFixture( + "test-fixture", + {}, + { type: "user", pubkey: "test" }, + { visibility: "allow" } + ); + + // Mock evaluator that always returns the expectation + const mockEvaluator = () => ({ visibility: "allow" }); + + const result = runConformanceTest(fixture, mockEvaluator); + expect(result.passed).toBe(true); + expect(result.actual).toEqual({ visibility: "allow" }); + expect(result.expected).toEqual({ visibility: "allow" }); + }); +}); \ No newline at end of file diff --git a/scripts/import-bitvid-fixtures.mjs b/scripts/import-bitvid-fixtures.mjs new file mode 100644 index 0000000..599a67e --- /dev/null +++ b/scripts/import-bitvid-fixtures.mjs @@ -0,0 +1,111 @@ +// Import BitVid fixtures into the conformance harness. +// This is a one-time extractor that reads from reference repos only. +// No live imports are allowed in package sources. +import { readFile, writeFile, mkdir } from "node:fs/promises"; +import { join, relative } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const ROOT = join(__filename, "../../.."); + +// BitVid checkout we're extracting from (read-only) +const BITVID_ROOT = "/home/user/Documents/GitHub/bitvid"; + +// Target directories +const FIXTURES_ROOT = join(ROOT, "fixtures/bitvid"); +const ADMIN_STATE_DIR = join(FIXTURES_ROOT, "admin-state"); +const REPORTS_DIR = join(FIXTURES_ROOT, "reports"); +const TRUSTED_MUTES_DIR = join(FIXTURES_ROOT, "trusted-mutes"); +const OVERRIDES_DIR = join(FIXTURES_ROOT, "overrides"); +const EXPECTATIONS_DIR = join(FIXTURES_ROOT, "expectations"); + +// Ensure directories exist +async function setupDirectories() { + const dirs = [ + FIXTURES_ROOT, + ADMIN_STATE_DIR, + REPORTS_DIR, + TRUSTED_MUTES_DIR, + OVERRIDES_DIR, + EXPECTATIONS_DIR, + ]; + for (const dir of dirs) { + await mkdir(dir, { recursive: true }); + } +} + +// Read a file from BitVid and write it to our fixtures +async function copyFixture(srcPath, destPath, transform = null) { + const content = await readFile(srcPath, "utf8"); + const finalContent = transform ? transform(content) : content; + await writeFile(destPath, finalContent, "utf8"); + console.log(`Copied ${relative(BITVID_ROOT, srcPath)} → ${relative(ROOT, destPath)}`); +} + +// Example: copy a test file that demonstrates trusted-report behavior +async function importTrustedReportFixtures() { + // This would extract specific test cases from BitVid's test files + // For now, we'll create placeholders that describe what we need + const placeholder = `// Trusted report threshold fixture +// Extracted from BitVid js/services/moderationService.js characterization +export default { + description: "Trusted report threshold reached", + state: { + // Admin list state (curators, editors, super admin) + adminState: { + editors: [], + whitelist: [], + blacklist: [], + eventBlacklist: [] + }, + // Trust graph state (who trusts whom) + trust: { + // pubkey -> [trustedBy1, trustedBy2, ...] + }, + // Active reports + reports: [ + // { reporter: pubkey, target: pubkey, category: string, timestamp: number } + ], + // Trusted mutes (aggregated from trust graph) + trustedMutes: [ + // { target: pubkey, count: number, categories: { spam: number, ... } } + ], + // Overrides (viewer-specific) + overrides: { + // pubkey -> { visibility: "allow"|"restrict"|"hide", reason: string } + } + }, + // Target to evaluate + target: { + type: "user", + pubkey: "example_pubkey_hex" + }, + // Expected outcome + expectation: { + visibility: "hide", + interaction: "deny", + reasons: ["trusted-report-threshold"], + evidence: [ + // { reporter: pubkey, category: string, timestamp: number } + ] + } +};`; + + await writeFile( + join(FIXTURES_ROOT, "trusted-report-threshold.fixture.js"), + placeholder + ); + console.log("Created trusted-report-threshold fixture placeholder"); +} + +// Main import process +async function main() { + await setupDirectories(); + await importTrustedReportFixtures(); + console.log("Fixture import script ready. Add real extractors as needed."); +} + +main().catch((error) => { + console.error("Fixture import failed:", error); + process.exit(1); +}); \ No newline at end of file diff --git a/tests/conformance/bitvid-fixtures.test.js b/tests/conformance/bitvid-fixtures.test.js new file mode 100644 index 0000000..411491a --- /dev/null +++ b/tests/conformance/bitvid-fixtures.test.js @@ -0,0 +1,47 @@ +// Test harness for BitVid parity cases +import { describe, it, expect } from "vitest"; + +// BitVid parity characterization cases +const BITVID_CHARACTERIZATION_CASES = [ + "personal-block-precedence", + "user-deny", + "event-deny", + "address-deny", + "allowlist-miss", + "protected-target", + "trusted-report-threshold-reached", + "duplicate-reporter-dedupe", + "blocked-reporter-ignored", + "trusted-mute-below-threshold", + "trusted-mute-at-threshold", + "expired-trusted-mute", + "viewer-override", + "author-override", + "cache-fallback-effective", + "report-category-thresholds", + "mute-aggregation-rules", + "hide-vs-restrict-vs-downrank", + "moderation-decorator-fields" +]; + +describe("BitVid parity characterization", () => { + // This suite will grow as we add real fixtures + // For now, just ensure the harness loads correctly + + it("has a list of characterization cases", () => { + expect(Array.isArray(BITVID_CHARACTERIZATION_CASES)).toBe(true); + expect(BITVID_CHARACTERIZATION_CASES.length).toBe(19); + }); + + // Placeholder for when we have real fixtures + describe.skip("trusted-report-threshold-reached", () => { + it("should hide targets with sufficient trusted reports", async () => { + // Load fixture + // const fixture = await import("../../fixtures/bitvid/trusted-report-threshold.fixture.js"); + // Run through evaluator + // const decision = evaluate(fixture.state, fixture.target); + // Compare to expectation + // expect(decision).toEqual(fixture.expectation); + }); + }); +}); \ No newline at end of file