mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
119 lines
4.3 KiB
TypeScript
119 lines
4.3 KiB
TypeScript
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
import { tmpdir } from "os";
|
|
import { join } from "path";
|
|
import { afterEach, expect, test, vi } from "vite-plus/test";
|
|
import { maybeSyncWikiData } from "../src/advisor/sync.ts";
|
|
import { readSkillLock } from "../src/skills/lock.ts";
|
|
|
|
const WIKI_SKILL_NAME = "bailian-docs-llm-wiki";
|
|
const CONTENT_HASH = `sha256:${"a".repeat(64)}`;
|
|
|
|
/** Isolated HOME/XDG/BAILIAN_CONFIG_DIR; agent config-dir overrides cleared for determinism */
|
|
async function inFakeHome(fn: (home: string) => Promise<void>): Promise<void> {
|
|
const saved = {
|
|
HOME: process.env.HOME,
|
|
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
|
|
BAILIAN_CONFIG_DIR: process.env.BAILIAN_CONFIG_DIR,
|
|
CLAUDE_CONFIG_DIR: process.env.CLAUDE_CONFIG_DIR,
|
|
CODEX_HOME: process.env.CODEX_HOME,
|
|
};
|
|
const home = mkdtempSync(join(tmpdir(), "bl-advisor-sync-"));
|
|
process.env.HOME = home;
|
|
process.env.XDG_CONFIG_HOME = join(home, ".config");
|
|
process.env.BAILIAN_CONFIG_DIR = join(home, ".bailian");
|
|
delete process.env.CLAUDE_CONFIG_DIR;
|
|
delete process.env.CODEX_HOME;
|
|
try {
|
|
await fn(home);
|
|
} finally {
|
|
for (const [key, value] of Object.entries(saved)) {
|
|
if (value === undefined) delete process.env[key];
|
|
else process.env[key] = value;
|
|
}
|
|
rmSync(home, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
/** Stub the registry fetch to serve a wiki entry with the given fingerprint */
|
|
function stubRegistryIndex() {
|
|
const fetchMock = vi.fn(async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({
|
|
skills: {
|
|
[WIKI_SKILL_NAME]: { contentHash: CONTENT_HASH, publishedAt: "2026-08-01 10:00:00" },
|
|
},
|
|
}),
|
|
}));
|
|
vi.stubGlobal("fetch", fetchMock);
|
|
return fetchMock;
|
|
}
|
|
|
|
/** Seed what postinstall leaves behind: canonical data + expired state + lock entry WITHOUT links */
|
|
function seedPostinstallState(configDir: string): void {
|
|
const catalogDir = join(configDir, "skills", WIKI_SKILL_NAME);
|
|
mkdirSync(join(catalogDir, "models"), { recursive: true });
|
|
writeFileSync(join(catalogDir, "models", "models.jsonl"), "{}\n");
|
|
writeFileSync(join(catalogDir, "SKILL.md"), "---\nname: wiki\ndescription: docs\n---\n");
|
|
// State older than the 12h throttle so the hash-hit branch is reached
|
|
writeFileSync(
|
|
join(configDir, "wiki-sync-state.json"),
|
|
JSON.stringify({ lastChecked: Date.now() - 13 * 3600_000, contentHash: CONTENT_HASH }),
|
|
);
|
|
writeFileSync(
|
|
join(configDir, "skills", "skill-lock.json"),
|
|
JSON.stringify({
|
|
version: 1,
|
|
skills: {
|
|
[WIKI_SKILL_NAME]: {
|
|
contentHash: CONTENT_HASH,
|
|
installedAt: "2026-08-01T00:00:00.000Z",
|
|
sourceType: "oss",
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
}
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
test("advisor sync: hash-hit backfills fan-out links missing from postinstall lock entry", async () => {
|
|
await inFakeHome(async (home) => {
|
|
seedPostinstallState(process.env.BAILIAN_CONFIG_DIR!);
|
|
mkdirSync(join(home, ".claude"), { recursive: true });
|
|
const fetchMock = stubRegistryIndex();
|
|
|
|
const updated = await maybeSyncWikiData();
|
|
|
|
// Content unchanged → no data update, but the fan-out gap is repaired
|
|
expect(updated).toBe(false);
|
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
const locked = readSkillLock().skills[WIKI_SKILL_NAME];
|
|
expect(locked?.links).toEqual([join(home, ".claude", "skills", WIKI_SKILL_NAME)]);
|
|
|
|
// Second run: fresh throttle + links already recorded → no fetch, no rewrite
|
|
await maybeSyncWikiData();
|
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
test("advisor sync: hash-hit keeps an existing links array untouched", async () => {
|
|
await inFakeHome(async (home) => {
|
|
seedPostinstallState(process.env.BAILIAN_CONFIG_DIR!);
|
|
// Upgrade the lock entry to "already fanned out" shape
|
|
const existingLink = join(home, ".claude", "skills", WIKI_SKILL_NAME);
|
|
const lockPath = join(process.env.BAILIAN_CONFIG_DIR!, "skills", "skill-lock.json");
|
|
const lockContent = JSON.parse(readFileSync(lockPath, "utf-8"));
|
|
lockContent.skills[WIKI_SKILL_NAME].links = [existingLink];
|
|
writeFileSync(lockPath, JSON.stringify(lockContent));
|
|
mkdirSync(join(home, ".claude"), { recursive: true });
|
|
stubRegistryIndex();
|
|
|
|
await maybeSyncWikiData();
|
|
|
|
expect(readSkillLock().skills[WIKI_SKILL_NAME]?.links).toEqual([existingLink]);
|
|
});
|
|
});
|