Skip to content

Commit d0d3155

Browse files
committed
Merge remote-tracking branch 'origin/main' into otelbot/spec-integration-v1.55.0-dev
2 parents 30fdb91 + c48fe2d commit d0d3155

File tree

25 files changed

+1476
-1069
lines changed

25 files changed

+1476
-1069
lines changed

content/en/site/_index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ Tentatively planned content organization:
3232
- [**Build**](./build/) — Tooling, local development setup, CI/CD workflows,
3333
deployment environments, and automation details.
3434
- **Deployment** — Deployment-specific behavior for the OpenTelemetry website.
35-
- **Quality** — Link checking, accessibility standards, tests, review practices,
36-
and other quality-related processes.
35+
- [**Testing**](./testing/) — Link checking, accessibility standards, tests,
36+
review practices, and other quality-related processes.
3737
- **Roadmap** — Milestones, backlog, priorities, technical debt, and
3838
design/implementation decisions.
3939

content/en/site/testing/_index.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
title: Testing
3+
description: Checking and testing strategies, processes, and test pages.
4+
---
5+
6+
This section contains checking and testing strategies, processes, and test pages
7+
used by website tests and deployed live checks.
8+
9+
> This section is under construction.
10+
11+
## Test assertions
12+
13+
### Goal
14+
15+
When a test fails, the output should make it obvious what was being checked and
16+
show a clear diff between actual and expected values, without long hand-written
17+
messages.
18+
19+
For example, avoid:
20+
21+
```js
22+
assert.ok(a === b, `expected ${a} to be ${b}`);
23+
```
24+
25+
Instead favor:
26+
27+
```js
28+
assert.strictEqual(status, expectedStatus, 'HTTP status');
29+
```
30+
31+
### Guidance
32+
33+
The points below use Node's built-in `node:test` runner and `assert` API because
34+
that is what several of our test suites use. The same ideas apply in other test
35+
frameworks: prefer assertions that produce tight diffs, keep failure context
36+
short and specific, and extract shared helpers when the same check repeats.
37+
38+
1. Prefer `assert.strictEqual` over `assert.equal` for primitive checks where
39+
strictness and diff quality matter.
40+
2. Add a short third argument as context, for example `HTTP status`,
41+
`Content-Type`, `Location`, `Request body`.
42+
3. Use `assert.match` when a regular expression can capture the intent more
43+
clearly than `includes` or chained `ok` logic.
44+
4. Put shared assertion helpers in a module imported by the test suites that use
45+
them, colocated with those tests instead of copy-pasted across files. Other
46+
small, test-only utilities can live in the same module when it stays focused
47+
(for example `assertVaryIncludesAccept` in
48+
`netlify/edge-functions/lib/test-helpers.ts`).
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
title: Test pages
3+
---
4+
5+
This section contains low-impact pages reserved for deployed live checks.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
title: HTML-only test page
3+
outputs: [HTML]
4+
---
5+
6+
This is a test page.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
title: Regular test page
3+
---
4+
5+
This is a test page.

netlify/edge-functions/asset-tracking/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ tests.
55

66
It tracks explicit asset-path requests by extension using `context.next()`
77
(currently `*.md` and `*.txt`) and emits GA4 `asset_fetch` events for tracked
8-
`GET` requests regardless of response status.
8+
`GET` requests regardless of response status. Emitted events set `event_emitter`
9+
to `tracking`.
910

1011
If the request includes `X-Asset-Fetch-Ga-Info`, the function treats it as an
1112
internal subrequest and skips tracking. This prevents duplicate events when
1213
`markdown-negotiation` fetches sibling `index.md` assets internally.
1314

15+
Responses also include `X-Asset-Fetch-Ga-Info` for coarse sanity-checking of the
16+
derived GA path and local GA-event candidate/config state.
17+
1418
### Live tests
1519

1620
To run the live checks over a server:

netlify/edge-functions/asset-tracking/index.test.ts

