From b27f7d7e954a9cf79ea161793c702a3c53664d09 Mon Sep 17 00:00:00 2001 From: Peter Kirkham Date: Wed, 8 Apr 2026 21:59:58 +0100 Subject: [PATCH] feat: improve tests, fix cache and fix package --- packages/enricher/package.json | 13 +- packages/enricher/src/detector.test.ts | 164 ++++++++++++++++++++++++ packages/enricher/src/parser-manager.ts | 11 ++ packages/enricher/tsup.config.ts | 7 +- 4 files changed, 193 insertions(+), 2 deletions(-) diff --git a/packages/enricher/package.json b/packages/enricher/package.json index 099fa0a82..208b86fda 100644 --- a/packages/enricher/package.json +++ b/packages/enricher/package.json @@ -7,6 +7,18 @@ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" + }, + "./classification": { + "types": "./dist/flag-classification.d.ts", + "import": "./dist/flag-classification.js" + }, + "./stale-flags": { + "types": "./dist/stale-flags.d.ts", + "import": "./dist/stale-flags.js" + }, + "./types": { + "types": "./dist/types.d.ts", + "import": "./dist/types.js" } }, "scripts": { @@ -28,7 +40,6 @@ }, "files": [ "dist/**/*", - "src/**/*", "grammars/**/*" ] } diff --git a/packages/enricher/src/detector.test.ts b/packages/enricher/src/detector.test.ts index 3d8e4ddd0..096bd19b1 100644 --- a/packages/enricher/src/detector.test.ts +++ b/packages/enricher/src/detector.test.ts @@ -447,4 +447,168 @@ describeWithGrammars("PostHogDetector", () => { ]); }); }); + + // ═══════════════════════════════════════════════════ + // Python — additional findPostHogCalls / findInitCalls + // ═══════════════════════════════════════════════════ + + describe("Python — findPostHogCalls (capture)", () => { + test("detects capture with positional event arg", async () => { + const code = `posthog.capture('user_id', 'purchase')`; + const calls = await detector.findPostHogCalls(code, "python"); + const capture = calls.find( + (c) => c.method === "capture" && c.key === "purchase", + ); + expect(capture).toBeDefined(); + }); + + test("detects flag method get_feature_flag", async () => { + const code = `posthog.get_feature_flag('my-flag')`; + const calls = await detector.findPostHogCalls(code, "python"); + expect(simpleCalls(calls)).toEqual([ + { line: 0, method: "get_feature_flag", key: "my-flag" }, + ]); + }); + }); + + describe("Python — findInitCalls", () => { + test("detects positional constructor Posthog('phc_token')", async () => { + const code = `Posthog('phc_token')`; + const inits = await detector.findInitCalls(code, "python"); + expect(inits).toHaveLength(1); + expect(inits[0].token).toBe("phc_token"); + }); + + test("detects keyword constructor with api_key and host", async () => { + const code = `Posthog(api_key='phc_token', host='https://app.posthog.com')`; + const inits = await detector.findInitCalls(code, "python"); + expect(inits).toHaveLength(1); + expect(inits[0].token).toBe("phc_token"); + expect(inits[0].apiHost).toBe("https://app.posthog.com"); + }); + }); + + // ═══════════════════════════════════════════════════ + // Go — additional findPostHogCalls / findInitCalls + // ═══════════════════════════════════════════════════ + + describe("Go — findPostHogCalls (capture & flags)", () => { + test("detects struct-based Enqueue capture", async () => { + const code = [ + `package main`, + ``, + `func main() {`, + ` client.Enqueue(posthog.Capture{Event: "purchase"})`, + `}`, + ].join("\n"); + const calls = await detector.findPostHogCalls(code, "go"); + const capture = calls.find( + (c) => c.method === "capture" && c.key === "purchase", + ); + expect(capture).toBeDefined(); + }); + + test("detects flag method GetFeatureFlag", async () => { + const code = [ + `package main`, + ``, + `func main() {`, + ` client.GetFeatureFlag(posthog.FeatureFlagPayload{Key: "my-flag"})`, + `}`, + ].join("\n"); + const calls = await detector.findPostHogCalls(code, "go"); + const flag = calls.find( + (c) => c.method === "GetFeatureFlag" && c.key === "my-flag", + ); + expect(flag).toBeDefined(); + }); + }); + + describe("Go — findInitCalls", () => { + test("detects posthog.New constructor", async () => { + const code = [ + `package main`, + ``, + `func main() {`, + ` client := posthog.New("phc_token")`, + `}`, + ].join("\n"); + const inits = await detector.findInitCalls(code, "go"); + expect(inits).toHaveLength(1); + expect(inits[0].token).toBe("phc_token"); + }); + + test("detects posthog.NewWithConfig constructor", async () => { + const code = [ + `package main`, + ``, + `func main() {`, + ` client, _ := posthog.NewWithConfig("phc_token", posthog.Config{Endpoint: "https://app.posthog.com"})`, + `}`, + ].join("\n"); + const inits = await detector.findInitCalls(code, "go"); + expect(inits).toHaveLength(1); + expect(inits[0].token).toBe("phc_token"); + expect(inits[0].apiHost).toBe("https://app.posthog.com"); + }); + }); + + // ═══════════════════════════════════════════════════ + // Ruby — additional findPostHogCalls / findInitCalls + // ═══════════════════════════════════════════════════ + + describe("Ruby — findPostHogCalls (capture & flags)", () => { + test("detects capture with keyword args", async () => { + const code = `client.capture(distinct_id: 'user', event: 'purchase')`; + const calls = await detector.findPostHogCalls(code, "ruby"); + const capture = calls.find( + (c) => c.method === "capture" && c.key === "purchase", + ); + expect(capture).toBeDefined(); + }); + + test("detects flag method get_feature_flag", async () => { + const code = `client.get_feature_flag('my-flag')`; + const calls = await detector.findPostHogCalls(code, "ruby"); + const flag = calls.find( + (c) => c.method === "get_feature_flag" && c.key === "my-flag", + ); + expect(flag).toBeDefined(); + }); + }); + + describe("Ruby — findInitCalls", () => { + test("detects PostHog::Client.new constructor", async () => { + const code = `client = PostHog::Client.new(api_key: 'phc_token')`; + const inits = await detector.findInitCalls(code, "ruby"); + expect(inits).toHaveLength(1); + expect(inits[0].token).toBe("phc_token"); + }); + }); + + // ═══════════════════════════════════════════════════ + // Negative / edge cases + // ═══════════════════════════════════════════════════ + + describe("Negative / edge cases", () => { + test("unsupported language returns empty arrays", async () => { + const code = `posthog.capture('event')`; + const calls = await detector.findPostHogCalls(code, "haskell"); + const inits = await detector.findInitCalls(code, "haskell"); + expect(calls).toEqual([]); + expect(inits).toEqual([]); + }); + + test("non-PostHog client names are ignored", async () => { + const code = `other.capture('event')`; + + const jsCalls = await detector.findPostHogCalls(code, "javascript"); + const pyCalls = await detector.findPostHogCalls(code, "python"); + const rbCalls = await detector.findPostHogCalls(code, "ruby"); + + expect(jsCalls).toEqual([]); + expect(pyCalls).toEqual([]); + expect(rbCalls).toEqual([]); + }); + }); }); diff --git a/packages/enricher/src/parser-manager.ts b/packages/enricher/src/parser-manager.ts index a270042d9..6493f4b8e 100644 --- a/packages/enricher/src/parser-manager.ts +++ b/packages/enricher/src/parser-manager.ts @@ -10,6 +10,7 @@ export class ParserManager { private parser: Parser | null = null; private languages = new Map(); private queryCache = new Map(); + private maxCacheSize = 256; private initPromise: Promise | null = null; private wasmDir = ""; config: DetectionConfig = DEFAULT_CONFIG; @@ -91,11 +92,21 @@ export class ParserManager { const cacheKey = `${lang.toString()}:${queryStr}`; let query = this.queryCache.get(cacheKey); if (query) { + // LRU: move to end by deleting and re-inserting + this.queryCache.delete(cacheKey); + this.queryCache.set(cacheKey, query); return query; } try { query = lang.query(queryStr); + // Evict oldest entry if at capacity + if (this.queryCache.size >= this.maxCacheSize) { + const oldest = this.queryCache.keys().next().value; + if (oldest !== undefined) { + this.queryCache.delete(oldest); + } + } this.queryCache.set(cacheKey, query); return query; } catch (err) { diff --git a/packages/enricher/tsup.config.ts b/packages/enricher/tsup.config.ts index 1465ea247..8dec083f2 100644 --- a/packages/enricher/tsup.config.ts +++ b/packages/enricher/tsup.config.ts @@ -1,7 +1,12 @@ import { defineConfig } from "tsup"; export default defineConfig({ - entry: ["src/index.ts"], + entry: [ + "src/index.ts", + "src/flag-classification.ts", + "src/stale-flags.ts", + "src/types.ts", + ], format: ["esm"], dts: true, sourcemap: true,