From af7167f9d792f5de8d513ac757ceaf443ee632d1 Mon Sep 17 00:00:00 2001 From: Adam Wright Date: Sat, 24 Oct 2026 02:12:08 +0000 Subject: [PATCH 1/2] Spec 013 T023: eight more content-service specs, in TypeScript Batch 3 of the .mjs specs: pathways-low, complexes, overview-export, schema-objects, orthology, query-attribute-endpoint, interactors-static and referrers -- 183 errors with checkJs on. Each is now a .ts spec, typed and held at zero on both counts, as batch 2's were: - the servers and stand-in graphs typed as before (Server and AddressInfo; the graph's read taking a cypher string and its parameters, with the parameters the tests read by name declared); - each JSON answer read as the shape Java writes; - "the last query asked" and "the nth query asked" throw when there is none, as indexing into nothing threw before, so none can pass on undefined; - tuple loops typed `as const`; body-less POSTs send no body, as before; - fixtures typed from what they feed (orthologyMap's found objects as GraphNode, the lazily loaded objects of the attribute endpoint). Same 103 tests as the .mjs versions, all passing; types, lint (1,329) and dead code (131) unchanged. Co-Authored-By: Claude Opus 5.5 --- specs/013-server-types/tasks.md | 2 +- .../{complexes.spec.mjs => complexes.spec.ts} | 49 +++++++++----- ...ic.spec.mjs => interactors-static.spec.ts} | 53 +++++++++------ .../{orthology.spec.mjs => orthology.spec.ts} | 66 +++++++++++-------- ...xport.spec.mjs => overview-export.spec.ts} | 51 ++++++++------ ...ways-low.spec.mjs => pathways-low.spec.ts} | 52 +++++++++------ ...c.mjs => query-attribute-endpoint.spec.ts} | 50 ++++++++------ .../{referrers.spec.mjs => referrers.spec.ts} | 60 ++++++++++------- ...bjects.spec.mjs => schema-objects.spec.ts} | 66 ++++++++++++------- 9 files changed, 280 insertions(+), 169 deletions(-) rename tools/content-node/{complexes.spec.mjs => complexes.spec.ts} (79%) rename tools/content-node/{interactors-static.spec.mjs => interactors-static.spec.ts} (75%) rename tools/content-node/{orthology.spec.mjs => orthology.spec.ts} (88%) rename tools/content-node/{overview-export.spec.mjs => overview-export.spec.ts} (79%) rename tools/content-node/{pathways-low.spec.mjs => pathways-low.spec.ts} (79%) rename tools/content-node/{query-attribute-endpoint.spec.mjs => query-attribute-endpoint.spec.ts} (84%) rename tools/content-node/{referrers.spec.mjs => referrers.spec.ts} (72%) rename tools/content-node/{schema-objects.spec.mjs => schema-objects.spec.ts} (67%) diff --git a/specs/013-server-types/tasks.md b/specs/013-server-types/tasks.md index 18251412..393fb010 100644 --- a/specs/013-server-types/tasks.md +++ b/specs/013-server-types/tasks.md @@ -73,7 +73,7 @@ Each task: rename to `.ts`, update imports, file names that name it (R3 list), f - [ ] T023 The tools beside the services that they do not import (`tools/java-parity`, `tools/library-update`, `tools/svg-export-harness`, the check specs): converted the same way, or recorded as left and why. - Measured 23 Oct with `checkJs` on: 1,229 errors in 47 `.mjs` files, 70 of them in the five tools and 1,159 in 42 specs. The tools are converted, typed, held at zero both counts, and still run as before (parity and the library guard directly under Node, the harness through its new `playwright.config.ts`, which also mends the README's command: it had found no tests since the harness stopped being called a spec) — #643. The specs go next, in batches: the ten check specs outside the content service (load, started, workflows, check-move, check-baseline-totals, the HAR, flaky and lint checks, and content-node's hermetic), 60 errors — #644. The content service's ten smallest (exporter, cache-headers, molecule-details-bound, component-of, diseases, participating-entities, interactors, query-ids, map-results, mapping), 139 — #645. + Measured 23 Oct with `checkJs` on: 1,229 errors in 47 `.mjs` files, 70 of them in the five tools and 1,159 in 42 specs. The tools are converted, typed, held at zero both counts, and still run as before (parity and the library guard directly under Node, the harness through its new `playwright.config.ts`, which also mends the README's command: it had found no tests since the harness stopped being called a spec) — #643. The specs go next, in batches: the ten check specs outside the content service (load, started, workflows, check-move, check-baseline-totals, the HAR, flaky and lint checks, and content-node's hermetic), 60 errors — #644. The content service's ten smallest (exporter, cache-headers, molecule-details-bound, component-of, diseases, participating-entities, interactors, query-ids, map-results, mapping), 139 — #645. The next eight (pathways-low, complexes, overview-export, schema-objects, orthology, query-attribute-endpoint, interactors-static, referrers), 183 — #647. - [ ] T024 `CLAUDE.md`: the rule for new service code (typed, and a load test entry for a new entry point) if one emerges. diff --git a/tools/content-node/complexes.spec.mjs b/tools/content-node/complexes.spec.ts similarity index 79% rename from tools/content-node/complexes.spec.mjs rename to tools/content-node/complexes.spec.ts index 60d15079..c243c06f 100644 --- a/tools/content-node/complexes.spec.mjs +++ b/tools/content-node/complexes.spec.ts @@ -5,13 +5,19 @@ // with a stand-in graph. PhysicalEntityController over graph-core's // PhysicalEntityRepository, whose queries are kept as written. The entities // here are invented; Java's messages were measured 17 Oct 2026. +import type { Server } from 'node:http'; +import type { AddressInfo } from 'node:net'; import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; -const asked = []; -let rows = []; -let failure; +/** A query's parameters, as the service passes them. */ +type Params = { id?: unknown; taxId?: unknown; [name: string]: unknown }; +/** Each query the stand-in graph was asked, with its parameters. */ +const asked: [string, Params][] = []; +let rows: unknown[] = []; +/** What the stand-in graph throws next, if anything. */ +let failure: Error | undefined; vi.mock('./graph.ts', () => ({ - read: async (cypher, params) => { + read: async (cypher: string, params: Params) => { asked.push([cypher, params]); if (failure) throw failure; return rows; @@ -23,26 +29,35 @@ vi.mock('./graph.ts', () => ({ const { app } = await import('./server.ts'); -let base; -let server; +let base: string; +let server: Server; beforeAll(async () => { - await new Promise((resolve) => { - server = app().listen(0, '127.0.0.1', resolve); + await new Promise((resolve) => { + server = app().listen(0, '127.0.0.1', () => resolve()); }); - base = `http://127.0.0.1:${server.address().port}`; + base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`; }); -afterAll(() => new Promise((resolve) => server.close(resolve))); +afterAll(() => new Promise((resolve) => server.close(() => resolve()))); -const get = (path) => fetch(`${base}/ContentService/data/${path}`); -const last = () => asked[asked.length - 1]; -const oneLine = (cypher) => cypher.replace(/\s+/g, ' ').trim(); -const entity = (dbId, schemaClass) => ({ +const get = (path: string) => fetch(`${base}/ContentService/data/${path}`); +/** The last query asked; none at all is the test's failure, not undefined. */ +const last = () => { + const query = asked.at(-1); + if (!query) throw new Error('the graph was asked nothing'); + return query; +}; +const oneLine = (cypher: string) => cypher.replace(/\s+/g, ' ').trim(); +const entity = (dbId: number, schemaClass: string) => ({ dbId, stId: `R-HSA-${dbId}`, displayName: `E${dbId}`, schemaClass, }); -const messages = async (response) => [response.status, (await response.json()).messages]; +/** Status and the messages of Java's error envelope. */ +const messages = async (response: Response) => [ + response.status, + ((await response.json()) as { messages: string[] }).messages, +]; describe('/data/complex/{id}/subunits', () => { it("runs graph-core's query as written and writes the entities in the rows' order", async () => { @@ -50,7 +65,7 @@ describe('/data/complex/{id}/subunits', () => { failure = undefined; asked.length = 0; const response = await get('complex/R-HSA-5/subunits'); - expect((await response.json()).map((e) => e.dbId)).toEqual([2, 1]); + expect(((await response.json()) as { dbId: number }[]).map((e) => e.dbId)).toEqual([2, 1]); expect(oneLine(last()[0])).toBe( 'MATCH (:PhysicalEntity{stId: $id})-[:hasComponent|hasMember|hasCandidate|repeatedUnit|proteinMarker|RNAMarker*]->(pe:PhysicalEntity) RETURN DISTINCT pe' ); @@ -111,7 +126,7 @@ describe('/data/complexes/{resource}/{identifier}', () => { failure = undefined; asked.length = 0; const response = await get('complexes/(%3Fi)uniprot/P00533'); - expect((await response.json()).map((e) => e.dbId)).toEqual([3]); + expect(((await response.json()) as { dbId: number }[]).map((e) => e.dbId)).toEqual([3]); expect(oneLine(last()[0])).toBe( 'MATCH (rd:ReferenceDatabase)<-[:referenceDatabase]-(n{identifier:$identifier}) WHERE rd.displayName =~ $resource WITH DISTINCT n MATCH (n)<-[:referenceEntity|referenceSequence|crossReference|referenceGene*]-(pe:PhysicalEntity) WITH DISTINCT pe MATCH (c:Complex)-[:hasComponent|hasMember|hasCandidate|repeatedUnit|proteinMarker|RNAMarker*]->(pe) RETURN DISTINCT c' ); diff --git a/tools/content-node/interactors-static.spec.mjs b/tools/content-node/interactors-static.spec.ts similarity index 75% rename from tools/content-node/interactors-static.spec.mjs rename to tools/content-node/interactors-static.spec.ts index 935e086b..ad471e61 100644 --- a/tools/content-node/interactors-static.spec.mjs +++ b/tools/content-node/interactors-static.spec.ts @@ -7,12 +7,18 @@ // InteractionManager over graph-core's InteractorCountRepository and // PathwayRepository, their queries kept as written. The counts here are // invented; Java's answers were measured 17 Oct 2026. +import type { Server } from 'node:http'; +import type { AddressInfo } from 'node:net'; import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; -let answer = () => []; -const asked = []; +/** A query's parameters, as the service passes them. */ +type Params = { accs?: string[]; speciesName?: string | null; [name: string]: unknown }; +/** The stand-in graph's rows for a query. */ +let answer: (cypher: string, params: Params) => unknown[] = () => []; +/** Each query the stand-in graph was asked, with its parameters. */ +const asked: [string, Params][] = []; vi.mock('./graph.ts', () => ({ - read: async (cypher, params) => { + read: async (cypher: string, params: Params) => { asked.push([cypher, params]); return answer(cypher, params); }, @@ -23,26 +29,35 @@ vi.mock('./graph.ts', () => ({ const { app } = await import('./server.ts'); -let base; -let server; +let base: string; +let server: Server; beforeAll(async () => { - await new Promise((resolve) => { - server = app().listen(0, '127.0.0.1', resolve); + await new Promise((resolve) => { + server = app().listen(0, '127.0.0.1', () => resolve()); }); - base = `http://127.0.0.1:${server.address().port}`; + base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`; }); -afterAll(() => new Promise((resolve) => server.close(resolve))); +afterAll(() => new Promise((resolve) => server.close(() => resolve()))); -const get = (path) => fetch(`${base}/ContentService/interactors/static/${path}`); -const post = (body, type = 'text/plain') => +const get = (path: string) => fetch(`${base}/ContentService/interactors/static/${path}`); +const post = (body: string | undefined, type = 'text/plain') => fetch(`${base}/ContentService/interactors/static/molecules/summary`, { method: 'POST', headers: type ? { 'content-type': type } : {}, - body, + ...(body === undefined ? {} : { body }), }); -const messages = async (response) => [response.status, (await response.json()).messages]; -const last = () => asked[asked.length - 1]; -const oneLine = (cypher) => cypher.replace(/\s+/g, ' ').trim(); +/** Status and the messages of Java's error envelope. */ +const messages = async (response: Response) => [ + response.status, + ((await response.json()) as { messages: string[] }).messages, +]; +/** The last query asked; none at all is the test's failure, not undefined. */ +const last = () => { + const query = asked.at(-1); + if (!query) throw new Error('the graph was asked nothing'); + return query; +}; +const oneLine = (cypher: string) => cypher.replace(/\s+/g, ' ').trim(); const COUNT = 'MATCH (t:ReferenceEntity)<-[:interactor]-(in:Interaction) WHERE t.variantIdentifier IN $accs OR (t.variantIdentifier IS NULL AND t.identifier IN $accs) RETURN DISTINCT t.identifier AS s, COUNT(DISTINCT in) as t'; @@ -77,10 +92,10 @@ describe('POST /interactors/static/molecules/summary', () => { expect(await response.text()).toBe( '{"resource":"static","entities":[{"acc":"Q00987","count":78},{"acc":"P04637","count":249}]}' ); - expect([...last()[1].accs].sort()).toEqual(['P04637', 'Q00987', 'xyz']); + expect([...(last()[1].accs ?? [])].sort()).toEqual(['P04637', 'Q00987', 'xyz']); // Newline and tab separate too. await post('P1\nP2\tP3'); - expect([...last()[1].accs].sort()).toEqual(['P1', 'P2', 'P3']); + expect([...(last()[1].accs ?? [])].sort()).toEqual(['P1', 'P2', 'P3']); }); it("answers Java's errors: 415, 422, and a 404 naming the body as sent", async () => { @@ -107,7 +122,7 @@ describe('/interactors/static/molecule/{acc}/pathways', () => { answer = () => [{ p: { dbId: 3, stId: 'R-HSA-3', schemaClass: 'Pathway' } }]; asked.length = 0; const response = await get('molecule/P1/pathways?species=Homo%20sapiens'); - expect((await response.json()).map((p) => p.dbId)).toEqual([3]); + expect(((await response.json()) as { dbId: number }[]).map((p) => p.dbId)).toEqual([3]); expect(oneLine(last()[0])).toBe( 'MATCH (a:ReferenceEntity)<-[:interactor]-()-[:interactor]->(b:ReferenceEntity) WHERE a.identifier = $acc OR a.variantIdentifier = $acc MATCH path=(p:Pathway)-[:hasEvent|input|output|catalystActivity|physicalEntity|regulatedBy|regulator*]->(pe:PhysicalEntity)-[:referenceEntity]->(b) WHERE p.speciesName = $speciesName AND SINGLE(e IN NODES(path) WHERE (e:Pathway)) RETURN DISTINCT p' ); @@ -131,7 +146,7 @@ describe('/interactors/static/molecule/{acc}/pathways', () => { }); it('is a 404 with no species, which matches nothing', async () => { - answer = (cypher, params) => (params.speciesName === null ? [] : [{ p: { dbId: 3 } }]); + answer = (_cypher, params) => (params.speciesName === null ? [] : [{ p: { dbId: 3 } }]); expect(await messages(await get('molecule/P1/pathways'))).toEqual([ 404, ['No pathways found for molecule: P1'], diff --git a/tools/content-node/orthology.spec.mjs b/tools/content-node/orthology.spec.ts similarity index 88% rename from tools/content-node/orthology.spec.mjs rename to tools/content-node/orthology.spec.ts index 254b8b41..4c3aacbd 100644 --- a/tools/content-node/orthology.spec.mjs +++ b/tools/content-node/orthology.spec.ts @@ -5,6 +5,8 @@ // the POST's content type, and the 405 Java answers for a method a mapping // does not take. Every expected value below was measured against Java's // /data/orthology and /data/orthologies on 15 Oct 2026. +import type { Server } from 'node:http'; +import type { AddressInfo } from 'node:net'; import { describe, expect, it, beforeAll, afterAll } from 'vitest'; import { app } from './server.ts'; import { JavaHashMap } from './java-hashmap.ts'; @@ -13,18 +15,22 @@ import { orthologyIds } from './body.ts'; import { springPathLong } from './spring.ts'; import { plainText, plainTextProblem } from './body.ts'; import { JsonMap, mapNodes, VIEW, writeDomain } from './domain.ts'; +import type { GraphNode } from './graph.ts'; -let base; -let server; +/** A found object, as far as orthologyMap reads one: whether it is there. */ +const node = (id: number | string): GraphNode => ({ id, labels: [], props: {} }); + +let base: string; +let server: Server; beforeAll(async () => { - await new Promise((resolve) => { - server = app().listen(0, '127.0.0.1', resolve); + await new Promise((resolve) => { + server = app().listen(0, '127.0.0.1', () => resolve()); }); - base = `http://127.0.0.1:${server.address().port}`; + base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`; }); -afterAll(() => new Promise((resolve) => server.close(resolve))); +afterAll(() => new Promise((resolve) => server.close(() => resolve()))); describe('springPathLong, a Long path variable as Spring converts it', () => { it('reads what Long.valueOf and Long.decode read', () => { @@ -50,7 +56,7 @@ describe('springPathLong, a Long path variable as Spring converts it', () => { }); it("refuses the rest with Java's 400, naming what it could not parse", () => { - const refused = (text) => + const refused = (text: string) => `Failed to convert value of type 'java.lang.String' to required type 'java.lang.Long'; ` + `nested exception is java.lang.NumberFormatException: For input string: "${text}"`; for (const [given, named] of [ @@ -60,7 +66,7 @@ describe('springPathLong, a Long path variable as Spring converts it', () => { ['0x', ''], ['1.5', '1.5'], ['48892.0', '48892.0'], - ]) + ] as const) expect(springPathLong(given, 'speciesId'), given).toEqual({ status: 400, error: refused(named), @@ -77,8 +83,8 @@ describe('springPathLong, a Long path variable as Spring converts it', () => { }); describe('JavaHashMap, in the order Java iterates a HashMap', () => { - const order = (keys) => { - const map = new JavaHashMap(); + const order = (keys: string[]) => { + const map = new JavaHashMap(); for (const key of keys) map.set(key, key); return [...map.keys()]; }; @@ -111,9 +117,9 @@ describe('JavaHashMap, in the order Java iterates a HashMap', () => { // 16 buckets hold 12; the 13th key doubles the table, and keys that share // a bucket at 16 keep their relative order in each half at 32. const keys = Array.from({ length: 13 }, (_, at) => `k${at * 16}`); - const map = new JavaHashMap(); + const map = new JavaHashMap(); for (const key of keys) map.set(key, true); - const hash = (key) => { + const hash = (key: string) => { let h = 0; for (const unit of key) h = (Math.imul(31, h) + unit.charCodeAt(0)) | 0; return (h ^ (h >>> 16)) >>> 0; @@ -160,13 +166,15 @@ describe('orthologyMap, the two maps getOrthologies builds', () => { 'R-HSA-69620.9', ...Array.from({ length: 11 }, (_, at) => `x${at + 1}`), ]; - const found = new Map(ids.map((id) => [id, id.startsWith('R') ? { id } : null])); + const found = new Map( + ids.map((id) => [id, id.startsWith('R') ? node(id) : null]) + ); expect([...orthologyMap(ids, found).keys()]).toEqual(['R-HSA-69620.11', 'R-HSA-69620.9']); }); it('drops an id with nothing found', () => { - const found = new Map([ - ['R-HSA-69620', { id: 1 }], + const found = new Map([ + ['R-HSA-69620', node(1)], ['R-ALL-29358', undefined], ]); expect([...orthologyMap(['R-HSA-69620', 'R-ALL-29358'], found).keys()]).toEqual([ @@ -177,7 +185,7 @@ describe('orthologyMap, the two maps getOrthologies builds', () => { describe('plainText, the POST body in its charset as the JDK decodes it', () => { // Each expected string is the JDK's new String(bytes, charset). - const read = (charset, hex) => + const read = (charset: string, hex: string) => plainText(Buffer.from(hex, 'hex'), `text/plain;charset=${charset}`); it('reads UTF-16 big-endian without a mark, and by its mark with one', () => { @@ -278,9 +286,11 @@ describe('writeDomain with a map', () => { }); describe('the orthology endpoints, before any query', () => { - const get = (path, init) => fetch(`${base}/ContentService/data${path}`, init); - const envelope = async (response) => { - const body = await response.json(); + const get = (path: string, init?: RequestInit) => + fetch(`${base}/ContentService/data${path}`, init); + /** Status, reason and messages: Java's error envelope. */ + const envelope = async (response: Response): Promise<[number, string, string[]]> => { + const body = (await response.json()) as { reason: string; messages: string[] }; return [response.status, body.reason, body.messages]; }; @@ -311,11 +321,11 @@ describe('the orthology endpoints, before any query', () => { ]); }); - const post = (species, body, type = 'text/plain') => + const post = (species: string, body: string | undefined, type = 'text/plain') => get(`/orthologies/ids/species/${species}`, { method: 'POST', headers: type ? { 'Content-Type': type } : {}, - body, + ...(body === undefined ? {} : { body }), }); it('checks the content type, then the species, then the body', async () => { @@ -363,14 +373,18 @@ describe('a method a mapping does not take', () => { fetch(`${base}/ContentService/data/orthology/R-HSA-69620/species/48892`, { method: 'PUT' }), fetch(`${base}/ContentService/data/orthologies/ids/species/48892`), ]); - const bodies = await Promise.all(answers.map((response) => response.json())); + const bodies = await Promise.all( + answers.map( + async (response) => (await response.json()) as { reason: string; messages: string[] } + ) + ); expect(answers.map((response) => response.status)).toEqual([405, 405, 405]); expect(bodies.map((body) => body.messages[0])).toEqual([ "Request method 'POST' not supported", "Request method 'PUT' not supported", "Request method 'GET' not supported", ]); - expect(bodies[0].reason).toBe('METHOD_NOT_ALLOWED'); + expect(bodies[0]?.reason).toBe('METHOD_NOT_ALLOWED'); }); }); @@ -379,7 +393,7 @@ describe("Spring's guard against a download named by the URL", () => { // written for a path whose last segment ends in an extension Spring does not trust is sent as an attachment-proof // inline f.txt. Each case measured against Java on 15 Oct 2026, on the // species segment, which fails before any query. - const disposition = async (last) => { + const disposition = async (last: string) => { const response = await fetch(`${base}/ContentService/data/orthology/xyz/species/${last}`); await response.arrayBuffer(); return response.headers.get('content-disposition'); @@ -395,7 +409,7 @@ describe("Spring's guard against a download named by the URL", () => { '48892.wasm', '48892.jsonp', '48892.5', - ]) + ] as const) it(`marks ${last}`, async () => { expect(await disposition(last)).toBe(guarded); }); @@ -408,7 +422,7 @@ describe("Spring's guard against a download named by the URL", () => { '48892.SVG', '48892.', '48892.json', - ]) + ] as const) it(`leaves ${last} alone`, async () => { expect(await disposition(last)).toBe(null); }); diff --git a/tools/content-node/overview-export.spec.mjs b/tools/content-node/overview-export.spec.ts similarity index 79% rename from tools/content-node/overview-export.spec.mjs rename to tools/content-node/overview-export.spec.ts index 7be86b3e..1783a7f8 100644 --- a/tools/content-node/overview-export.spec.mjs +++ b/tools/content-node/overview-export.spec.ts @@ -5,17 +5,23 @@ // T093); the old /exporter/fireworks/ URL redirects to it. A stand-in graph // answers the species lookup, and a stand-in render service records what it // is asked for. Java's answers measured on production, 19 Oct 2026. -import { createServer } from 'node:http'; +import { createServer, type Server } from 'node:http'; +import type { AddressInfo } from 'node:net'; import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; -const SPECIES = { 9606: '9606', 'Homo sapiens': '9606', 48887: '9606', 'Mus musculus': '10090' }; +const SPECIES: Record = { + 9606: '9606', + 'Homo sapiens': '9606', + 48887: '9606', + 'Mus musculus': '10090', +}; vi.mock('./graph.ts', () => ({ - read: async (cypher, params) => { - const taxId = SPECIES[params?.value]; + read: async (cypher: string, params?: { value?: string }) => { + const taxId = params?.value === undefined ? undefined : SPECIES[params.value]; if (cypher.includes('{taxId: $value}')) - return taxId && params.value === taxId ? [{ taxId }] : []; + return taxId && params?.value === taxId ? [{ taxId }] : []; if (cypher.includes('{dbId: toInteger($value)}')) - return params.value === '48887' ? [{ taxId: '9606' }] : []; + return params?.value === '48887' ? [{ taxId: '9606' }] : []; if (cypher.includes('$value IN s.name')) return taxId ? [{ taxId }] : []; return []; }, @@ -24,32 +30,35 @@ vi.mock('./graph.ts', () => ({ envSetting: () => '', })); -const asked = []; -let renderer; -let base; -let server; +/** What the stand-in render service was asked for, by path and query. */ +const asked: (string | undefined)[] = []; +let renderer: Server; +let base: string; +let server: Server; beforeAll(async () => { renderer = createServer((request, response) => { asked.push(request.url); response.writeHead(200, { 'Content-Type': 'image/svg+xml' }); response.end(''); }); - await new Promise((resolve) => renderer.listen(0, '127.0.0.1', resolve)); - process.env.RENDER_SERVICE_URL = `http://127.0.0.1:${renderer.address().port}`; + await new Promise((resolve) => renderer.listen(0, '127.0.0.1', resolve)); + process.env['RENDER_SERVICE_URL'] = + `http://127.0.0.1:${(renderer.address() as AddressInfo).port}`; const { app } = await import('./server.ts'); - await new Promise((resolve) => { - server = app().listen(0, '127.0.0.1', resolve); + await new Promise((resolve) => { + server = app().listen(0, '127.0.0.1', () => resolve()); }); - base = `http://127.0.0.1:${server.address().port}`; + base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`; }); afterAll(async () => { - await new Promise((resolve) => server.close(resolve)); - await new Promise((resolve) => renderer.close(resolve)); + await new Promise((resolve) => server.close(() => resolve())); + await new Promise((resolve) => renderer.close(() => resolve())); }); -const at = (path) => fetch(`${base}/ContentService/exporter/voronoi/${path}`); -const envelope = async (response) => { - const body = await response.json(); +const at = (path: string) => fetch(`${base}/ContentService/exporter/voronoi/${path}`); +/** Status, reason and messages: Java's error envelope. */ +const envelope = async (response: Response): Promise<[number, string, string[]]> => { + const body = (await response.json()) as { reason: string; messages: string[] }; return [response.status, body.reason, body.messages]; }; @@ -158,7 +167,7 @@ describe('the old fireworks URL', () => { 'fire%77orks/9606.svg?x=/ContentService/exporter/fireworks/', '9606.svg?x=/ContentService/exporter/fireworks/', ], - ]) { + ] as const) { const response = await fetch(`${base}/ContentService/exporter/${path}`, { redirect: 'manual', }); diff --git a/tools/content-node/pathways-low.spec.mjs b/tools/content-node/pathways-low.spec.ts similarity index 79% rename from tools/content-node/pathways-low.spec.mjs rename to tools/content-node/pathways-low.spec.ts index 03716e89..fa9fea31 100644 --- a/tools/content-node/pathways-low.spec.mjs +++ b/tools/content-node/pathways-low.spec.ts @@ -7,12 +7,17 @@ // /low/entity/{id}: a stable id or a dbId, each with or without a species, to // graph-core's PathwayRepository query, kept here as written. The pathways // here are invented. +import type { Server } from 'node:http'; +import type { AddressInfo } from 'node:net'; import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; -const asked = []; -let rows = []; +/** A query's parameters, as the service passes them. */ +type Params = { id?: unknown; taxId?: unknown; [name: string]: unknown }; +/** Each query the stand-in graph was asked, with its parameters. */ +const asked: [string, Params][] = []; +let rows: unknown[] = []; vi.mock('./graph.ts', () => ({ - read: async (cypher, params) => { + read: async (cypher: string, params: Params) => { asked.push([cypher, params]); // speciesTaxId's lookup: 9606 is a species. if (/^MATCH \(s:Species/.test(cypher)) return [{ taxId: '9606' }]; @@ -25,38 +30,47 @@ vi.mock('./graph.ts', () => ({ const { app } = await import('./server.ts'); -let base; -let server; +let base: string; +let server: Server; beforeAll(async () => { - await new Promise((resolve) => { - server = app().listen(0, '127.0.0.1', resolve); + await new Promise((resolve) => { + server = app().listen(0, '127.0.0.1', () => resolve()); }); - base = `http://127.0.0.1:${server.address().port}`; + base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`; }); -afterAll(() => new Promise((resolve) => server.close(resolve))); +afterAll(() => new Promise((resolve) => server.close(() => resolve()))); -const get = (path) => fetch(`${base}/ContentService/data/pathways/low/${path}`); -const node = (dbId, name) => ({ +/** The messages of Java's error envelope. */ +const messages = async (response: Response) => + ((await response.json()) as { messages: string[] }).messages; + +const get = (path: string) => fetch(`${base}/ContentService/data/pathways/low/${path}`); +const node = (dbId: number, name: string) => ({ dbId, stId: `R-HSA-${dbId}`, displayName: name, schemaClass: 'Pathway', }); -const last = () => asked[asked.length - 1]; -const oneLine = (cypher) => cypher.replace(/\s+/g, ' ').trim(); +/** The last query asked; none at all is the test's failure, not undefined. */ +const last = () => { + const query = asked.at(-1); + if (!query) throw new Error('the graph was asked nothing'); + return query; +}; +const oneLine = (cypher: string) => cypher.replace(/\s+/g, ' ').trim(); describe('the three variants', () => { for (const [path, column, start] of [ ['entity/R-HSA-1/allForms', 'p', 'MATCH (:DatabaseObject{stId: $id})-[:regulator|'], ['diagram/entity/R-HSA-1', 'm', 'MATCH (:PhysicalEntity{stId: $id})<-[:regulatedBy|'], ['diagram/entity/R-HSA-1/allForms', 'm', 'MATCH (:PhysicalEntity{stId: $id})-[:regulator|'], - ]) + ] as const) it(`${path} runs its own query and writes the pathways in the rows' order`, async () => { rows = [{ [column]: node(2, 'B') }, { [column]: node(1, 'A') }]; asked.length = 0; const response = await get(path); expect(response.status).toBe(200); - expect((await response.json()).map((p) => p.dbId)).toEqual([2, 1]); + expect(((await response.json()) as { dbId: number }[]).map((p) => p.dbId)).toEqual([2, 1]); expect(oneLine(last()[0]).startsWith(start)).toBe(true); expect(last()[1].id).toBe('R-HSA-1'); }); @@ -114,13 +128,9 @@ describe("Java's 404", () => { ['diagram/entity/xyz', 'No result for xyz in null'], ['diagram/entity/xyz/allForms?species=9606', 'No result for xyz in 9606'], ['entity/xyz/allForms?species=Homo%20sapiens', 'No result for xyz in Homo sapiens'], - ]) { + ] as const) { const response = await get(path); - expect([path, response.status, (await response.json()).messages]).toEqual([ - path, - 404, - [message], - ]); + expect([path, response.status, await messages(response)]).toEqual([path, 404, [message]]); } }); }); diff --git a/tools/content-node/query-attribute-endpoint.spec.mjs b/tools/content-node/query-attribute-endpoint.spec.ts similarity index 84% rename from tools/content-node/query-attribute-endpoint.spec.mjs rename to tools/content-node/query-attribute-endpoint.spec.ts index 7071b44c..f0c9fe0d 100644 --- a/tools/content-node/query-attribute-endpoint.spec.mjs +++ b/tools/content-node/query-attribute-endpoint.spec.ts @@ -4,18 +4,23 @@ // through the service, with a stand-in graph: the object findById loads, then // what LazyFetchAspect would load for a getter whose relationship is empty. // The objects here are invented; Java's answers were measured 18 Oct 2026. +import type { Server } from 'node:http'; +import type { AddressInfo } from 'node:net'; import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; +import type { GraphNode } from './graph.ts'; -let root; -let lazy = {}; -const asked = []; +/** The object findById loads; none, for an id the graph does not hold. */ +let root: GraphNode | undefined; +/** The lazy loads' rows, by `${type} ${direction} ${dbId}`. */ +let lazy: Record = {}; +const asked: string[] = []; // The lazy loads by what they ask: `${type} ${direction} ${dbId}`, read from // the query and its parameter -- a load in the wrong direction, or of // another object, finds nothing. Directions are Java's model's // (domain-snapshot.json): an edit's `modified` comes in to the object, and // so does the `hasComponent` behind a complex's componentOf. vi.mock('./graph.ts', () => ({ - read: async (cypher, params) => { + read: async (cypher: string, params: { dbId?: unknown }) => { asked.push(cypher); // /query/{id}/stId's and /query/{id}/normalPathway's own queries. if (/n\.stId AS stId, n\.schemaClass AS schemaClass/.test(cypher)) @@ -41,24 +46,28 @@ vi.mock('./graph.ts', () => ({ const { app } = await import('./server.ts'); -let base; -let server; +let base: string; +let server: Server; beforeAll(async () => { - await new Promise((resolve) => { - server = app().listen(0, '127.0.0.1', resolve); + await new Promise((resolve) => { + server = app().listen(0, '127.0.0.1', () => resolve()); }); - base = `http://127.0.0.1:${server.address().port}`; + base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`; }); -afterAll(() => new Promise((resolve) => server.close(resolve))); +afterAll(() => new Promise((resolve) => server.close(() => resolve()))); -const get = (path) => fetch(`${base}/ContentService/data/${path}`); -const messages = async (response) => [response.status, (await response.json()).messages]; +const get = (path: string) => fetch(`${base}/ContentService/data/${path}`); +/** Status and the messages of Java's error envelope. */ +const messages = async (response: Response) => [ + response.status, + ((await response.json()) as { messages: string[] }).messages, +]; const pathway = { id: 1, labels: ['DatabaseObject', 'Event', 'Pathway'], props: { dbId: 9, stId: 'R-HSA-9', displayName: 'P', schemaClass: 'Pathway' }, }; -const loaded = (id, schemaClass, labels, n = null) => ({ +const loaded = (id: number, schemaClass: string, labels: string[], n: number | null = null) => ({ m: { id, labels: ['DatabaseObject', ...labels], @@ -145,7 +154,7 @@ describe('stId and normalPathway in any case', () => { ['STID', /n\.stId AS stId, n\.schemaClass AS schemaClass/], ['normalpathway', /\[:normalPathway\]/], ['NormalPathway', /\[:normalPathway\]/], - ]) { + ] as const) { asked.length = 0; await get(`query/R-HSA-9/${spelled}`); expect([ @@ -162,11 +171,9 @@ describe("Java's other answers", () => { root = pathway; for (const path of ['query/abridged/R-HSA-9', 'query/R-HSA-9/more']) { const response = await get(path); - expect([path, response.status, (await response.json()).error]).toEqual([ - path, - 404, - 'Not Found', - ]); + expect([path, response.status, ((await response.json()) as { error: string }).error]).toEqual( + [path, 404, 'Not Found'] + ); } }); @@ -178,7 +185,10 @@ describe("Java's other answers", () => { }; lazy = {}; const failed = await get('query/17/simplifiedDatabaseName'); - expect([failed.status, (await failed.json()).reason]).toEqual([500, 'INTERNAL_SERVER_ERROR']); + expect([failed.status, ((await failed.json()) as { reason: string }).reason]).toEqual([ + 500, + 'INTERNAL_SERVER_ERROR', + ]); expect(await messages(await get('query/17/xyz'))).toEqual([ 404, ["Attribute: 'xyz' has not been found for object: ReferenceGeneProduct"], diff --git a/tools/content-node/referrers.spec.mjs b/tools/content-node/referrers.spec.ts similarity index 72% rename from tools/content-node/referrers.spec.mjs rename to tools/content-node/referrers.spec.ts index 03cf3a1b..ecba6bc9 100644 --- a/tools/content-node/referrers.spec.mjs +++ b/tools/content-node/referrers.spec.ts @@ -5,11 +5,16 @@ // ReferralsLinkageRepository.getReferralsTo, by stable id or dbId, and // Java's 404 and 500. The answers' shapes are Java's, compared live on 17 Oct // 2026; the graph here is invented. +import type { Server } from 'node:http'; +import type { AddressInfo } from 'node:net'; import { describe, expect, it, beforeAll, afterAll, vi } from 'vitest'; -const asked = []; +/** A query's parameters, as the service passes them. */ +type Params = { id?: unknown; taxId?: unknown; [name: string]: unknown }; +/** Each query the stand-in graph was asked, with its parameters. */ +const asked: [string, Params][] = []; vi.mock('./graph.ts', () => ({ - read: async (cypher, params) => { + read: async (cypher: string, params: Params) => { asked.push([cypher, params]); // findNewStId: REACT_1 became R-HSA-9; REACT_2 became nothing. if (cypher.includes('oldStId')) return params.id === 'REACT_1' ? [{ stId: 'R-HSA-9' }] : []; @@ -56,19 +61,30 @@ vi.mock('./graph.ts', () => ({ const { app } = await import('./server.ts'); -let base; -let server; +let base: string; +let server: Server; beforeAll(async () => { - await new Promise((resolve) => { - server = app().listen(0, '127.0.0.1', resolve); + await new Promise((resolve) => { + server = app().listen(0, '127.0.0.1', () => resolve()); }); - base = `http://127.0.0.1:${server.address().port}`; + base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`; }); -afterAll(() => new Promise((resolve) => server.close(resolve))); +afterAll(() => new Promise((resolve) => server.close(() => resolve()))); -const get = (id) => fetch(`${base}/ContentService/data/instance/${id}/referrers`); +/** The messages of Java's error envelope. */ +/** The nth query asked; one not asked is the test's failure, not undefined. */ +const query = (at: number) => { + const found = asked[at]; + if (!found) throw new Error(`the graph was not asked a query ${at}`); + return found; +}; + +const messages = async (response: Response) => + ((await response.json()) as { messages: string[] }).messages; + +const get = (id: string) => fetch(`${base}/ContentService/data/instance/${id}/referrers`); describe('/data/instance/{id}/referrers', () => { it('groups what refers to the object by relationship, each a simple object', async () => { @@ -78,25 +94,25 @@ describe('/data/instance/{id}/referrers', () => { '[{"referral":"hasComponent","objects":[{"dbId":7,"stId":"R-HSA-7","displayName":"A complex","schemaClass":"Complex"}]},' + '{"referral":"updatedInstance","objects":[{"dbId":8,"displayName":"An update","schemaClass":"UpdateTracker"}]}]' ); - expect(asked[0][0]).toMatch(/\(d:DatabaseObject\{stId:\$id\}\)<-\[rel\]-\(ref\)/); + expect(query(0)[0]).toMatch(/\(d:DatabaseObject\{stId:\$id\}\)<-\[rel\]-\(ref\)/); // graph-core's exclusions, all seven, and no edit history. - expect(asked[0][0]).toContain('NOT ref:InstanceEdit'); + expect(query(0)[0]).toContain('NOT ref:InstanceEdit'); // The class from the node's own property, as SimpleDatabaseObject.build(Value) reads it. - expect(asked[0][0]).toContain('schemaClass: ref.schemaClass'); - expect(asked[0][0]).toContain( + expect(query(0)[0]).toContain('schemaClass: ref.schemaClass'); + expect(query(0)[0]).toContain( 'NOT (d)<-[:species|compartment|includedLocation|referenceDatabase|evidenceType|reviewStatus|previousReviewStatus]-(ref)' ); // Groups by relationship, each once, at most 1000. - expect(asked[0][0]).toMatch(/RETURN DISTINCT TYPE\(rel\) AS referral,/); - expect(asked[0][0]).toMatch(/LIMIT 1000$/); - expect(asked[0][1]).toEqual({ id: 'R-HSA-141433' }); + expect(query(0)[0]).toMatch(/RETURN DISTINCT TYPE\(rel\) AS referral,/); + expect(query(0)[0]).toMatch(/LIMIT 1000$/); + expect(query(0)[1]).toEqual({ id: 'R-HSA-141433' }); }); it("is Java's 500 for a dbId past a long's range, as Long.parseLong refuses it", async () => { asked.length = 0; for (const id of ['99999999999999999999', '99999999999999999999.1']) { const response = await get(id); - expect([response.status, (await response.json()).messages]).toEqual([ + expect([response.status, await messages(response)]).toEqual([ 500, ['For input string: "99999999999999999999"'], ]); @@ -107,10 +123,10 @@ describe('/data/instance/{id}/referrers', () => { const echoed = await fetch( `${base}/ContentService/data/instance/%20${encodeURIComponent(arabic)}.1%20/referrers` ); - expect((await echoed.json()).messages).toEqual([`For input string: "${arabic}"`]); + expect(await messages(echoed)).toEqual([`For input string: "${arabic}"`]); // One past the largest long. const past = await get('9223372036854775808'); - expect((await past.json()).messages).toEqual(['For input string: "9223372036854775808"']); + expect(await messages(past)).toEqual(['For input string: "9223372036854775808"']); expect(asked).toHaveLength(0); const largest = await get('9223372036854775807'); expect(largest.status).toBe(200); @@ -124,8 +140,8 @@ describe('/data/instance/{id}/referrers', () => { it('reads a dbId as a dbId, version cut, as graph-core does', async () => { asked.length = 0; await get('141433.2'); - expect(asked[0][0]).toMatch(/\{dbId:toInteger\(\$id\)\}/); - expect(asked[0][1]).toEqual({ id: '141433' }); + expect(query(0)[0]).toMatch(/\{dbId:toInteger\(\$id\)\}/); + expect(query(0)[1]).toEqual({ id: '141433' }); }); it('is an empty list when nothing refers to it, and the 404 for no identifier', async () => { @@ -145,7 +161,7 @@ describe('/data/instance/{id}/referrers', () => { await get('REACT_1.3'); expect(asked.map(([, params]) => params.id)).toEqual(['REACT_1', 'R-HSA-9']); // Then by the new id as a stable id. - expect(asked[1][0]).toMatch(/\(d:DatabaseObject\{stId:\$id\}\)/); + expect(query(1)[0]).toMatch(/\(d:DatabaseObject\{stId:\$id\}\)/); asked.length = 0; await get('REACT_2'); expect(asked.map(([, params]) => params.id)).toEqual(['REACT_2', 'REACT_2']); diff --git a/tools/content-node/schema-objects.spec.mjs b/tools/content-node/schema-objects.spec.ts similarity index 67% rename from tools/content-node/schema-objects.spec.mjs rename to tools/content-node/schema-objects.spec.ts index dbe1ccaa..addda1c9 100644 --- a/tools/content-node/schema-objects.spec.mjs +++ b/tools/content-node/schema-objects.spec.ts @@ -6,12 +6,23 @@ // as /min (springInteger page then offset, Java's 500 for a class it lacks, // 404 for none), but at most 25 to a page, and the nodes returned whole. // The objects here are invented. +import type { Server } from 'node:http'; +import type { AddressInfo } from 'node:net'; import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; -const asked = []; -let rows = []; +/** A query's parameters, as the service passes them. */ +type Params = { + id?: unknown; + taxId?: unknown; + skip?: unknown; + limit?: unknown; + [name: string]: unknown; +}; +/** Each query the stand-in graph was asked, with its parameters. */ +const asked: [string, Params][] = []; +let rows: unknown[] = []; vi.mock('./graph.ts', () => ({ - read: async (cypher, params) => { + read: async (cypher: string, params: Params) => { asked.push([cypher, params]); return rows; }, @@ -22,18 +33,29 @@ vi.mock('./graph.ts', () => ({ const { app } = await import('./server.ts'); -let base; -let server; +let base: string; +let server: Server; beforeAll(async () => { - await new Promise((resolve) => { - server = app().listen(0, '127.0.0.1', resolve); + await new Promise((resolve) => { + server = app().listen(0, '127.0.0.1', () => resolve()); }); - base = `http://127.0.0.1:${server.address().port}`; + base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`; }); -afterAll(() => new Promise((resolve) => server.close(resolve))); +afterAll(() => new Promise((resolve) => server.close(() => resolve()))); -const get = (query) => fetch(`${base}/ContentService/data/schema/${query}`); -const pathway = (id, name) => ({ +/** The messages of Java's error envelope. */ +/** The nth query asked; one not asked is the test's failure, not undefined. */ +const query = (at: number) => { + const found = asked[at]; + if (!found) throw new Error(`the graph was not asked a query ${at}`); + return found; +}; + +const messages = async (response: Response) => + ((await response.json()) as { messages: string[] }).messages; + +const get = (path: string) => fetch(`${base}/ContentService/data/schema/${path}`); +const pathway = (id: number, name: string) => ({ n: { dbId: id, stId: `R-HSA-${id}`, displayName: name, schemaClass: 'Pathway' }, labels: ['DatabaseObject', 'Event', 'Pathway'], id, @@ -44,8 +66,8 @@ describe('/data/schema/{className}', () => { rows = [pathway(2, 'A'), pathway(1, 'B')]; asked.length = 0; const response = await get('Pathway?page=2&offset=10'); - expect((await response.json()).map((o) => o.dbId)).toEqual([2, 1]); - const [cypher, params] = asked[0]; + expect(((await response.json()) as { dbId: number }[]).map((o) => o.dbId)).toEqual([2, 1]); + const [cypher, params] = query(0); expect(cypher).toMatch(/^MATCH \(n:Pathway\) RETURN n, /); expect(cypher).toMatch(/ ORDER BY n\.displayName SKIP \$skip LIMIT \$limit$/); expect([Number(params.skip), Number(params.limit)]).toEqual([10, 10]); @@ -55,17 +77,17 @@ describe('/data/schema/{className}', () => { rows = [pathway(1, 'A')]; asked.length = 0; await get('Pathway?page=3&offset=1000'); - expect([Number(asked[0][1].skip), Number(asked[0][1].limit)]).toEqual([50, 25]); + expect([Number(query(0)[1].skip), Number(query(0)[1].limit)]).toEqual([50, 25]); }); it('filters by species, by taxId when it is a number', async () => { rows = [pathway(1, 'A')]; asked.length = 0; await get('Pathway?page=1&offset=5&species=9606'); - expect(asked[0][0]).toMatch( + expect(query(0)[0]).toMatch( /^MATCH \(s:Species\{taxId:\$taxId\}\)<-\[:species\]-\(n:Pathway\)/ ); - expect(asked[0][1].taxId).toBe('9606'); + expect(query(0)[1].taxId).toBe('9606'); }); it('answers as Java does: 400 without a page, 500 for a class it lacks, 404 for none', async () => { @@ -75,7 +97,7 @@ describe('/data/schema/{className}', () => { expect(unknown.status).toBe(500); rows = []; const none = await get('Pathway?page=99&offset=5'); - expect([none.status, (await none.json()).messages]).toEqual([ + expect([none.status, await messages(none)]).toEqual([ 404, ['No entries found for class: Pathway'], ]); @@ -93,7 +115,7 @@ describe('/data/schema/{className}/reference', () => { expect(await response.text()).toBe( '[{"dbId":2,"identifier":"10036","databaseName":"ChEBI"},{"dbId":1,"identifier":"null"}]' ); - const [cypher, params] = asked[0]; + const [cypher, params] = query(0); expect(cypher).toBe( 'MATCH (n:ReferenceMolecule) RETURN n.dbId AS dbId, n.databaseName AS databaseName, n.identifier AS identifier ORDER BY n.identifier SKIP $skip LIMIT $limit' ); @@ -104,19 +126,19 @@ describe('/data/schema/{className}/reference', () => { rows = [{ dbId: 1, identifier: 'x', databaseName: 'y' }]; asked.length = 0; await get('ExternalOntology/reference?page=1&offset=50000&species=9606'); - expect(asked[0][0]).not.toMatch(/Species/); - expect(Number(asked[0][1].limit)).toBe(20000); + expect(query(0)[0]).not.toMatch(/Species/); + expect(Number(query(0)[1].limit)).toBe(20000); }); it('is a 404 for a class that is no reference, a 500 for one Java lacks', async () => { rows = [{ dbId: 1, identifier: 'x', databaseName: 'y' }]; const pathway = await get('Pathway/reference?page=1&offset=3'); - expect([pathway.status, (await pathway.json()).messages]).toEqual([ + expect([pathway.status, await messages(pathway)]).toEqual([ 404, ['No entries found for class: Pathway'], ]); const unknown = await get('Nonsense/reference?page=1&offset=3'); - expect([unknown.status, (await unknown.json()).messages]).toEqual([ + expect([unknown.status, await messages(unknown)]).toEqual([ 500, ['Specified class was not found'], ]); From 59c2eb6f76d6aa5b864801679d5c90ff6b72cf7b Mon Sep 17 00:00:00 2001 From: Adam Wright Date: Sat, 24 Oct 2026 02:22:22 +0000 Subject: [PATCH 2/2] T023 batch 3: the messages helpers' doc comments back on them (from the review) Co-Authored-By: Claude Opus 5.5 --- tools/content-node/referrers.spec.ts | 2 +- tools/content-node/schema-objects.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/content-node/referrers.spec.ts b/tools/content-node/referrers.spec.ts index ecba6bc9..4f9e6226 100644 --- a/tools/content-node/referrers.spec.ts +++ b/tools/content-node/referrers.spec.ts @@ -73,7 +73,6 @@ beforeAll(async () => { afterAll(() => new Promise((resolve) => server.close(() => resolve()))); -/** The messages of Java's error envelope. */ /** The nth query asked; one not asked is the test's failure, not undefined. */ const query = (at: number) => { const found = asked[at]; @@ -81,6 +80,7 @@ const query = (at: number) => { return found; }; +/** The messages of Java's error envelope. */ const messages = async (response: Response) => ((await response.json()) as { messages: string[] }).messages; diff --git a/tools/content-node/schema-objects.spec.ts b/tools/content-node/schema-objects.spec.ts index addda1c9..73c0c413 100644 --- a/tools/content-node/schema-objects.spec.ts +++ b/tools/content-node/schema-objects.spec.ts @@ -43,7 +43,6 @@ beforeAll(async () => { }); afterAll(() => new Promise((resolve) => server.close(() => resolve()))); -/** The messages of Java's error envelope. */ /** The nth query asked; one not asked is the test's failure, not undefined. */ const query = (at: number) => { const found = asked[at]; @@ -51,6 +50,7 @@ const query = (at: number) => { return found; }; +/** The messages of Java's error envelope. */ const messages = async (response: Response) => ((await response.json()) as { messages: string[] }).messages;