import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8') const registry = JSON.parse(read('contracts/numbered-ipc-registry.json')) const runtime = read('src-tauri/src/module_package_runtime.rs') const moduleAdapter = read('src-tauri/src/web_novel_modules.rs') const frontend = [ read('src/modules/web-novel/WebNovelWorkspace.tsx'), read('src/modules/web-novel/AuthorModuleCenter.tsx'), read('src/modules/web-novel/AuthorWritingSidecar.tsx'), ].join('\n') const packages = [ 'WORKBENCH', 'OUTLINE', 'GRID', 'STORYWORLD', 'DELIVERY', ].map((name) => JSON.parse(read(`fixtures/module-packages/HLP-MOD-OFFICIAL-WEB-NOVEL-${name}-0001-0.1.0.ghmod`))) test('web novel family has five immutable official identities behind one shared lifecycle', () => { assert.equal(packages.length, 5) assert.ok(packages.every((value) => value.manifest.registrationClass === 'OFFICIAL_LIGHTHOUSE')) assert.ok(packages.every((value) => value.manifest.adapter === 'web-novel-workbench-v1')) assert.equal(new Set(packages.map((value) => value.manifest.moduleNumber)).size, 5) assert.match(runtime, /const BUNDLED_MODULES/) assert.doesNotMatch(frontend, /install_web_novel_author_module|mount_web_novel_author_module|uninstall_web_novel_author_module/) }) test('all 38 web novel effects cross exact numbered routes and no raw tauri command surface', () => { const routes = registry.operations.filter((route) => Number(route.operation_number.slice(-4)) >= 103 && Number(route.operation_number.slice(-4)) <= 140) assert.equal(routes.length, 38) assert.deepEqual([...new Set(routes.map((route) => route.module_number))], [ 'HLP-NIPC-MOD-0025', 'HLP-NIPC-MOD-0026', 'HLP-NIPC-MOD-0027', 'HLP-NIPC-MOD-0028', 'HLP-NIPC-MOD-0029', ]) assert.ok(routes.every((route) => route.admission === 'VERIFIED_HUMAN_ROUTE')) assert.doesNotMatch(frontend, /from ['"]@tauri-apps\/api\/core['"]/) for (const source of ['web_novel_workspace.rs', 'web_novel_import.rs', 'web_novel_author.rs', 'web_novel_modules.rs']) { assert.doesNotMatch(read(`src-tauri/src/${source}`), /#\[tauri::command\]/) } }) test('advanced effects require their own exact active module number', () => { assert.match(moduleAdapter, /require_module_active\(&app, OUTLINE\)/) assert.match(moduleAdapter, /require_module_active\(&app, GRID\)/) assert.match(moduleAdapter, /require_module_active\(&app, STORYWORLD\)/) assert.match(moduleAdapter, /require_module_active\(&app, DELIVERY\)/) assert.match(moduleAdapter, /shared signed package runtime/) }) test('workspace is independently lazy-loaded and preserves old account data in place', () => { assert.match(read('src/main.tsx'), /lazy\(\(\) => import\('\.\/modules\/web-novel\/WebNovelWorkspace'\)/) assert.equal(packages[0].payload.adapterConfig.legacyDataPolicy, 'READ_IN_PLACE_NO_DESTRUCTIVE_MIGRATION') assert.equal(packages[0].payload.adapterConfig.thirdPartyPublishDefault, 'DENY') assert.doesNotMatch(read('src-tauri/src/web_novel_workspace.rs'), /serde_json::from_str[^\n]+unwrap_or_default\(\)/) })