Files
Arcrun/console-ui/tests/portal-source-href.test.mjs
T
uncle6me-web c662af7e2a fix(portal): 溯源連結兼容 kb:// 與兩種 gitea scheme(daemon-beta t21)
新 ingest 鏈(rag_ingest_card/rag_ingest_direct)寫的 metadata source 是
kb://<path>,但 portal 溯源只認 gitea://——B4 部好溯源後一經重灌即再斷鏈。

- gitea://<path>(舊 rag_ingest v2):{base}/<path>,行為一字不變(不回歸)
- gitea:<org/repo>@<path>(km-wiki-ingest 實形):
  {base}/<org/repo>/src/branch/main/<path>,#segNN 錨點捨棄
- kb://<path>(新 ingest 鏈實形):雲端沒有對應網頁,不造死鏈——
  卡片詳頁顯示本地路徑文字+「在你的同步資料夾內」標註(沿用既有
  sourceWebBase 未設時的純文字降級樣式);AI 出處列同標註
- 測試:console-ui/tests/portal-source-href.test.mjs(node --test,
  抽真身 srcHref/srcLocalPath 求值,6 case 全綠);package.json 加 test script
- public/portal/index.html 用 ARCRUN_API_BASE=https://cypher.arcrun.dev
  重 build(與既有產物同參數,diff 僅本次 scheme 改動)

實形出處:kb:// = arcrun-rag workflows/rag-ingest.yaml:115('kb://'+path);
gitea:@ = registry/examples/km-wiki-ingest/workflow.yaml:276。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:38:39 +08:00

