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, findPluginRoot } from "../src/utils/storage.js"; // Regression lock for the fresh-clone / foreign-CWD analytics root resolution. // The root must anchor on the .claude-plugin/plugin.json marker (correct by // construction), NOT on a fragile count of "../" segments that silently breaks // if the source layout is ever moved. describe("analytics root resolution", () => { let tempDir: string | undefined; const savedEnv = process.env.ANALYTICS_ROOT; afterEach(() => { if (tempDir && existsSync(tempDir)) { rmSync(tempDir, { recursive: true, force: true }); } tempDir = undefined; if (savedEnv === undefined) { delete process.env.ANALYTICS_ROOT; } else { process.env.ANALYTICS_ROOT = savedEnv; } }); 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("getAnalyticsRoot", () => { test("honors ANALYTICS_ROOT override (resolved env path)", () => { tempDir = mkdtempSync(join(tmpdir(), "analytics-root-")); process.env.ANALYTICS_ROOT = tempDir; assert.equal(getAnalyticsRoot(), resolve(tempDir)); }); test("default (no env) anchors on the plugin dir, not scripts/analytics/assets", () => { delete process.env.ANALYTICS_ROOT; const root = getAnalyticsRoot(); const suffix = join("assets", "analytics"); assert.ok(root.endsWith(suffix), `expected to end with ${suffix}, got ${root}`); // The parent of assets/analytics must be the real plugin root (holds the marker), // proving the root is NOT scripts/analytics/assets/analytics. const pluginRoot = root.slice(0, root.length - (suffix.length + 1)); assert.ok( existsSync(join(pluginRoot, ".claude-plugin", "plugin.json")), `plugin marker missing under resolved root parent: ${pluginRoot}`, ); assert.ok( !pluginRoot.endsWith(join("scripts", "analytics")), `root wrongly anchored under scripts/analytics: ${pluginRoot}`, ); }); }); });