Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .Jules/vantage.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@
## 2026-05-15 - Standardizing Telemetry Resilience and Verifying Infrastructure Mocks | **Learning:** Telemetry must capture the full context of unknown errors via `String(e)` to avoid masking failure signals, and re-exporting mocks requires explicit execution to register in coverage reports. | **Action:** Updated `src/utils/github-releases.ts` to use `String(error)` in catch blocks and added an `exerciseMock` helper to `src/__tests__/mocks/astro-zod.ts` to achieve 100% coverage.

## 2026-05-20 - Hardening Telemetry Verification and Refining Stream Types | **Learning:** Index-based assertions in telemetry tests are brittle; using `.find()` ensures robust verification. Standardizing on `String(error)` across all API catch blocks prevents context loss during failure events. | **Action:** Refactored `chat-api.test.ts` and `github-releases.test.ts` to use search-based spy assertions, and added explicit return types to `chat-stream.ts` to resolve Astro compiler hints.

## 2026-05-25 - Expanding PostHog Visit API Test Guardrails | **Learning:** Testing Cloudflare Workers API endpoints against malformed environment configurations (e.g. invalid host protocols, non-digit project IDs) prevents unhandled exceptions and ensures stable 204 fallbacks in production. | **Action:** Expanded `src/__tests__/visits-api.test.ts` with test coverage for project ID validation, URL parsing, fetch errors, and valid HogQL response mapping.
72 changes: 72 additions & 0 deletions src/__tests__/visits-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,76 @@ describe('visits API', () => {
const response = await GET({} as Parameters<typeof GET>[0]);
expect(response.status).toBe(204);
});

it('returns 204 when POSTHOG_PROJECT_ID is non-numeric', async () => {
vi.spyOn(console, 'error').mockImplementation(() => {});
const bindings = workerEnv as VisitEnv;
bindings.POSTHOG_PERSONAL_API_KEY = 'test-key';
bindings.POSTHOG_PROJECT_ID = 'invalid-project';

const response = await GET({} as Parameters<typeof GET>[0]);
expect(response.status).toBe(204);
});

it('returns 204 when POSTHOG_QUERY_HOST is not https or is invalid', async () => {
vi.spyOn(console, 'error').mockImplementation(() => {});
const bindings = workerEnv as VisitEnv;
bindings.POSTHOG_PERSONAL_API_KEY = 'test-key';
bindings.POSTHOG_PROJECT_ID = '12345';
bindings.POSTHOG_QUERY_HOST = 'http://insecure.example.com';

const response = await GET({} as Parameters<typeof GET>[0]);
expect(response.status).toBe(204);

bindings.POSTHOG_QUERY_HOST = 'not-a-valid-url';
const responseInvalid = await GET({} as Parameters<typeof GET>[0]);
expect(responseInvalid.status).toBe(204);
});

it('returns 204 when the query fetch fails or returns non-ok status', async () => {
vi.spyOn(console, 'error').mockImplementation(() => {});
const bindings = workerEnv as VisitEnv;
bindings.POSTHOG_PERSONAL_API_KEY = 'test-key';

vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response(null, { status: 500 }));

const response = await GET({} as Parameters<typeof GET>[0]);
expect(response.status).toBe(204);
});

it('returns 204 when response json is invalid or zero count', async () => {
vi.spyOn(console, 'error').mockImplementation(() => {});
const bindings = workerEnv as VisitEnv;
bindings.POSTHOG_PERSONAL_API_KEY = 'test-key';

vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response('{invalid-json', { status: 200 }));

const response = await GET({} as Parameters<typeof GET>[0]);
expect(response.status).toBe(204);
});

it('returns 200 with glance JSON when PostHog query succeeds', async () => {
const bindings = workerEnv as VisitEnv;
bindings.POSTHOG_PERSONAL_API_KEY = 'valid-key';

const mockResults = [[100, 50, '2024-01-01T00:00:00Z', 20, 10, 2, 1, 5, 30, 25, 50, 45]];

vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response(JSON.stringify({ results: mockResults }), {
status: 200,
headers: { 'content-type': 'application/json' },
})
);

const response = await GET({} as Parameters<typeof GET>[0]);
expect(response.status).toBe(200);
expect(response.headers.get('content-type')).toBe('application/json');

const data = await response.json();
expect(data).toMatchObject({
pageviews: 100,
uniqueVisitors: 50,
firstSeen: '2024-01-01T00:00:00.000Z',
});
});
});
Loading