const assert = require("assert"); const fs = require("fs"); const os = require("os"); const path = require("path"); const Database = require("better-sqlite3"); const { PRESERVED_PATHS } = require("../../../src/services/update-repository"); const { accessForUser, createEntry, ensureTables, getEntryBySlug, grantPermission, listEntries, listPermissions, listVersions, revokePermission, restoreVersion, searchForAi, setEntryWorkflow, updateEntry } = require("../backend/okf_store"); const { ensureKnowledgeDirs, getCommunityKnowledgeFile, listCommunityKnowledgeFiles, loadKnowledgeEntries, listKnowledgePlaceholders, saveCorrectionKnowledgeFile, saveCommunityKnowledgeFile, migrateSingleBracePlaceholders, searchFileKnowledge } = require("../backend/file_knowledge"); const { generateKnowledgeFiles } = require("../backend/generate_knowledge"); const { renderMarkdown } = require("../backend/markdown"); const db = new Database(":memory:"); db.exec(` CREATE TABLE user_profiles ( id TEXT PRIMARY KEY, internal_username TEXT NOT NULL UNIQUE, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ); `); ensureTables(db); const now = Date.now(); db.prepare("INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)").run("admin-1", "Admin", now, now); db.prepare("INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)").run("mod-1", "Mod", now, now); db.prepare("INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)").run("user-1", "User", now, now); db.prepare("INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)").run("editor-1", "Editor", now, now); const admin = { id: "admin-1", isAdmin: true, isMod: true }; const mod = { id: "mod-1", isMod: true }; const user = { id: "user-1" }; const editor = { id: "editor-1" }; assert(PRESERVED_PATHS.includes("knowledge/community")); assert(PRESERVED_PATHS.includes("knowledge/corrections")); const adminTemplate = fs.readFileSync(path.join(__dirname, "..", "views", "admin.ejs"), "utf8"); assert(adminTemplate.includes("General OKF")); assert(adminTemplate.includes("Community OKF")); assert(adminTemplate.includes("System-generated OKF")); assert(adminTemplate.includes("tab=general")); assert(adminTemplate.includes("tab=community")); assert(adminTemplate.includes("tab=system")); assert(adminTemplate.includes("selectedSystemFile")); assert(adminTemplate.includes("System-generated OKF") && !adminTemplate.includes("Save system file")); assert(adminTemplate.includes("data-okf-file-list")); assert(adminTemplate.includes("data-okf-file-search")); assert(adminTemplate.includes('data-okf-file-filter="category"')); assert(adminTemplate.includes("No community OKF files match these filters.")); assert(adminTemplate.includes("No system-generated OKF files match these filters.")); const okfPluginSource = fs.readFileSync(path.join(__dirname, "..", "index.js"), "utf8"); assert(okfPluginSource.includes("preferSpecificKnowledgeChunks")); assert(okfPluginSource.includes("trimForContext")); assert(okfPluginSource.includes("remaining = 4200")); grantPermission(db, { user_id: "editor-1", level: "edit", notes: "Trusted writer" }, admin); assert.equal(accessForUser(db, editor).canEdit, true); assert.equal(listPermissions(db).filter((grant) => !grant.revoked_at).length, 1); const entry = createEntry(db, { title: "Currency basics", slug: "currency-basics", category: "Community", tags: "currency, coins", aliases: "How do coins, bonuses, and payouts work?\nWhat is currency?", summary: "Explains the community currency.", user_markdown: "Users earn **coins** through activity.", moderator_markdown: "Moderators may help users with missing transaction context.", admin_markdown: "Admin-only economy configuration details.", ai_facts_markdown: "The primary community currency is coins.", source_links: "/commands", visibility: "mod", status: "published", review_state: "approved" }, admin); assert.equal(entry.slug, "currency-basics"); assert.deepEqual(entry.aliases, ["How do coins, bonuses, and payouts work?", "What is currency?"]); assert.equal(listEntries(db, {}, user).length, 0); assert.equal(listEntries(db, {}, mod).length, 1); assert.equal(getEntryBySlug(db, "currency-basics", user), null); assert.equal(getEntryBySlug(db, "currency-basics", mod).moderator_markdown.includes("Moderators"), true); assert.equal(getEntryBySlug(db, "currency-basics", admin).admin_markdown.includes("Admin-only"), true); assert(searchForAi(db, "currency", mod).some((item) => item.facts.includes("Moderators"))); assert.equal(searchForAi(db, "currency", user).length, 0); const publicEntry = createEntry(db, { title: "Public help", slug: "public-help", category: "Support", summary: "Public support answer.", user_markdown: "Everyone can read this.", admin_markdown: "Admin-only implementation notes.", ai_facts_markdown: "Admin AI fact.", visibility: "user", status: "published", review_state: "approved" }, admin); const publicUserContext = searchForAi(db, "public", user); const publicAdminContext = searchForAi(db, "public", admin); assert(publicUserContext.some((item) => item.id === publicEntry.id)); assert.equal(publicUserContext.some((item) => item.facts.includes("Admin-only")), false); assert(publicAdminContext.some((item) => item.facts.includes("Admin-only implementation notes."))); assert(publicAdminContext.some((item) => item.facts.includes("Admin AI fact."))); const proposed = createEntry(db, { title: "Draft from editor", slug: "draft-from-editor", summary: "Editor draft", user_markdown: "Draft content." }, editor); assert.equal(proposed.status, "draft"); assert.equal(proposed.review_state, "review_pending"); assert.equal(listEntries(db, {}, user).some((item) => item.slug === "draft-from-editor"), false); assert.equal(listEntries(db, {}, editor, { management: true }).some((item) => item.slug === "draft-from-editor"), true); updateEntry(db, "draft-from-editor", { title: "Draft from editor", slug: "draft-from-editor", summary: "Updated editor draft", user_markdown: "Updated draft content.", change_note: "Editor update." }, editor); assert.equal(listVersions(db, proposed.id).length, 2); const restored = restoreVersion(db, proposed.id, 1, admin, "Test restore."); assert.equal(restored.summary, "Editor draft"); assert.equal(restored.user_markdown, "Draft content."); assert.equal(listVersions(db, proposed.id).length, 3); assert.equal(searchForAi(db, "draft", admin).length, 0); setEntryWorkflow(db, "draft-from-editor", "review", admin); setEntryWorkflow(db, "draft-from-editor", "publish", admin); assert.equal(getEntryBySlug(db, "draft-from-editor", user).summary, "Editor draft"); assert(searchForAi(db, "currency", mod).some((item) => item.slug === "currency-basics")); assert.equal(searchForAi(db, "admin-only economy", user).length, 0); assert(renderMarkdown(" **safe**").includes("<script>")); assert.equal(renderMarkdown("[bad](javascript:alert(1))").includes("javascript:"), false); const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-okf-")); ensureKnowledgeDirs(tempRoot); const commandsKnowledgePath = path.join(tempRoot, "knowledge", "core", "commands.md"); const currencyKnowledgePath = path.join(tempRoot, "knowledge", "community", "currency.md"); const correctionKnowledgePath = path.join(tempRoot, "knowledge", "corrections", "currency.md"); fs.writeFileSync(commandsKnowledgePath, [ "---", "id: core.commands", "title: Core commands", "scope: core", "status: active", "priority: 1", "visibility: user", "tags: commands, help", "generated: true", "editable: false", "---", "# Commands", "Use !help to list available commands.", "The public currency is {{community.currency.primary_name}}.", "The admin-only reference is {{community.admin-secret.secret_value}}." ].join("\n")); fs.writeFileSync(currencyKnowledgePath, [ "---", "id: community.currency", "title: Community currency", "scope: community", "status: active", "priority: 5", "visibility: user", "tags: currency, coins", "primary_name: coins", "---", "# Coins", "The community currency is coins, not points." ].join("\n")); fs.writeFileSync(correctionKnowledgePath, [ "---", "id: correction.currency-name", "title: Currency correction", "scope: corrections", "status: active", "priority: 10", "visibility: user", "tags: currency", "---", "# Currency name", "Always call the currency coins." ].join("\n")); fs.writeFileSync(path.join(tempRoot, "knowledge", "community", "admin-secret.md"), [ "---", "id: community.admin-secret", "title: Admin secret", "scope: community", "status: active", "visibility: admin", "secret_value: hidden-admin-context", "---", "# Secret", "Only admins should retrieve this." ].join("\n")); fs.writeFileSync(path.join(tempRoot, "knowledge", "community", "people.md"), [ "---", "id: community.people", "title: Community people", "scope: community", "status: active", "priority: 20", "visibility: user", "tags: people, identity, ookamikuntv, jenni", "---", "# Community people", "## OokamiKunTV", "OokamiKunTV is a known community contact in Lumi's local community knowledge." ].join("\n")); const fileEntries = loadKnowledgeEntries(tempRoot); assert.equal(fileEntries.length, 5); assert(fileEntries.every((item) => item.chunks.length >= 1)); const userPlaceholders = listKnowledgePlaceholders({ user, rootDir: tempRoot, includeHidden: true }); assert(userPlaceholders.includes("{{community.currency.primary_name}}")); assert.equal(userPlaceholders.includes("{{community.currency.scope}}"), false); assert.equal(userPlaceholders.includes("{{plugins.plugin-throne-wishlist.scope}}"), false); assert.equal(userPlaceholders.includes("{{community.admin-secret.secret_value}}"), false); const adminPlaceholders = listKnowledgePlaceholders({ user: admin, rootDir: tempRoot, includeHidden: true }); assert(adminPlaceholders.includes("{{community.admin-secret.secret_value}}")); const currencyResults = searchFileKnowledge({ query: "currency coins", user, rootDir: tempRoot, limit: 5 }); assert.equal(currencyResults[0].id, "correction.currency-name"); assert(currencyResults[0].source_metadata.path.endsWith("corrections/currency.md")); assert(currencyResults[0].source_metadata.heading); assert(Number.isFinite(currencyResults[0].source_metadata.score)); const identityResults = searchFileKnowledge({ query: "Who is OokamiKunTV?", user, rootDir: tempRoot, limit: 5 }); assert.equal(identityResults[0].id, "community.people"); assert(identityResults[0].facts.includes("OokamiKunTV is a known community contact")); const commandUserResult = searchFileKnowledge({ query: "commands", user, rootDir: tempRoot, limit: 5 })[0]; assert(commandUserResult.facts.includes("The public currency is coins.")); assert(commandUserResult.facts.includes("[missing OKF reference]")); const commandAdminResult = searchFileKnowledge({ query: "commands", user: admin, rootDir: tempRoot, limit: 5 })[0]; assert(commandAdminResult.facts.includes("The admin-only reference is hidden-admin-context.")); assert.equal(searchFileKnowledge({ query: "secret", user, rootDir: tempRoot, limit: 5 }).length, 0); assert.equal(searchFileKnowledge({ query: "secret", user: admin, rootDir: tempRoot, limit: 5 })[0].id, "community.admin-secret"); const savedCommunity = saveCommunityKnowledgeFile(tempRoot, { title: "Community roles", slug: "community-roles", id: "community.roles", category: "Community", visibility: "user", status: "active", priority: "7", tags: "roles, community", body: "# Roles\nCommunity roles are documented here." }); assert.equal(savedCommunity.id, "community.roles"); assert.equal(getCommunityKnowledgeFile(tempRoot, "community-roles").title, "Community roles"); assert(listCommunityKnowledgeFiles(tempRoot).some((item) => item.slug === "community-roles")); const renamedCommunity = saveCommunityKnowledgeFile(tempRoot, { existing_slug: "community-roles", title: "Community role guide", slug: "role-guide", id: "community.roles", category: "Community", visibility: "user", status: "active", tags: "roles", body: "# Roles\nRenamed community role guide." }); assert.equal(renamedCommunity.slug, "role-guide"); assert(fs.existsSync(path.join(tempRoot, "knowledge", "community", "role-guide.md"))); assert.equal(fs.existsSync(path.join(tempRoot, "knowledge", "community", "community-roles.md")), false); fs.writeFileSync(path.join(tempRoot, "knowledge", "community", "generated.md"), [ "---", "id: community.generated", "title: Generated community file", "scope: community", "generated: true", "editable: false", "---", "# Generated" ].join("\n")); assert.throws(() => saveCommunityKnowledgeFile(tempRoot, { existing_slug: "community-generated", title: "Blocked", body: "# Blocked" }), /not editable/); const savedCorrection = saveCorrectionKnowledgeFile(tempRoot, { title: "Feedback correction", slug: "feedback-correction", source_feedback_id: "feedback-1", source_feedback_url: "/admin/feedback?feedback=feedback-1", visibility: "user", priority: "120", body: "# Feedback correction\nUse the corrected behavior from reviewed feedback." }); assert.equal(savedCorrection.id, "correction.feedback-correction"); assert.equal(savedCorrection.path, "knowledge/corrections/feedback-correction.md"); assert.equal(savedCorrection.frontmatter.source_feedback_id, "feedback-1"); assert.equal(searchFileKnowledge({ query: "corrected behavior", user, rootDir: tempRoot, limit: 5 })[0].id, "correction.feedback-correction"); fs.writeFileSync(currencyKnowledgePath, [ "---", "id: community.currency", "title: Community currency", "scope: community", "status: active", "priority: 5", "visibility: user", "tags: currency, coins", "primary_name: gold stars", "---", "# Coins", "The community currency is gold stars, not points." ].join("\n")); assert(searchFileKnowledge({ query: "commands", user, rootDir: tempRoot, limit: 5 })[0].facts.includes("The public currency is gold stars.")); fs.rmSync(correctionKnowledgePath, { force: true }); assert.equal(searchFileKnowledge({ query: "currency coins", user, rootDir: tempRoot, limit: 5 }).some((item) => item.id === "correction.currency-name"), false); fs.rmSync(tempRoot, { recursive: true, force: true }); const migrationRoot = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-okf-migrate-")); ensureKnowledgeDirs(migrationRoot); const legacyPlaceholderPath = path.join(migrationRoot, "knowledge", "community", "legacy.md"); fs.writeFileSync(legacyPlaceholderPath, [ "---", "id: community.legacy", "title: Legacy placeholder", "scope: community", "status: active", "visibility: user", "---", "# Legacy", "The old token is {community.currency.primary_name}.", "The new token stays {{community.currency.primary_name}}." ].join("\n")); const migratedPlaceholders = migrateSingleBracePlaceholders(migrationRoot); assert.equal(migratedPlaceholders.changed, 1); const migratedPlaceholderContent = fs.readFileSync(legacyPlaceholderPath, "utf8"); assert(migratedPlaceholderContent.includes("The old token is {{community.currency.primary_name}}.")); assert(migratedPlaceholderContent.includes("The new token stays {{community.currency.primary_name}}.")); fs.rmSync(migrationRoot, { recursive: true, force: true }); const generatedRoot = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-okf-generated-")); fs.mkdirSync(path.join(generatedRoot, "plugins", "test-plugin"), { recursive: true }); fs.writeFileSync(path.join(generatedRoot, "package.json"), JSON.stringify({ name: "lumi-test", version: "1.2.3", description: "Generated test core." }, null, 2)); fs.writeFileSync(path.join(generatedRoot, "README.md"), "# Lumi Test\n\nGenerated README."); fs.mkdirSync(path.join(generatedRoot, "src", "web"), { recursive: true }); fs.writeFileSync(path.join(generatedRoot, "src", "web", "server.js"), "app.get('/admin/test', handler);\napp.post('/api/test', handler);\n"); fs.writeFileSync(path.join(generatedRoot, "plugins", "test-plugin", "plugin.json"), JSON.stringify({ id: "test-plugin", name: "Test Plugin", version: "0.0.1", description: "Generated plugin knowledge." }, null, 2)); fs.writeFileSync(path.join(generatedRoot, "plugins", "test-plugin", "index.js"), [ "router.get('/settings', handler);", "commandRouter.registerCommands('test-plugin', [{ trigger: 'test', description: 'Test command.' }]);" ].join("\n")); const generated = generateKnowledgeFiles(generatedRoot); const normalizedGenerated = generated.map((file) => file.replace(/\\/g, "/")); assert(normalizedGenerated.some((file) => file.endsWith("knowledge/core/lumi-core.md"))); assert(normalizedGenerated.some((file) => file.endsWith("knowledge/plugins/test-plugin.md"))); const generatedCore = fs.readFileSync(path.join(generatedRoot, "knowledge", "core", "lumi-core.md"), "utf8"); assert(generatedCore.includes("GET /admin/test")); assert(generatedCore.includes("## Route Reference")); assert(generatedCore.includes("### GET /admin/test")); assert(generatedCore.includes("Response format:")); const generatedPlugin = fs.readFileSync(path.join(generatedRoot, "knowledge", "plugins", "test-plugin.md"), "utf8"); assert(generatedPlugin.includes("Test Plugin")); assert(generatedPlugin.includes("### GET /plugins/test-plugin/settings")); assert(generatedPlugin.includes("Inputs:")); assert(generatedPlugin.includes("test")); fs.writeFileSync(path.join(generatedRoot, "knowledge", "plugins", "test-plugin.md"), [ "---", "id: plugin.test-plugin", "title: Manual Test Plugin", "generated: false", "editable: true", "---", "# Manual" ].join("\n")); generateKnowledgeFiles(generatedRoot); assert(fs.readFileSync(path.join(generatedRoot, "knowledge", "plugins", "test-plugin.md"), "utf8").includes("Manual Test Plugin")); fs.rmSync(generatedRoot, { recursive: true, force: true }); const grant = listPermissions(db).find((row) => row.user_id === "editor-1" && !row.revoked_at); revokePermission(db, grant.id, admin); assert.equal(accessForUser(db, editor).canEdit, false); db.close(); console.log("OKF plugin verification passed.");