mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
* fix(daemon): preserve terminal delivery lifecycle evidence * fix(daemon): preserve per-attempt terminal evidence Generated-By: looper 0.12.0 (runner=fixer, agent=codex) * fix(daemon): preserve terminal attempt and persistence boundaries Generated-By: looper 0.12.0 (runner=fixer, agent=codex) * fix(daemon): settle terminal claims before capture Defer the one-shot run_finished envelope by one check phase so adjacent duplicate and late terminal claims are included in telemetry, and bind the daemon lifecycle type directly to the shared API contract. Generated-By: looper 0.12.0 (runner=fixer, agent=codex) * fix(daemon): preserve replay and teardown evidence Generated-By: looper 0.12.0 (runner=fixer, agent=codex)
195 lines
6.3 KiB
TypeScript
195 lines
6.3 KiB
TypeScript
import { mkdtemp, writeFile } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
const posthogCapture = vi.hoisted(() => vi.fn());
|
|
const posthogGroupIdentify = vi.hoisted(() => vi.fn());
|
|
const posthogShutdown = vi.hoisted(() => vi.fn(async () => undefined));
|
|
const posthogCtor = vi.hoisted(() =>
|
|
vi.fn(function PostHogMock(_key: string, _options?: Record<string, unknown>) {
|
|
return {
|
|
capture: posthogCapture,
|
|
groupIdentify: posthogGroupIdentify,
|
|
on: vi.fn(),
|
|
shutdown: posthogShutdown,
|
|
};
|
|
}),
|
|
);
|
|
|
|
vi.mock('posthog-node', () => ({
|
|
PostHog: posthogCtor,
|
|
}));
|
|
|
|
describe('analytics telemetry environment', () => {
|
|
it('exposes the telemetry env in public analytics config', async () => {
|
|
const { readPublicConfigResponse } = await import('../src/analytics.js');
|
|
|
|
expect(readPublicConfigResponse({
|
|
POSTHOG_KEY: 'phc_test',
|
|
OD_TELEMETRY_ENV: 'local_development',
|
|
})).toMatchObject({
|
|
enabled: true,
|
|
env: 'local_development',
|
|
key: 'phc_test',
|
|
});
|
|
});
|
|
|
|
it('enables GeoIP so daemon events get user country from their real IP', async () => {
|
|
// posthog-node defaults disableGeoip:true (it assumes a datacenter
|
|
// deployment). The daemon runs on the user's own machine, so its
|
|
// ingestion IP is the user's real public IP and country enrichment is
|
|
// accurate — the service must explicitly opt back in or every
|
|
// daemon-emitted event lands in the null-country bucket.
|
|
posthogCtor.mockClear();
|
|
const dataDir = await mkdtemp(path.join(tmpdir(), 'od-analytics-geoip-'));
|
|
const { createAnalyticsService } = await import('../src/analytics.js');
|
|
createAnalyticsService({
|
|
dataDir,
|
|
env: { POSTHOG_KEY: 'phc_test', OD_TELEMETRY_ENV: 'local_development' },
|
|
});
|
|
|
|
expect(posthogCtor).toHaveBeenCalledTimes(1);
|
|
expect(posthogCtor.mock.calls[0]?.[1]).toMatchObject({ disableGeoip: false });
|
|
});
|
|
|
|
it('stamps daemon PostHog captures with env', async () => {
|
|
posthogCapture.mockReset();
|
|
const dataDir = await mkdtemp(path.join(tmpdir(), 'od-analytics-env-'));
|
|
await writeFile(path.join(dataDir, 'app-config.json'), JSON.stringify({
|
|
installationId: 'install-1',
|
|
telemetry: { metrics: true },
|
|
}));
|
|
const { createAnalyticsService } = await import('../src/analytics.js');
|
|
const analytics = createAnalyticsService({
|
|
dataDir,
|
|
env: {
|
|
POSTHOG_KEY: 'phc_test',
|
|
OD_TELEMETRY_ENV: 'local_development',
|
|
},
|
|
});
|
|
|
|
const delivery = await analytics.capture({
|
|
eventName: 'unit_event',
|
|
appVersion: '1.2.3',
|
|
context: {
|
|
deviceId: 'device-1',
|
|
sessionId: 'session-1',
|
|
clientType: 'web',
|
|
locale: 'en',
|
|
requestId: null,
|
|
},
|
|
insertId: 'insert-1',
|
|
properties: {},
|
|
});
|
|
|
|
await vi.waitFor(() => {
|
|
expect(posthogCapture).toHaveBeenCalled();
|
|
});
|
|
expect(posthogCapture.mock.calls[0]?.[0]).toMatchObject({
|
|
event: 'unit_event',
|
|
properties: {
|
|
env: 'local_development',
|
|
},
|
|
});
|
|
expect(delivery).toEqual({
|
|
status: 'queued',
|
|
acknowledgement: 'local_buffer',
|
|
errorType: null,
|
|
});
|
|
});
|
|
|
|
it('updates a workspace group only when analytics consent is enabled', async () => {
|
|
posthogGroupIdentify.mockReset();
|
|
const dataDir = await mkdtemp(path.join(tmpdir(), 'od-analytics-group-'));
|
|
await writeFile(path.join(dataDir, 'app-config.json'), JSON.stringify({
|
|
installationId: 'install-1',
|
|
telemetry: { metrics: true },
|
|
}));
|
|
const { createAnalyticsService } = await import('../src/analytics.js');
|
|
const analytics = createAnalyticsService({
|
|
dataDir,
|
|
env: { POSTHOG_KEY: 'phc_test', OD_TELEMETRY_ENV: 'local_development' },
|
|
});
|
|
|
|
await analytics.identifyGroup({
|
|
context: {
|
|
deviceId: 'device-1',
|
|
sessionId: 'session-1',
|
|
clientType: 'web',
|
|
locale: 'en',
|
|
requestId: null,
|
|
},
|
|
groupType: 'workspace',
|
|
groupKey: 'workspace-1',
|
|
properties: { member_count: 3, project_count: 8, ignored: null },
|
|
});
|
|
|
|
expect(posthogGroupIdentify).toHaveBeenCalledWith({
|
|
groupType: 'workspace',
|
|
groupKey: 'workspace-1',
|
|
distinctId: 'device-1',
|
|
properties: { member_count: 3, project_count: 8 },
|
|
});
|
|
});
|
|
|
|
it('returns a bounded enqueue failure without exposing the thrown error', async () => {
|
|
posthogCapture.mockReset();
|
|
posthogCapture.mockImplementationOnce(() => {
|
|
throw new Error('network path and private details');
|
|
});
|
|
const dataDir = await mkdtemp(path.join(tmpdir(), 'od-analytics-enqueue-failure-'));
|
|
await writeFile(path.join(dataDir, 'app-config.json'), JSON.stringify({
|
|
telemetry: { metrics: true },
|
|
}));
|
|
const { createAnalyticsService } = await import('../src/analytics.js');
|
|
const analytics = createAnalyticsService({
|
|
dataDir,
|
|
env: { POSTHOG_KEY: 'phc_test' },
|
|
});
|
|
|
|
await expect(analytics.capture({
|
|
eventName: 'run_finished',
|
|
appVersion: '1.2.3',
|
|
context: {
|
|
deviceId: 'device-1', sessionId: 'session-1', clientType: 'web',
|
|
locale: 'en', requestId: null,
|
|
},
|
|
insertId: 'insert-1',
|
|
properties: {},
|
|
})).resolves.toEqual({
|
|
status: 'failed', acknowledgement: 'none', errorType: 'enqueue_failed',
|
|
});
|
|
});
|
|
|
|
it('reports metrics opt-out as not expected rather than a delivery failure', async () => {
|
|
posthogCapture.mockReset();
|
|
const dataDir = await mkdtemp(path.join(tmpdir(), 'od-analytics-consent-off-'));
|
|
await writeFile(path.join(dataDir, 'app-config.json'), JSON.stringify({
|
|
telemetry: { metrics: false },
|
|
}));
|
|
const { createAnalyticsService } = await import('../src/analytics.js');
|
|
const analytics = createAnalyticsService({
|
|
dataDir,
|
|
env: { POSTHOG_KEY: 'phc_test' },
|
|
});
|
|
|
|
await expect(analytics.capture({
|
|
eventName: 'run_finished',
|
|
appVersion: '1.2.3',
|
|
context: {
|
|
deviceId: 'device-1', sessionId: 'session-1', clientType: 'web',
|
|
locale: 'en', requestId: null,
|
|
},
|
|
insertId: 'insert-1',
|
|
properties: {},
|
|
})).resolves.toEqual({
|
|
status: 'not_expected',
|
|
acknowledgement: 'none',
|
|
errorType: 'metrics_consent_disabled',
|
|
});
|
|
expect(posthogCapture).not.toHaveBeenCalled();
|
|
});
|
|
});
|