import { describe, test, afterEach } from "node:test"; import assert from "node:assert/strict"; import { mkdtempSync, rmSync, mkdirSync, writeFileSync, existsSync } from "node:fs"; import { join, resolve } from "node:path"; import { tmpdir } from "node:os"; import { getAnalyticsRoot, getDataRoot, findPluginRoot } from "../src/utils/storage.js"; // M0: the data root moved OUT of the plugin tree to the external per-user dir // (~/.claude/linkedin-studio), behind getDataRoot(). getAnalyticsRoot() is now a // thin alias = getDataRoot("analytics") that still honors the deprecated // ANALYTICS_ROOT env for back-compat (D4). findPluginRoot stays — it now locates // bundled read-only assets only (commit 798484b, the fresh-clone crash fix). // // The default must be stubbed via HOME + LINKEDIN_STUDIO_DATA save/restore so it // never couples to the dev's real $HOME. Mirrors the .mjs twin test // (hooks/scripts/__tests__/data-root.test.mjs) — keep them in sync. describe("data root resolution", () => { let tempDir: string | undefined; const saved = { ANALYTICS_ROOT: process.env.ANALYTICS_ROOT, LINKEDIN_STUDIO_DATA: process.env.LINKEDIN_STUDIO_DATA, HOME: process.env.HOME, USERPROFILE: process.env.USERPROFILE, }; afterEach(() => { if (tempDir && existsSync(tempDir)) { rmSync(tempDir, { recursive: true, force: true }); } tempDir = undefined; for (const [k, v] of Object.entries(saved)) { if (v === undefined) delete process.env[k]; else process.env[k] = v; } }); describe("findPluginRoot", () => { test("walks up to the directory containing .claude-plugin/plugin.json", () => { tempDir = mkdtempSync(join(tmpdir(), "plugin-root-")); mkdirSync(join(tempDir, ".claude-plugin"), { recursive: true }); writeFileSync(join(tempDir, ".claude-plugin", "plugin.json"), "{}"); // Mimic the real depth: scripts/analytics/src/utils const start = join(tempDir, "scripts", "analytics", "src", "utils"); mkdirSync(start, { recursive: true }); assert.equal(findPluginRoot(start), tempDir); }); test("returns null when no marker exists up-tree", () => { tempDir = mkdtempSync(join(tmpdir(), "no-marker-")); const start = join(tempDir, "a", "b", "c"); mkdirSync(start, { recursive: true }); assert.equal(findPluginRoot(start), null); }); }); describe("getDataRoot", () => { test("external default (HOME-stubbed): getDataRoot('analytics') = HOME/.claude/linkedin-studio/analytics", () => { process.env.HOME = "/home/tester"; delete process.env.USERPROFILE; delete process.env.LINKEDIN_STUDIO_DATA; assert.equal( getDataRoot("analytics"), join("/home/tester", ".claude", "linkedin-studio", "analytics"), ); }); test("LINKEDIN_STUDIO_DATA override wins", () => { process.env.LINKEDIN_STUDIO_DATA = "/tmp/lis-data"; assert.equal(getDataRoot("analytics"), join("/tmp/lis-data", "analytics")); }); }); describe("getAnalyticsRoot", () => { test("default (no env) now resolves to the EXTERNAL data dir, not in-plugin assets/analytics", () => { process.env.HOME = "/home/tester"; delete process.env.USERPROFILE; delete process.env.LINKEDIN_STUDIO_DATA; delete process.env.ANALYTICS_ROOT; assert.equal( getAnalyticsRoot(), join("/home/tester", ".claude", "linkedin-studio", "analytics"), ); }); test("honors deprecated ANALYTICS_ROOT alias (resolved env path) for back-compat", () => { tempDir = mkdtempSync(join(tmpdir(), "analytics-root-")); process.env.ANALYTICS_ROOT = tempDir; assert.equal(getAnalyticsRoot(), resolve(tempDir)); }); test("LINKEDIN_STUDIO_DATA drives the analytics root when ANALYTICS_ROOT is unset", () => { delete process.env.ANALYTICS_ROOT; process.env.LINKEDIN_STUDIO_DATA = "/tmp/lis-data"; assert.equal(getAnalyticsRoot(), join("/tmp/lis-data", "analytics")); }); }); });