Lines changed: 90 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -14,65 +14,15 @@ import {
1414
ASSET_FETCH_GA_INFO_HEADER,
1515
INTERNAL_ASSET_FETCH_GA_INFO_VALUE,
1616
} from '../lib/ga4-asset-fetch.ts';
17+
import {
18+
assertAssetFetchGa4Event,
19+
createWaitUntilSpy,
20+
firstAssetFetchEvent,
21+
setupGa4CapturingFetchMock,
22+
setupNetlifyEnv,
23+
} from '../lib/test-helpers.ts';
1724
import assetTracking, { shouldTrackAssetFetch } from './index.ts';
1825

19-
function setupNetlifyEnv(t: { after: (fn: () => void) => void }) {
20-
const g = globalThis as Record<string, unknown>;
21-
const originalNetlify = g.Netlify;
22-
t.after(() => {
23-
g.Netlify = originalNetlify;
24-
});
25-
26-
g.Netlify = {
27-
env: {
28-
get: (name: string) => {
29-
if (name === 'HUGO_SERVICES_GOOGLEANALYTICS_ID') return 'G-TEST';
30-
if (name === 'GA4_API_SECRET') return 'secret';
31-
return undefined;
32-
},
33-
},
34-
};
35-
}
36-
37-
function setupFetchMock(t: { after: (fn: () => void) => void }) {
38-
const originalFetch = globalThis.fetch;
39-
t.after(() => {
40-
globalThis.fetch = originalFetch;
41-
});
42-
43-
const ga4Bodies: Record<string, unknown>[] = [];
44-
45-
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
46-
const url =
47-
input instanceof URL
48-
? input.toString()
49-
: typeof input === 'string'
50-
? input
51-
: input.url;
52-
53-
if (url.includes('google-analytics.com')) {
54-
if (init?.body) {
55-
ga4Bodies.push(JSON.parse(init.body as string));
56-
}
57-
return new Response('', { status: 200 });
58-
}
59-
60-
return new Response('unexpected fetch', { status: 500 });
61-
}) as typeof fetch;
62-
63-
return ga4Bodies;
64-
}
65-
66-
function createWaitUntilSpy() {
67-
const promises: Promise<unknown>[] = [];
68-
return {
69-
waitUntil: (p: Promise<unknown>) => {
70-
promises.push(p);
71-
},
72-
flush: () => Promise.all(promises),
73-
};
74-
}
75-
7626
test('shouldTrackAssetFetch accepts GET .md requests', () => {
7727
const request = new Request(
7828
'https://example.com/docs/concepts/resources/index.md',
@@ -82,7 +32,11 @@ test('shouldTrackAssetFetch accepts GET .md requests', () => {
8232
status: 200,
8333
});
8434

85-
assert.equal(shouldTrackAssetFetch(request, response), true);
35+
assert.strictEqual(
36+
shouldTrackAssetFetch(request, response),
37+
true,
38+
'shouldTrackAssetFetch',
39+
);
8640
});
8741

8842
test('shouldTrackAssetFetch accepts GET .txt requests', () => {
@@ -92,7 +46,11 @@ test('shouldTrackAssetFetch accepts GET .txt requests', () => {
9246
status: 200,
9347
});
9448

95-
assert.equal(shouldTrackAssetFetch(request, response), true);
49+
assert.strictEqual(
50+
shouldTrackAssetFetch(request, response),
51+
true,
52+
'shouldTrackAssetFetch',
53+
);
9654
});
9755

9856
test('shouldTrackAssetFetch returns false for internal marked requests', () => {
@@ -109,7 +67,11 @@ test('shouldTrackAssetFetch returns false for internal marked requests', () => {
10967
status: 200,
11068
});
11169

112-
assert.equal(shouldTrackAssetFetch(request, response), false);
70+
assert.strictEqual(
71+
shouldTrackAssetFetch(request, response),
72+
false,
73+
'shouldTrackAssetFetch',
74+
);
11375
});
11476

11577
test('shouldTrackAssetFetch returns false for non-GET methods', () => {
@@ -123,7 +85,11 @@ test('shouldTrackAssetFetch returns false for non-GET methods', () => {
12385
status: 200,
12486
});
12587

126-
assert.equal(shouldTrackAssetFetch(request, response), false);
88+
assert.strictEqual(
89+
shouldTrackAssetFetch(request, response),
90+
false,
91+
'shouldTrackAssetFetch',
92+
);
12793
}
12894
});
12995

