hololake-system-architecture/product-source/hololake-native-desktop/scripts/web-novel-workspace.test.mjs

185 lines
10 KiB
JavaScript
Raw Normal View History

import assert from 'node:assert/strict'
import crypto from 'node:crypto'
import fs from 'node:fs'
import test from 'node:test'
const read = (path) => fs.readFileSync(new URL('../' + path, import.meta.url), 'utf8')
const donors = JSON.parse(read('contracts/web-novel-open-source-donor-assessment.json'))
const contract = JSON.parse(read('contracts/web-novel-workspace.json'))
const routing = JSON.parse(read('contracts/subdomain-industry-routing.json'))
const channelBody = JSON.parse(read('contracts/user-channel-body.json'))
const marketplace = JSON.parse(read('contracts/web-novel-module-marketplace-plan.json'))
const rust = read('src-tauri/src/web_novel_workspace.rs')
const authorRust = read('src-tauri/src/web_novel_author.rs')
const moduleRust = read('src-tauri/src/web_novel_modules.rs')
const rustLibrary = read('src-tauri/src/lib.rs')
const workspaceUi = read('src/WebNovelWorkspace.tsx')
const moduleUi = read('src/AuthorModuleCenter.tsx')
const mainUi = read('src/main.tsx')
test('web novel upstream sources were reviewed at immutable commits through the Singapore relay', () => {
assert.equal(donors.state, 'PRIMARY_SOURCE_CODE_REVIEW_COMPLETE_IMPLEMENTATION_BOUNDARY_LOCKED')
assert.equal(donors.download_route.relay, 'BS-SG-003')
assert.equal(donors.download_route.repositories_are_clean_git_worktrees, true)
assert.equal(donors.candidates.length, 6)
for (const candidate of donors.candidates) {
assert.match(candidate.commit, /^[0-9a-f]{40}$/)
assert.ok(candidate.inspected_paths.length > 0)
assert.ok(candidate.observed_engine.length > 0)
}
})
test('license decisions keep GPL source out of the HoloLake implementation', () => {
assert.match(donors.candidates.find((item) => item.name === 'novelWriter').decision, /DO_NOT_COPY_GPL_SOURCE/)
assert.match(donors.candidates.find((item) => item.name === 'Manuskript').decision, /DO_NOT_COPY_GPL_SOURCE/)
assert.equal(donors.implementation_selection.external_source_files_copied, 0)
assert.equal(donors.implementation_selection.native_owner, 'HOLOLAKE_TAURI_RUST_CORE')
})
test('web novel workspace is account scoped and only projects persona task organs', () => {
assert.equal(contract.state, 'NATIVE_ENGINE_AND_FOUR_HOTPLUG_MODULE_CANDIDATES_MIGRATED_ACCEPTANCE_PENDING')
assert.equal(contract.domain_entry, 'BRANCH_DOMAIN')
assert.equal(contract.industry_number, 'IND-WEBNOVEL-001')
assert.equal(contract.native_storage.authenticated_account_required, true)
assert.equal(contract.native_storage.cross_account_projection_allowed, false)
assert.ok(contract.required_real_engines.includes('CHAPTER_VERSION_HISTORY'))
assert.ok(contract.required_real_engines.includes('CONTINUITY_AUDIT'))
assert.ok(contract.forbidden_substitutes.includes('UI_ONLY_BUTTONS_WITHOUT_NATIVE_COMMANDS'))
assert.equal(contract.ontology_correction.this_contract_is_not, 'USER_CHANNEL_BODY')
assert.equal(contract.ontology_correction.author_editor_operator_are_ui_tabs, false)
assert.equal(contract.distribution_boundary.preinstalled, 'LIGHT_AUTHOR_WORKBENCH_WITH_REAL_ENGINE')
assert.equal(contract.distribution_boundary.current_advanced_features_are_registered_marketplace_modules, false)
const industry = routing.industries.find((item) => item.key === 'WEB_NOVEL')
assert.equal(industry.industry_number, 'IND-WEBNOVEL-001')
assert.equal(industry.initialization_channel.legacy_projection_only, false)
assert.equal(industry.initialization_channel.installed_native_modules, 1)
assert.equal(industry.initialization_channel.module_slots[0].slot_id, 'WEBNOVEL-AUTHOR-LIGHT')
})
test('user channel contract is a durable multi-persona body rather than a module manifest', () => {
assert.equal(channelBody.state, 'FOUNDATION_RUNTIME_PARTIAL_ACCEPTANCE_PENDING')
assert.equal(channelBody.core_cardinality.language_cores_per_human, 1)
assert.equal(channelBody.core_cardinality.persona_cores_per_language_core, 1)
assert.equal(channelBody.core_cardinality.persona_entities_per_persona_core, 'MANY')
assert.equal(channelBody.persona_entity_boundary.role_template_creates_mature_persona, false)
assert.equal(channelBody.persona_entity_boundary.task_agent_is_persona, false)
assert.equal(channelBody.task_projection.workbench_is_channel_body, false)
assert.equal(channelBody.built_in_organs_policy.built_in_organs_allowed, true)
assert.equal(channelBody.module_ecosystem.official_module_number_before_real_acceptance, false)
assert.ok(channelBody.forbidden_substitutes.includes('DECLARATIVE_MODULE_MANIFEST_AS_CHANNEL_BODY'))
assert.ok(channelBody.acceptance_required.includes('TWO_PERSONAS_WITH_ISOLATED_GROWTH_MEMORY'))
})
test('the lightweight author workbench is built in while complete author capabilities stay numbered marketplace candidates', () => {
assert.equal(marketplace.built_in_light_author_workbench.distribution, 'PREINSTALLED')
assert.equal(marketplace.built_in_light_author_workbench.real_engine_owner, 'HOLOLAKE_NATIVE_RUST_SQLITE')
assert.ok(marketplace.built_in_light_author_workbench.capabilities.includes('REAL_CHAPTER_TEXT_EDITING'))
assert.equal(marketplace.official_module_candidates.length, 4)
for (const candidate of marketplace.official_module_candidates) {
assert.match(candidate.module_number, /^HL-MOD-WEBNOVEL-/)
assert.equal(candidate.numbering_state, 'CANDIDATE_NUMBER_NOT_OFFICIALLY_REGISTERED')
}
assert.deepEqual(marketplace.publication_gate.slice(-3), [
'THEN_ASSIGN_PERMANENT_MODULE_NUMBER',
'THEN_REGISTER_OFFICIAL_MODULE_REGISTRY',
'THEN_PUBLISH_OFFICIAL_MARKETPLACE',
])
assert.ok(marketplace.channel_deployment_flow.includes('MOUNT_IN_CURRENT_CHANNEL'))
assert.equal(marketplace.data_boundary.uninstall_preserves_story_data_and_receipts, true)
})
test('the desktop workbench invokes a registered native engine for every primary workflow', () => {
const commands = [
'get_web_novel_workspace_snapshot',
'create_web_novel_work',
'create_web_novel_volume',
'create_web_novel_chapter',
'save_web_novel_chapter',
'transition_web_novel_chapter',
'create_web_novel_checkpoint',
'restore_web_novel_checkpoint',
'upsert_web_novel_story_entity',
'create_web_novel_story_relation',
'upsert_web_novel_foreshadow',
'create_web_novel_review_note',
'resolve_web_novel_review_note',
'save_web_novel_metric',
'run_web_novel_continuity_audit',
'export_web_novel_markdown',
]
for (const command of commands) {
assert.match(rust, new RegExp(`fn ${command}`), `missing Rust implementation: ${command}`)
assert.match(rustLibrary, new RegExp(`web_novel_workspace::${command}`), `missing Tauri registration: ${command}`)
assert.match(workspaceUi, new RegExp(`['\"]${command}['\"]`), `missing UI invocation: ${command}`)
}
assert.match(rust, /account_storage_root\(app, "web-novel-workspace-v1"\)/)
assert.match(rust, /HOLOLAKE_WEBNOVEL_CHAPTER_REVISION_CONFLICT/)
assert.match(rust, /web_novel_chapter_versions/)
assert.match(workspaceUi, /window\.setTimeout\(\(\) => void flushChapterSave\(\), 650\)/)
assert.match(workspaceUi, /role=\{createFeedback\.state === 'error' \? 'alert' : 'status'\}/)
assert.match(workspaceUi, /正在建立作品与第一卷/)
assert.match(workspaceUi, /作品没有创建:/)
assert.match(workspaceUi, /第一卷已经可以开始写作/)
assert.match(mainUi, /worldStage === 'webNovelWorkspace'/)
})
test('author formatting is automatic on import, selectable, reversible, and can cover the whole work', () => {
for (const command of ['format_web_novel_chapter', 'format_web_novel_work']) {
assert.match(authorRust, new RegExp(`fn ${command}`))
assert.match(rustLibrary, new RegExp(`web_novel_author::${command}`))
assert.match(workspaceUi, new RegExp(`['"]${command}['"]`))
}
assert.match(authorRust, /formatted\.join\("\\n\\n"\)/)
assert.match(read('src-tauri/src/web_novel_import.rs'), /AUTO_FORMAT_ON_IMPORT/)
assert.match(workspaceUi, /细纲结构视图/)
assert.match(read('src/AuthorWritingSidecar.tsx'), /一键排版整部作品/)
})
test('author modules are immutable packages with native install mount data and uninstall engines', () => {
const modules = [
['HL-MOD-WEBNOVEL-OUTLINE-001', '6d31d3f530862000bd84a8f5dc73bcb9cf0d5738219f7a287562d4f4bcfa5318'],
['HL-MOD-WEBNOVEL-GRID-002', '2e34e0337c51dbbdd1efdd807be71e69a0cb2a1745624e1f51b452b7b54f1553'],
['HL-MOD-WEBNOVEL-STORYWORLD-003', 'd486719500ddfc08054d9c9bb6d3683afe5547a2b24c9b04d0f899ac66094d2f'],
['HL-MOD-WEBNOVEL-DELIVERY-004', '4af1d4610826edd29759bd0dc12b4b0c04fd2ebebb4ebc362d47d762810989e8'],
]
for (const [moduleId, expectedSha] of modules) {
const bytes = fs.readFileSync(new URL(`../official-modules/web-novel/${moduleId}/1.0.0/module.json`, import.meta.url))
const manifest = JSON.parse(bytes.toString('utf8'))
assert.equal(manifest.moduleId, moduleId)
assert.equal(crypto.createHash('sha256').update(bytes).digest('hex'), expectedSha)
assert.match(moduleRust, new RegExp(expectedSha))
}
assert.match(moduleRust, /web_novel_module_installations/)
assert.match(moduleRust, /web_novel_module_receipts/)
assert.match(moduleRust, /require_mounted/)
assert.match(moduleRust, /USER_DATA_REMAINS_AFTER_UNINSTALL|作品、模块数据和历史回执保持/)
assert.match(moduleRust, /RESTORED_FROM_VERSION/)
assert.match(moduleRust, /render_docx/)
assert.match(moduleRust, /render_epub/)
})
test('every author module action is registered and invoked by the live channel workbench', () => {
const commands = [
'get_web_novel_author_module_marketplace',
'install_web_novel_author_module',
'mount_web_novel_author_module',
'unmount_web_novel_author_module',
'uninstall_web_novel_author_module',
'get_web_novel_author_module_data',
'upsert_web_novel_author_scene',
'upsert_web_novel_author_beat',
'upsert_web_novel_story_field_definition',
'upsert_web_novel_story_field_value',
'upsert_web_novel_timeline_event',
'link_web_novel_scene_entity',
'restore_web_novel_chapter_version',
'export_web_novel_author_delivery',
]
for (const command of commands) {
assert.match(moduleRust, new RegExp(`fn ${command}`), `missing module engine: ${command}`)
assert.match(rustLibrary, new RegExp(`web_novel_modules::${command}`), `missing module registration: ${command}`)
assert.match(moduleUi, new RegExp(`['"]${command}['"]`), `missing module UI invocation: ${command}`)
}
assert.match(workspaceUi, /<AuthorModuleCenter/)
assert.match(workspaceUi, /\['modules', '作者模块'\]/)
})