Skip to content

Commit e2ed3c6

Browse files
committed
fix(media-use): harden test isolation for telemetry with a real interception seam
resolve.test.mjs already set DO_NOT_TRACK=1 for every spawned resolve.mjs invocation, so local test runs were not actually leaking events. But that safety relied entirely on every call site remembering to set the env var, with no way to prove it. Add a MEDIA_USE_TELEMETRY_HOST override read at the point telemetry.mjs builds its POST url (falling back to the real endpoint when unset), and a test that points it at a local HTTP server to assert a real event is actually intercepted rather than trusting the env-var default alone.
1 parent 2f45c34 commit e2ed3c6

3 files changed

Lines changed: 87 additions & 2 deletions

File tree

skills-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"files": 10
4747
},
4848
"media-use": {
49-
"hash": "b99ad1530966d786",
49+
"hash": "7c3982d07b883fe1",
5050
"files": 122
5151
},
5252
"motion-graphics": {

skills/media-use/scripts/lib/telemetry.mjs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ const POSTHOG_HOST = "https://us.i.posthog.com";
2121
const TIMEOUT_MS = 1500;
2222
let identifiedAccount = false;
2323

24+
// Test-only interception seam: a real HTTP destination a test can point at,
25+
// so a spawned-child test (resolve.test.mjs) can prove track() never reaches
26+
// production rather than trusting DO_NOT_TRACK alone (a future call site or
27+
// test could forget to set that env var). Falls back to the real production
28+
// host whenever unset — production behavior is unchanged.
29+
function posthogHost() {
30+
return process.env.MEDIA_USE_TELEMETRY_HOST || POSTHOG_HOST;
31+
}
32+
2433
/** True when telemetry must NOT be sent (opt-out envs, CI, dev). */
2534
export function optedOut() {
2635
return (
@@ -131,7 +140,7 @@ function showTelemetryNotice() {
131140

132141
async function postBatch(batch) {
133142
try {
134-
await fetch(`${POSTHOG_HOST}/batch/`, {
143+
await fetch(`${posthogHost()}/batch/`, {
135144
method: "POST",
136145
headers: { "Content-Type": "application/json", Connection: "close" },
137146
body: JSON.stringify({ api_key: POSTHOG_API_KEY, batch }),

skills/media-use/scripts/resolve.test.mjs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "node:fs";
1111
import { join } from "node:path";
1212
import { tmpdir } from "node:os";
13+
import { createServer } from "node:http";
1314
import { execFileSync, spawnSync } from "node:child_process";
1415
import { appendRecord, readManifest } from "./lib/manifest.mjs";
1516
import { regenerateIndex } from "./lib/index-gen.mjs";
@@ -687,6 +688,81 @@ test("identical grade resolve hits the project cache without re-freezing", () =>
687688
cleanup();
688689
});
689690

691+
// --- telemetry isolation (U7) ---
692+
693+
// Every other test relies on runResolve/spawnResolve's default DO_NOT_TRACK:
694+
// "1" to keep track() a no-op. That default is fragile on its own (a future
695+
// call site or test could forget to set it), so telemetry.mjs also exposes a
696+
// MEDIA_USE_TELEMETRY_HOST override read at the point the POST URL is built.
697+
// This test proves that seam actually intercepts a real event end to end: a
698+
// resolve that reaches track("media_use_resolve", ...) with tracking allowed
699+
// posts to a local HTTP server instead of production, and the server actually
700+
// receives it (not just "nothing happened because nothing was listening").
701+
test("track() posts to MEDIA_USE_TELEMETRY_HOST when set, proving real interception", async () => {
702+
setup();
703+
const received = [];
704+
const server = createServer((req, res) => {
705+
let body = "";
706+
req.on("data", (chunk) => (body += chunk));
707+
req.on("end", () => {
708+
try {
709+
received.push(JSON.parse(body));
710+
} catch {
711+
// ignore malformed body; assertions below fail on empty `received`
712+
}
713+
res.writeHead(200, { "Content-Type": "application/json" });
714+
res.end("{}");
715+
});
716+
});
717+
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
718+
const port = server.address().port;
719+
const sandboxHome = mkdtempSync(join(tmpdir(), "mu-resolve-telemetry-home-"));
720+
721+
try {
722+
const record = makeRecord({
723+
provenance: { prompt: "telemetry seam test", provider: "test" },
724+
});
725+
appendRecord(tmp, record);
726+
const filePath = join(tmp, record.path);
727+
mkdirSync(join(filePath, ".."), { recursive: true });
728+
writeFileSync(filePath, "telemetry seam audio");
729+
730+
// Override this one invocation's env only: allow tracking (DO_NOT_TRACK
731+
// default flipped off), sandbox HOME so anonymousId()/showTelemetryNotice()
732+
// never touch the real developer machine, and point the host at the local
733+
// server. Every other test in this file keeps its untouched default env.
734+
runResolve(["--type", "bgm", "--intent", "telemetry seam test", "--project", tmp, "--json"], {
735+
env: {
736+
DO_NOT_TRACK: "0",
737+
HYPERFRAMES_NO_TELEMETRY: "0",
738+
CI: "",
739+
NODE_ENV: "test",
740+
HOME: sandboxHome,
741+
MEDIA_USE_TELEMETRY_HOST: `http://127.0.0.1:${port}`,
742+
},
743+
});
744+
745+
// runResolve blocks synchronously (execFileSync) until the child exits, which
746+
// pauses this process's own event loop for that whole span — the child's
747+
// request to our local server sits accepted-but-unprocessed in the kernel
748+
// backlog until control returns here. Poll briefly to let the event loop
749+
// drain it rather than asserting before the server has had a turn to run.
750+
for (let i = 0; i < 100 && received.length === 0; i++) {
751+
await new Promise((resolve) => setTimeout(resolve, 20));
752+
}
753+
} finally {
754+
await new Promise((resolve) => server.close(resolve));
755+
rmSync(sandboxHome, { recursive: true, force: true });
756+
cleanup();
757+
}
758+
759+
assert.ok(received.length > 0, "expected the local telemetry server to receive a POST");
760+
const resolveEvent = received[0].batch.find((event) => event.event === "media_use_resolve");
761+
assert.ok(resolveEvent, "expected a media_use_resolve event in the intercepted batch");
762+
assert.equal(resolveEvent.properties.provider, "test");
763+
assert.equal(resolveEvent.properties.type, "bgm");
764+
});
765+
690766
// --- run ---
691767

692768
async function main() {

0 commit comments

Comments
 (0)