@@ -137,7 +103,11 @@ test('shouldTrackAssetFetch accepts non-2xx responses for tracked assets', () =>
137103
status,
138104
});
139105

140-
assert.equal(shouldTrackAssetFetch(request, response), true);
106+
assert.strictEqual(
107+
shouldTrackAssetFetch(request, response),
108+
true,
109+
'shouldTrackAssetFetch',
110+
);
141111
}
142112
});
143113

@@ -150,7 +120,11 @@ test('shouldTrackAssetFetch accepts non-markdown content type for tracked assets
150120
status: 200,
151121
});
152122

153-
assert.equal(shouldTrackAssetFetch(request, response), true);
123+
assert.strictEqual(
124+
shouldTrackAssetFetch(request, response),
125+
true,
126+
'shouldTrackAssetFetch',
127+
);
154128
});
155129

156130
test('shouldTrackAssetFetch returns false for non-tracked extensions', () => {
@@ -162,12 +136,16 @@ test('shouldTrackAssetFetch returns false for non-tracked extensions', () => {
162136
status: 200,
163137
});
164138

165-
assert.equal(shouldTrackAssetFetch(request, response), false);
139+
assert.strictEqual(
140+
shouldTrackAssetFetch(request, response),
141+
false,
142+
'shouldTrackAssetFetch',
143+
);
166144
});
167145

