Skip to content

Commit 8a588d1

Browse files
committed
test: make Discogs snapshots insensitive to JSON key order
- compare Discogs parser snapshots as parsed JSON instead of serialized strings, so object key ordering changes no longer cause false test failures. Snapshot regeneration via `pnpm test:discogs -u` is preserved and writes stable sorted-key JSON. - also omit the volatile Discogs `community` field from fetched fixtures and remove it from the existing fixture set, since the importer does not use it and its counts change frequently.
1 parent 48390e0 commit 8a588d1

24 files changed

Lines changed: 63 additions & 569 deletions

oxfmt.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default defineConfig({
1212
endOfLine: 'lf',
1313
insertFinalNewline: true,
1414
proseWrap: 'preserve',
15-
ignorePatterns: ['pnpm-lock.yaml', 'tests/**/snapshots/**'],
15+
ignorePatterns: ['pnpm-lock.yaml'],
1616
sortImports: true,
1717
overrides: [
1818
{

tests/discogs/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ A **fixture** is the JSON returned by the Discogs API for a release (e.g. `fixtu
2020

2121
A **snapshot** is the JSON produced by running `parseDiscogsRelease` on that fixture (e.g. `snapshots/1996829.json`). It is the expected MusicBrainz import data: artist credits, labels, discs, tracks, durations, and so on.
2222

23-
Snapshot files are excluded from oxfmt formatting so their format is controlled exclusively by the test runner.
23+
Snapshot files are regenerated by the tests with stable key ordering when `pnpm test:discogs -u` is used.
2424

2525
When you change the parser, tests re-parse the fixtures and compare the result to the snapshots.
2626

@@ -29,7 +29,7 @@ When you change the parser, tests re-parse the fixtures and compare the result t
2929
1. Read each entry listed in `config.ts`.
3030
2. Load the matching fixture from `fixtures/<id>.json`.
3131
3. Run `parseDiscogsRelease` on it (via `load-parser.ts`, which loads the userscript in a Node VM with the required mocks).
32-
4. Compare the output to `snapshots/<id>.json` using `toMatchFileSnapshot`.
32+
4. Parse `snapshots/<id>.json` and compare it to the parser output with deep equality. Object key order is ignored, but array order and values still matter.
3333

3434
CI runs `pnpm test:discogs` on every pull request. No Discogs API calls are made during normal test runs.
3535

@@ -67,7 +67,7 @@ Fetch fixtures from the Discogs API (needed after adding a release):
6767
pnpm test:discogs:update-fixtures
6868
```
6969

70-
Approve updated snapshots after an intentional parser change (no network request):
70+
Regenerate snapshots after an intentional parser change (no network request):
7171

7272
```bash
7373
pnpm test:discogs -u

tests/discogs/discogs.test.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,47 @@ const SNAPSHOTS_DIR = path.join(DIR, 'snapshots');
1313

1414
const parseDiscogsRelease = loadParseDiscogsRelease();
1515

16+
function sortJsonValue(value: unknown): unknown {
17+
if (Array.isArray(value)) {
18+
return value.map(sortJsonValue);
19+
}
20+
21+
if (value !== null && typeof value === 'object') {
22+
return Object.fromEntries(
23+
Object.entries(value)
24+
.sort(([left], [right]) => left.localeCompare(right))
25+
.map(([key, child]) => [key, sortJsonValue(child)]),
26+
);
27+
}
28+
29+
return value;
30+
}
31+
32+
function stableStringify(value: unknown): string {
33+
return `${JSON.stringify(sortJsonValue(JSON.parse(JSON.stringify(value))), null, 4)}\n`;
34+
}
35+
36+
// These tests compare parsed JSON structurally, so mirror Vitest's snapshot update flags explicitly.
37+
function shouldUpdateSnapshots(): boolean {
38+
return process.argv.some(arg => arg === '-u' || arg === '--update' || arg === '--updateSnapshot');
39+
}
40+
1641
const releases = RELEASES.map(({ url, description }) => ({
1742
id: path.basename(new URL(url).pathname),
1843
description,
1944
}));
2045

21-
test.each(releases)('$id: $description', async ({ id }) => {
46+
test.each(releases)('$id: $description', ({ id }) => {
2247
const fixture = JSON.parse(fs.readFileSync(path.join(FIXTURES_DIR, `${id}.json`), 'utf8')) as Record<string, unknown>;
48+
const snapshotPath = path.join(SNAPSHOTS_DIR, `${id}.json`);
2349
const result = parseDiscogsRelease(fixture);
24-
await expect(JSON.stringify(result, null, 4)).toMatchFileSnapshot(path.join(SNAPSHOTS_DIR, `${id}.json`));
50+
const serializedResult = stableStringify(result);
51+
52+
if (shouldUpdateSnapshots() || !fs.existsSync(snapshotPath)) {
53+
fs.writeFileSync(snapshotPath, serializedResult, 'utf8');
54+
return;
55+
}
56+
57+
const snapshot = JSON.parse(fs.readFileSync(snapshotPath, 'utf8')) as Record<string, unknown>;
58+
expect(JSON.parse(serializedResult)).toEqual(snapshot);
2559
});

tests/discogs/fetch-fixtures.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const DIR = path.dirname(fileURLToPath(import.meta.url));
88
const FIXTURES_DIR = path.join(DIR, 'fixtures');
99

1010
function stableStringify(value: unknown): string {
11-
return JSON.stringify(value, null, 4);
11+
return `${JSON.stringify(value, null, 4)}\n`;
1212
}
1313

1414
function fixturePath(id: number): string {
@@ -42,6 +42,9 @@ async function updateRelease(url: string): Promise<void> {
4242
const fixture = await fetchFixture(url);
4343
const id = releaseIdFromFixture(fixture, url);
4444

45+
// Delete unused fields that change too often and are not needed by the script.
46+
delete fixture['community'];
47+
4548
fs.mkdirSync(FIXTURES_DIR, { recursive: true });
4649
fs.writeFileSync(fixturePath(id), stableStringify(fixture));
4750

tests/discogs/fixtures/1156598.json

Lines changed: 2 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -110,147 +110,11 @@
110110
}
111111
],
112112
"data_quality": "Correct",
113-
"community": {
114-
"have": 3158,
115-
"want": 1359,
116-
"rating": {
117-
"count": 389,
118-
"average": 4.36
119-
},
120-
"submitter": {
121-
"username": "kinoeyevert",
122-
"resource_url": "https://api.discogs.com/users/kinoeyevert"
123-
},
124-
"contributors": [
125-
{
126-
"username": "kinoeyevert",
127-
"resource_url": "https://api.discogs.com/users/kinoeyevert"
128-
},
129-
{
130-
"username": "kwulf",
131-
"resource_url": "https://api.discogs.com/users/kwulf"
132-
},
133-
{
134-
"username": "marcelrecords",
135-
"resource_url": "https://api.discogs.com/users/marcelrecords"
136-
},
137-
{
138-
"username": "blach",
139-
"resource_url": "https://api.discogs.com/users/blach"
140-
},
141-
{
142-
"username": "mcdustsucker",
143-
"resource_url": "https://api.discogs.com/users/mcdustsucker"
144-
},
145-
{
146-
"username": "jos_jaspers",
147-
"resource_url": "https://api.discogs.com/users/jos_jaspers"
148-
},
149-
{
150-
"username": "Lovinda",
151-
"resource_url": "https://api.discogs.com/users/Lovinda"
152-
},
153-
{
154-
"username": "3tone",
155-
"resource_url": "https://api.discogs.com/users/3tone"
156-
},
157-
{
158-
"username": "Rossmichael",
159-
"resource_url": "https://api.discogs.com/users/Rossmichael"
160-
},
161-
{
162-
"username": "nik",
163-
"resource_url": "https://api.discogs.com/users/nik"
164-
},
165-
{
166-
"username": "MannerHeikki",
167-
"resource_url": "https://api.discogs.com/users/MannerHeikki"
168-
},
169-
{
170-
"username": "kosats",
171-
"resource_url": "https://api.discogs.com/users/kosats"
172-
},
173-
{
174-
"username": "home-of-the-record",
175-
"resource_url": "https://api.discogs.com/users/home-of-the-record"
176-
},
177-
{
178-
"username": "Stathisan",
179-
"resource_url": "https://api.discogs.com/users/Stathisan"
180-
},
181-
{
182-
"username": "pshico87",
183-
"resource_url": "https://api.discogs.com/users/pshico87"
184-
},
185-
{
186-
"username": "karlglogauer",
187-
"resource_url": "https://api.discogs.com/users/karlglogauer"
188-
},
189-
{
190-
"username": "yuhann",
191-
"resource_url": "https://api.discogs.com/users/yuhann"
192-
},
193-
{
194-
"username": "solmaz.malek",
195-
"resource_url": "https://api.discogs.com/users/solmaz.malek"
196-
},
197-
{
198-
"username": "allvinyl2",
199-
"resource_url": "https://api.discogs.com/users/allvinyl2"
200-
},
201-
{
202-
"username": "Vinylfan4",
203-
"resource_url": "https://api.discogs.com/users/Vinylfan4"
204-
},
205-
{
206-
"username": "DasBosun",
207-
"resource_url": "https://api.discogs.com/users/DasBosun"
208-
},
209-
{
210-
"username": "mashanovmv",
211-
"resource_url": "https://api.discogs.com/users/mashanovmv"
212-
},
213-
{
214-
"username": "bdierkes",
215-
"resource_url": "https://api.discogs.com/users/bdierkes"
216-
},
217-
{
218-
"username": "Dreammare",
219-
"resource_url": "https://api.discogs.com/users/Dreammare"
220-
},
221-
{
222-
"username": "romel55",
223-
"resource_url": "https://api.discogs.com/users/romel55"
224-
},
225-
{
226-
"username": "dr._phibes_02",
227-
"resource_url": "https://api.discogs.com/users/dr._phibes_02"
228-
},
229-
{
230-
"username": "floodzoog",
231-
"resource_url": "https://api.discogs.com/users/floodzoog"
232-
},
233-
{
234-
"username": "anjo29",
235-
"resource_url": "https://api.discogs.com/users/anjo29"
236-
},
237-
{
238-
"username": "ernstlx",
239-
"resource_url": "https://api.discogs.com/users/ernstlx"
240-
},
241-
{
242-
"username": "templar53",
243-
"resource_url": "https://api.discogs.com/users/templar53"
244-
}
245-
],
246-
"data_quality": "Correct",
247-
"status": "Accepted"
248-
},
249113
"format_quantity": 1,
250114
"date_added": "2008-01-15T05:20:44-08:00",
251115
"date_changed": "2025-02-16T06:54:30-08:00",
252-
"num_for_sale": 95,
253-
"lowest_price": 2.42,
116+
"num_for_sale": 98,
117+
"lowest_price": 2.38,
254118
"master_id": 498,
255119
"master_url": "https://api.discogs.com/masters/498",
256120
"title": "Lizard",
@@ -390,13 +254,6 @@
390254
"description": "Full Album Playlist: \nhttps://www.youtube.com/playlist?list=PLXhfRoiJBIivF7WWLowP0hkPDypPtqInQ\n\nLIZARD\n(Fripp / Sinfield)\n\nPRINCE RUPERT AWAKES\n\nFarewell the temple master’s bells \nHis kiosk and his black worm seed \nCourtship solely of his word \nWith Eden",
391255
"duration": 0,
392256
"embed": true
393-
},
394-
{
395-
"uri": "https://www.youtube.com/watch?v=Zx4FOsyetMs",
396-
"title": "King Crimson - Lizard (Album Visualiser)",
397-
"description": "00:00:00 Cirkus (Including 'Entry Of The Cameleons')\n00:06:28 Indoor Games\n00:12:03 Happy Family\n00:16:26 Lady Of The Dancing Water\n00:19:14 Lizard ('Prince Rupert Awakes'/ 'Bolero'/ 'The Battle Of Glass Tears'/ 'Big Top')\n00:42:35 Studio Sessions: 'Cirku",
398-
"duration": 0,
399-
"embed": true
400257
}
401258
],
402259
"genres": ["Rock"],

tests/discogs/fixtures/1450895.json

Lines changed: 2 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -233,79 +233,11 @@
233233
}
234234
],
235235
"data_quality": "Correct",
236-
"community": {
237-
"have": 323,
238-
"want": 16,
239-
"rating": {
240-
"count": 16,
241-
"average": 3.44
242-
},
243-
"submitter": {
244-
"username": "aclbernie",
245-
"resource_url": "https://api.discogs.com/users/aclbernie"
246-
},
247-
"contributors": [
248-
{
249-
"username": "aclbernie",
250-
"resource_url": "https://api.discogs.com/users/aclbernie"
251-
},
252-
{
253-
"username": "love-vinyl-records",
254-
"resource_url": "https://api.discogs.com/users/love-vinyl-records"
255-
},
256-
{
257-
"username": "Kergillian",
258-
"resource_url": "https://api.discogs.com/users/Kergillian"
259-
},
260-
{
261-
"username": "MaximusMCX",
262-
"resource_url": "https://api.discogs.com/users/MaximusMCX"
263-
},
264-
{
265-
"username": "harbour",
266-
"resource_url": "https://api.discogs.com/users/harbour"
267-
},
268-
{
269-
"username": "Bladerunner1858",
270-
"resource_url": "https://api.discogs.com/users/Bladerunner1858"
271-
},
272-
{
273-
"username": "gone4sure",
274-
"resource_url": "https://api.discogs.com/users/gone4sure"
275-
},
276-
{
277-
"username": "themusicgoesaround",
278-
"resource_url": "https://api.discogs.com/users/themusicgoesaround"
279-
},
280-
{
281-
"username": "vokuhila",
282-
"resource_url": "https://api.discogs.com/users/vokuhila"
283-
},
284-
{
285-
"username": "johntugby",
286-
"resource_url": "https://api.discogs.com/users/johntugby"
287-
},
288-
{
289-
"username": "uzumaki",
290-
"resource_url": "https://api.discogs.com/users/uzumaki"
291-
},
292-
{
293-
"username": "capitolfive",
294-
"resource_url": "https://api.discogs.com/users/capitolfive"
295-
},
296-
{
297-
"username": "andygrayrecords",
298-
"resource_url": "https://api.discogs.com/users/andygrayrecords"
299-
}
300-
],
301-
"data_quality": "Correct",
302-
"status": "Accepted"
303-
},
304236
"format_quantity": 2,
305237
"date_added": "2008-09-07T01:35:40-07:00",
306238
"date_changed": "2020-01-12T09:14:01-08:00",
307-
"num_for_sale": 70,
308-
"lowest_price": 0.17,
239+
"num_for_sale": 72,
240+
"lowest_price": 0.18,
309241
"master_id": 77692,
310242
"master_url": "https://api.discogs.com/masters/77692",
311243
"title": "Live",

tests/discogs/fixtures/15313328.json

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -87,34 +87,6 @@
8787
}
8888
],
8989
"data_quality": "Needs Vote",
90-
"community": {
91-
"have": 17,
92-
"want": 1,
93-
"rating": {
94-
"count": 1,
95-
"average": 4
96-
},
97-
"submitter": {
98-
"username": "steverogers",
99-
"resource_url": "https://api.discogs.com/users/steverogers"
100-
},
101-
"contributors": [
102-
{
103-
"username": "steverogers",
104-
"resource_url": "https://api.discogs.com/users/steverogers"
105-
},
106-
{
107-
"username": "demier777",
108-
"resource_url": "https://api.discogs.com/users/demier777"
109-
},
110-
{
111-
"username": "M3acull",
112-
"resource_url": "https://api.discogs.com/users/M3acull"
113-
}
114-
],
115-
"data_quality": "Needs Vote",
116-
"status": "Accepted"
117-
},
11890
"format_quantity": 41,
11991
"date_added": "2020-05-15T15:52:30-07:00",
12092
"date_changed": "2024-01-17T00:55:56-08:00",

0 commit comments

Comments
 (0)