Files
Arcrun/kbdb/tests/embed-selftest.test.ts

125 lines
5.1 KiB
TypeScript
Raw Permalink 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.
import { describe, it, expect } from 'vitest';
import { embedSelfTest } from '../src/embed';
import type { Bindings, Entry } from '../src/types';
// ── Minimal in-memory fakes ───────────────────────────────────────────────
// embedSelfTest issues exactly one DB statement:
// SELECT * FROM entries WHERE is_embedded = 1 AND content <> '' [AND owner_id = ?]
// ORDER BY updated_at DESC LIMIT 1
// The fake's `first()` filters the in-memory store accordingly and returns the
// last match (proxy for "ORDER BY updated_at DESC LIMIT 1" given store insertion order).
function mkEntry(id: string, content: string, ownerId = 'leo', is_embedded = 1): Entry {
return {
id, content, entry_type: 'block', owner_id: ownerId, parent_id: null, page_name: null,
refs_json: '[]', tags_json: '[]', task_status: null, content_hash: null, is_embedded,
confidence: null, metadata_json: JSON.stringify({ embed: true }), created_at: 1, updated_at: 1,
};
}
function makeFakeDB(store: Entry[]) {
const prepare = (_sql: string) => {
let bound: unknown[] = [];
const stmt = {
bind(...args: unknown[]) { bound = args; return stmt; },
async first<T>() {
const ownerId = bound.length > 0 ? String(bound[0]) : undefined;
const rows = store.filter(
(e) => e.is_embedded === 1 && (e.content ?? '').trim() !== '' && (!ownerId || e.owner_id === ownerId),
);
return (rows.length > 0 ? rows[rows.length - 1] : null) as unknown as T;
},
async all<T>() { return { results: [] as T[] }; },
async run() { return { success: true }; },
};
return stmt;
};
return { prepare } as unknown as D1Database;
}
function makeEnv(
store: Entry[],
opts: { withBindings?: boolean; matches?: { id: string; score: number }[] } = {},
): Bindings {
const withBindings = opts.withBindings ?? true;
return {
DB: makeFakeDB(store),
ENVIRONMENT: 'test',
...(withBindings
? {
AI: { async run() { return { data: [[0.1, 0.2, 0.3]] }; } },
VECTORIZE: { async query() { return { matches: opts.matches ?? [] }; } },
}
: {}),
} as unknown as Bindings;
}
describe('embedSelfTest(檢修孔:卡片自我查詢,驗證 index 真的可用)', () => {
it('module off → enabled:false, tested:false, passed:null(誠實不假綠)', async () => {
const env = makeEnv([mkEntry('e1', 'hello')], { withBindings: false });
const r = await embedSelfTest(env);
expect(r.enabled).toBe(false);
expect(r.tested).toBe(false);
expect(r.passed).toBeNull();
expect(typeof r.note).toBe('string');
});
it('沒有任何已嵌入卡片 → tested:false, passed:null(非失敗,只是還沒東西可測)', async () => {
const env = makeEnv([]);
const r = await embedSelfTest(env);
expect(r.enabled).toBe(true);
expect(r.tested).toBe(false);
expect(r.passed).toBeNull();
});
it('自我查詢能搜到自己 → passed:true', async () => {
const store = [mkEntry('e1', 'doorbell workflow content')];
const env = makeEnv(store, { matches: [{ id: 'e1', score: 0.9 }] });
const r = await embedSelfTest(env);
expect(r.enabled).toBe(true);
expect(r.tested).toBe(true);
expect(r.passed).toBe(true);
});
it('自我查詢搜不到自己 → passed:falseArcrun#11 那種「嵌了但查不到」故障模式)', async () => {
const store = [mkEntry('e1', 'doorbell workflow content')];
const env = makeEnv(store, { matches: [{ id: 'some-other-id', score: 0.5 }] });
const r = await embedSelfTest(env);
expect(r.enabled).toBe(true);
expect(r.tested).toBe(true);
expect(r.passed).toBe(false);
});
it('向量化本身失敗(AI 額度用完)→ tested:falsenote 說明故障,不 throw 也不假 passed', async () => {
const store = [mkEntry('e1', '取樣內容', 'o1')];
const env = {
DB: makeFakeDB(store),
ENVIRONMENT: 'test',
AI: { async run() { throw new Error('3040: daily limit'); } },
VECTORIZE: { async query() { return { matches: [] }; } },
} as unknown as Bindings;
const r = await embedSelfTest(env, { owner_id: 'o1' });
expect(r.enabled).toBe(true);
expect(r.tested).toBe(false);
expect(r.passed).toBeNull();
expect(r.note).toContain('沒跑成');
});
it('依 owner_id 隔離:別的租戶的已嵌入卡片不會被拿來測', async () => {
const store = [mkEntry('e1', 'content', 'other-tenant')];
const env = makeEnv(store, { matches: [] });
const r = await embedSelfTest(env, { owner_id: 'leo' });
expect(r.enabled).toBe(true);
expect(r.tested).toBe(false);
expect(r.passed).toBeNull();
});
it('回應絕不含卡片內容或 entry id(隱私紅線)', async () => {
const store = [mkEntry('e1', 'this is the secret card body, must never leak')];
const env = makeEnv(store, { matches: [{ id: 'e1', score: 0.9 }] });
const r = await embedSelfTest(env);
const json = JSON.stringify(r);
expect(json).not.toContain('e1');
expect(json).not.toContain('secret card body');
});
});