|
| 1 | +import { http, HttpResponse } from 'msw'; |
| 2 | +import { describe, expect, it } from 'vitest'; |
| 3 | +import { OsmApiClient } from '~/services/osm'; |
| 4 | +import { server } from '../../mocks/server'; |
| 5 | + |
| 6 | +import type { TdeiClient } from '~/services/tdei'; |
| 7 | + |
| 8 | +// getOsmChange only needs its tdei collaborator for auth: a no-op refresh and an |
| 9 | +// `auth` object whose `complete` flag gates the Authorization header. The real |
| 10 | +// BaseHttpClient runs end to end — MSW intercepts at fetch, so URL building and |
| 11 | +// the async XML parse are exercised. |
| 12 | +const tdeiClient = { |
| 13 | + tryRefreshAuth: async () => {}, |
| 14 | + auth: { complete: false, accessToken: '' } |
| 15 | +} as unknown as TdeiClient; |
| 16 | + |
| 17 | +const OSM_WEB_BASE = 'http://api.test/osm/'; |
| 18 | +const OSM_API_BASE = 'http://api.test/osm/api/0.6/'; |
| 19 | + |
| 20 | +// A minimal but real osmChange document: one created node carrying a tag and a |
| 21 | +// timestamp, so we can assert both the parsed structure and the Date coercion. |
| 22 | +const OSM_CHANGE_XML = `<osmChange version="0.6"> |
| 23 | + <create> |
| 24 | + <node id="1" lat="47.6" lon="-122.3" version="1" changeset="42" timestamp="2026-07-01T00:00:00Z" user="tester" uid="9"> |
| 25 | + <tag k="highway" v="crossing"/> |
| 26 | + </node> |
| 27 | + </create> |
| 28 | +</osmChange>`; |
| 29 | + |
| 30 | +function makeClient() { |
| 31 | + return new OsmApiClient(OSM_WEB_BASE, OSM_API_BASE, tdeiClient); |
| 32 | +} |
| 33 | + |
| 34 | +function stubDownload(xml: string = OSM_CHANGE_XML) { |
| 35 | + server.use( |
| 36 | + http.get(`${OSM_API_BASE}changeset/:id/download`, () => |
| 37 | + HttpResponse.text(xml, { headers: { 'Content-Type': 'application/xml' } }) |
| 38 | + ) |
| 39 | + ); |
| 40 | +} |
| 41 | + |
| 42 | +describe('OsmApiClient.getOsmChange', () => { |
| 43 | + // Regression: parseOsmChangeXml is async (it resolves a Promise once the |
| 44 | + // streaming sax parse completes). Using it without `await` left a Promise |
| 45 | + // here, which JSON.stringify's to "{}" — the bug that made the exported |
| 46 | + // changesets/{id}.json empty even when the .osc had content. |
| 47 | + it('parses the downloaded osmChange XML into a populated object', async () => { |
| 48 | + stubDownload(); |
| 49 | + |
| 50 | + const osmChange = await makeClient().getOsmChange(1, 777); |
| 51 | + |
| 52 | + expect(JSON.stringify(osmChange)).not.toBe('{}'); |
| 53 | + expect(osmChange.create).toHaveLength(1); |
| 54 | + |
| 55 | + const element = osmChange.create![0]!; |
| 56 | + expect(element.type).toBe('node'); |
| 57 | + expect(element.tags).toEqual({ highway: 'crossing' }); |
| 58 | + }); |
| 59 | + |
| 60 | + // The same missing `await` also silently skipped getOsmChange's normalization |
| 61 | + // loop (it iterated the Promise, not the elements), so timestamps stayed raw |
| 62 | + // strings. With the await, each element.timestamp is coerced to a Date. |
| 63 | + it('normalizes each element timestamp into a Date', async () => { |
| 64 | + stubDownload(); |
| 65 | + |
| 66 | + const osmChange = await makeClient().getOsmChange(1, 777); |
| 67 | + |
| 68 | + const element = osmChange.create![0]!; |
| 69 | + expect(element.timestamp).toBeInstanceOf(Date); |
| 70 | + expect(element.timestamp.toISOString()).toBe('2026-07-01T00:00:00.000Z'); |
| 71 | + }); |
| 72 | +}); |
0 commit comments