168146
test('handler emits asset_fetch for explicit .md requests', async (t) => {
169147
setupNetlifyEnv(t);
170-
const ga4Bodies = setupFetchMock(t);
148+
const ga4Bodies = setupGa4CapturingFetchMock(t);
171149
const spy = createWaitUntilSpy();
172150

173151
const response = await assetTracking(
@@ -184,24 +162,28 @@ test('handler emits asset_fetch for explicit .md requests', async (t) => {
184162

185163
await spy.flush();
186164

187-
assert.equal(response.status, 200);
188-
assert.equal(ga4Bodies.length, 1);
189-
190-
const event = (
191-
ga4Bodies[0].events as { name: string; params: Record<string, string> }[]
192-
)[0];
193-
assert.equal(event.name, 'asset_fetch');
194-
assert.equal(event.params.asset_group, 'markdown');
195-
assert.equal(event.params.asset_path, '/docs/concepts/resources/index.md');
196-
assert.equal(event.params.asset_ext, 'md');
197-
assert.equal(event.params.content_type, 'text/markdown');
198-
assert.equal(event.params.status_code, '200');
199-
assert.ok(!('original_path' in event.params));
165+
assert.strictEqual(response.status, 200, 'HTTP status');
166+
assert.strictEqual(
167+
response.headers.get('x-asset-fetch-ga-info'),
168+
'/docs/concepts/resources/index.md;ga-event-candidate,config-present',
169+
'X-Asset-Fetch-Ga-Info',
170+
);
171+
172+
assertAssetFetchGa4Event(
173+
firstAssetFetchEvent(ga4Bodies),
174+
{
175+
asset_path: '/docs/concepts/resources/index.md',
176+
content_type: 'text/markdown',
177+
status_code: '200',
178+
event_emitter: 'tracking',
179+
},
180+
{ expectOriginalPathAbsent: true },
181+
);
200182
});
201183

202184
test('handler emits asset_fetch for explicit .txt requests', async (t) => {
203185
setupNetlifyEnv(t);
204-
const ga4Bodies = setupFetchMock(t);
186+
const ga4Bodies = setupGa4CapturingFetchMock(t);
205187
const spy = createWaitUntilSpy();
206188

207189
const response = await assetTracking(
@@ -218,24 +200,23 @@ test('handler emits asset_fetch for explicit .txt requests', async (t) => {
218200

219201
await spy.flush();
220202

221-
assert.equal(response.status, 200);
222-
assert.equal(ga4Bodies.length, 1);
223-
224-
const event = (
225-
ga4Bodies[0].events as { name: string; params: Record<string, string> }[]
226-
)[0];
227-
assert.equal(event.name, 'asset_fetch');
228-
assert.equal(event.params.asset_group, 'text');
229-
assert.equal(event.params.asset_path, '/llms.txt');
230-
assert.equal(event.params.asset_ext, 'txt');
231-
assert.equal(event.params.content_type, 'text/plain');
232-
assert.equal(event.params.status_code, '200');
233-
assert.ok(!('original_path' in event.params));
203+
assert.strictEqual(response.status, 200, 'HTTP status');
204+
205+
assertAssetFetchGa4Event(
206+
firstAssetFetchEvent(ga4Bodies),
207+
{
208+
asset_path: '/llms.txt',
209+
content_type: 'text/plain',
210+
status_code: '200',
211+
event_emitter: 'tracking',
212+
},
213+
{ expectOriginalPathAbsent: true },
214+
);
234215
});
235216

236217
test('handler skips asset_fetch for internal marked explicit .md requests', async (t) => {
237218
setupNetlifyEnv(t);
238-
const ga4Bodies = setupFetchMock(t);
219+
const ga4Bodies = setupGa4CapturingFetchMock(t);
239220
const spy = createWaitUntilSpy();
240221

241222
const response = await assetTracking(
@@ -256,13 +237,18 @@ test('handler skips asset_fetch for internal marked explicit .md requests', asyn
256237

257238
await spy.flush();
258239

259-
assert.equal(response.status, 200);
260-
assert.equal(ga4Bodies.length, 0);
240+
assert.strictEqual(response.status, 200, 'HTTP status');
241+
assert.strictEqual(ga4Bodies.length, 0, 'GA4 body count');
242+
assert.strictEqual(
243+
response.headers.get('x-asset-fetch-ga-info'),
244+
'none: internal subrequest',
245+
'X-Asset-Fetch-Ga-Info',
246+
);
261247
});
262248

263249
test('handler emits asset_fetch for explicit .md requests regardless of response status', async (t) => {
264250
setupNetlifyEnv(t);
265-
const ga4Bodies = setupFetchMock(t);
251+
const ga4Bodies = setupGa4CapturingFetchMock(t);
266252
const spy = createWaitUntilSpy();
267253

268254
const response = await assetTracking(
@@ -279,16 +265,12 @@ test('handler emits asset_fetch for explicit .md requests regardless of response
279265

280266
await spy.flush();
281267

282-
assert.equal(response.status, 404);
283-
assert.equal(ga4Bodies.length, 1);
284-
285-
const event = (
286-
ga4Bodies[0].events as { name: string; params: Record<string, string> }[]
287-
)[0];
288-
assert.equal(event.name, 'asset_fetch');
289-
assert.equal(event.params.asset_group, 'markdown');
290-
assert.equal(event.params.asset_path, '/docs/concepts/resources/index.md');
291-
assert.equal(event.params.asset_ext, 'md');
292-
assert.equal(event.params.content_type, 'text/plain');
293-
assert.equal(event.params.status_code, '404');
268+
assert.strictEqual(response.status, 404, 'HTTP status');
269+
270+
assertAssetFetchGa4Event(firstAssetFetchEvent(ga4Bodies), {
271+
asset_path: '/docs/concepts/resources/index.md',
272+
content_type: 'text/plain',
273+
status_code: '404',
274+
event_emitter: 'tracking',
275+
});
294276
});

0 commit comments

Comments
 (0)