109 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Portal 溯源 scheme 測試(daemon-beta t21 — kb:// vs gitea:// 斷鏈地雷)
*
* 驗的是 src/portal-ui.ts 內嵌客戶端 JS 的 srcHref / srcLocalPath
* gitea://<path> → {base}/<path>(舊 rag_ingest v2 實形,行為不回歸)
* gitea:<org/repo>@<path> → {base}/<org/repo>/src/branch/main/<path>km-wiki-ingest 實形,
* 見 registry/examples/km-wiki-ingest/workflow.yaml `gitea:${repo}@${relPath}`
* kb://<path> → 不造連結(雲端無對應網頁),srcLocalPath 給本地路徑文字,
* UI 標「在你的同步資料夾內」(arcrun-rag rag-ingest.yaml `'kb://' + path`
*
* 測法:從 portal-ui.ts 的 template literal 抽出三個函式原始碼(brace 平衡掃描),
* 還原 template 跳脫(`\\` → `\`)後用 new Function 求值——測的就是真正會被
* build 進 public/portal/index.html 的那份 JS,零外部依賴(console-ui 哲學)。
*/
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const HERE = dirname(fileURLToPath(import.meta.url));
const SRC = readFileSync(join(HERE, '..', 'src', 'portal-ui.ts'), 'utf8');
/** 從原始碼抽出 `function <name>(...) { ... }` 全文(brace 平衡;函式體無字串內大括號)。 */
function extractFn(name) {
const start = SRC.indexOf(`function ${name}(`);
assert.ok(start >= 0, `portal-ui.ts 找不到 function ${name}`);
const braceStart = SRC.indexOf('{', SRC.indexOf(')', start));
let i = braceStart + 1;
let depth = 1;
while (i < SRC.length && depth > 0) {
if (SRC[i] === '{') depth++;
else if (SRC[i] === '}') depth--;
i++;
}
// template literal 內的 `\\` 在產出 HTML 時是 `\`——還原成瀏覽器實際跑的 JS
return SRC.slice(start, i).replace(/\\\\/g, '\\');
}
/** 用指定 SOURCE_WEB_BASE 建出真正的 srcHref / srcLocalPath。 */
function makeFns(base) {
const code = [extractFn('encSrcPath'), extractFn('srcHref'), extractFn('srcLocalPath')].join('\n');
return new Function('SOURCE_WEB_BASE', `${code}\nreturn { srcHref, srcLocalPath };`)(base);
}
const BASE = 'https://gitea.example.com';
test('gitea://<path>(舊 rag_ingest v2)— 現行為不回歸:base + path、#n 錨點捨棄', () => {
const { srcHref } = makeFns(BASE + '/'); // 尾斜線要被吃掉
assert.equal(srcHref('gitea://system-dev/wiki/foo.md'), `${BASE}/system-dev/wiki/foo.md`);
assert.equal(srcHref('gitea://system-dev/wiki/foo.md#3'), `${BASE}/system-dev/wiki/foo.md`);
// 中文路徑逐段 encode
assert.equal(srcHref('gitea://docs/員工手冊.md'), `${BASE}/docs/${encodeURIComponent('員工手冊.md')}`);
});
test('gitea:<org/repo>@<path>km-wiki-ingest)— base/<org/repo>/src/branch/main/<path>', () => {
const { srcHref } = makeFns(BASE);
assert.equal(
srcHref('gitea:Leo/notes@system-dev/wiki/cards/foo.md'),
`${BASE}/Leo/notes/src/branch/main/system-dev/wiki/cards/foo.md`
);
// 分段錨點(#segNN)捨棄
assert.equal(
srcHref('gitea:Leo/notes@wiki/foo.md#seg02'),
`${BASE}/Leo/notes/src/branch/main/wiki/foo.md`
);
// 中文路徑逐段 encode
assert.equal(
srcHref('gitea:Leo/notes@docs/員工手冊.md'),
`${BASE}/Leo/notes/src/branch/main/docs/${encodeURIComponent('員工手冊.md')}`
);
// 缺 @ 或畸形 → 不造連結
assert.equal(srcHref('gitea:Leo/notes'), '');
assert.equal(srcHref('gitea:@wiki/foo.md'), '');
assert.equal(srcHref('gitea:Leo/notes@'), '');
});
test('kb://<path>(新 ingest 鏈)— 不造死鏈、srcLocalPath 給本地路徑', () => {
const { srcHref, srcLocalPath } = makeFns(BASE);
assert.equal(srcHref('kb://人事/員工特休假規定.md'), '');
assert.equal(srcLocalPath('kb://人事/員工特休假規定.md'), '人事/員工特休假規定.md');
assert.equal(srcLocalPath('kb://foo.md#seg01'), 'foo.md');
// 非 kb:// 不給本地路徑
assert.equal(srcLocalPath('gitea://foo.md'), '');
assert.equal(srcLocalPath('gitea:Leo/notes@foo.md'), '');
assert.equal(srcLocalPath('https://example.com/x'), '');
});
test('SOURCE_WEB_BASE 未設 — 全部 scheme 都不給 href(降級純文字),kb:// 顯示不受影響', () => {
const { srcHref, srcLocalPath } = makeFns('');
assert.equal(srcHref('gitea://wiki/foo.md'), '');
assert.equal(srcHref('gitea:Leo/notes@wiki/foo.md'), '');
assert.equal(srcHref('kb://foo.md'), '');
assert.equal(srcLocalPath('kb://foo.md'), 'foo.md'); // 本地路徑與 base 無關
});
test('未知 scheme — 誠實維持純文字(不猜連結)', () => {
const { srcHref, srcLocalPath } = makeFns(BASE);
assert.equal(srcHref('https://example.com/foo'), '');
assert.equal(srcHref('random-string'), '');
assert.equal(srcLocalPath('random-string'), '');
});
test('UI 有 kb:// 的誠實標註(兩個顯示點:卡片詳頁 srcline + AI 出處列)', () => {
const hits = SRC.split('在你的同步資料夾內').length - 1;
assert.ok(hits >= 2, `「在你的同步資料夾內」標註應出現在 ≥2 個顯示點,實得 ${hits}`);
assert.ok(SRC.includes('srcLocalPath(src)'), '卡片詳頁應走 srcLocalPath 降級');
});