@@ -170,6 +170,42 @@ const PHANTOM_PATHS = [
170170 '/api/.well-known/objectstack' ,
171171] ;
172172
173+ /**
174+ * The `paths` section the static artifact carried until #5744 — rebuilt here
175+ * as a FIXTURE, because the artifact no longer supplies one.
176+ *
177+ * #5821 pinned "no artifact path leaks into the served document" by reading
178+ * the artifact's OWN `paths` and looping over them. That was the right input
179+ * while spec still emitted a route section. #5744 then removed the emission —
180+ * correctly, ADR-0076: a section can only be produced by the package that
181+ * mounts the routes — and the pin's input silently became the empty set.
182+ * Re-measured on this branch through the same `loadOpenApiSpec` seam the pin
183+ * uses (#6894):
184+ *
185+ * #5821 pin input set (stale paths): [] -> size 0
186+ * loop body executions: 0
187+ *
188+ * A `for` over nothing asserts nothing, and the case stayed green saying so.
189+ * The other direction — waiting for the artifact to carry a route section
190+ * again — would mean asking the producer to keep a removed defect alive in
191+ * order to feed a test, so the input is constructed here instead.
192+ */
193+ function staleArtifactPaths ( ) : Record < string , any > {
194+ const operation = ( operationId : string ) => ( {
195+ operationId,
196+ responses : { '200' : { description : 'OK' } } ,
197+ } ) ;
198+ const section : Record < string , any > = { } ;
199+ for ( const path of PHANTOM_PATHS ) {
200+ section [ path ] = { get : operation ( `stale ${ path } ` ) } ;
201+ }
202+ // The historical section's other defect, kept so the fixture is the shape
203+ // that really shipped rather than a uniform one: it documented PUT on a
204+ // record, a verb this server answers 405 to.
205+ section [ '/api/{object}/{id}' ] . put = operation ( 'stale put record' ) ;
206+ return section ;
207+ }
208+
173209describe ( '#5588 — built-in routes come from rest, not from the static artifact' , ( ) => {
174210 it ( 'publishes no path the server does not mount' , async ( ) => {
175211 // The converse of the bug, asserted as a set relation rather than by
@@ -273,23 +309,53 @@ describe('#5588 — built-in routes come from rest, not from the static artifact
273309 expect ( names ) . toEqual ( [ 'environmentId' , 'object' ] ) ;
274310 } ) ;
275311
276- it ( 'discards the static artifact section even though spec still emits it' , async ( ) => {
277- // Leg 2 (#5744) removes the spec-side generation. Until it lands, the
278- // bundled artifact really does carry `/api/{object}` & co — so the serve
279- // path has to DISCARD, not merge. Proven against the artifact this
280- // runtime actually loads.
312+ it ( 'passes the half of the document `packages/spec` owns through serve untouched' , async ( ) => {
313+ // The artifact's surviving half — `components.schemas`, `securitySchemes`,
314+ // `info` — is the contract, and serve-time enrichment must not touch it.
281315 const rest = makeRest ( makeProtocol ( { object : [ ] , api : [ ] } ) . protocol ) ;
282316 const artifact = await ( rest as any ) . loadOpenApiSpec ( ) ;
283317 expect ( artifact , 'the bundled artifact must be loadable for this pin to mean anything' ) . toBeTruthy ( ) ;
284- const stale = Object . keys ( artifact . paths ?? { } ) ;
318+ // Recorded rather than assumed, because the pin below depends on it: since
319+ // #5744 the artifact is the contract half ONLY and describes no routes, so
320+ // the discard case has to plant its own section to have anything to check.
321+ expect (
322+ artifact . paths ,
323+ 'since #5744 the artifact emits no route section — if this ever comes back, feed it to the discard pin below instead of a fixture' ,
324+ ) . toBeUndefined ( ) ;
285325
286326 const { body } = await serveOpenApiFrom ( rest ) ;
287- for ( const path of stale ) {
288- expect ( body . paths [ path ] , `stale artifact path '${ path } ' leaked into the served document` ) . toBeUndefined ( ) ;
289- }
290- // What spec genuinely owns survives untouched.
291327 expect ( Object . keys ( body . components . schemas ) ) . toEqual ( Object . keys ( artifact . components . schemas ) ) ;
292328 expect ( body . components . securitySchemes ) . toEqual ( artifact . components . securitySchemes ) ;
293329 expect ( body . info . title ) . toBe ( artifact . info . title ) ;
294330 } ) ;
331+
332+ it ( 'discards a `paths`-carrying artifact instead of merging it' , async ( ) => {
333+ // #5588 ruling C: the serve path DISCARDS whatever route section the
334+ // static artifact carries rather than merging it, because a merge with a
335+ // wrong section republishes the wrong section. Today's artifact carries
336+ // none (#5744), so the only honest way to keep asserting the discard is to
337+ // hand the handler one that does — see `staleArtifactPaths` for why the
338+ // input is a fixture and not the artifact's own key any more (#6894).
339+ const rest = makeRest ( makeProtocol ( { object : [ ] , api : [ ] } ) . protocol ) ;
340+ const artifact = await ( rest as any ) . loadOpenApiSpec ( ) ;
341+ expect ( artifact , 'the bundled artifact must be loadable for this pin to mean anything' ) . toBeTruthy ( ) ;
342+ // `loadOpenApiSpec` caches per instance and hands back the cached object
343+ // itself, so planting the section on it is what the handler will read.
344+ artifact . paths = staleArtifactPaths ( ) ;
345+
346+ // Read the input back THROUGH the loader, and require it to be non-empty.
347+ // This assertion is the one that would have caught #6894: it turns "the
348+ // loop found nothing to check" from a silent pass into a failure, so the
349+ // layer cannot go hollow again without saying so.
350+ const stale = Object . keys ( ( await ( rest as any ) . loadOpenApiSpec ( ) ) . paths ?? { } ) ;
351+ expect (
352+ stale . length ,
353+ 'the pin input set is empty — the loop below would assert nothing (#6894)' ,
354+ ) . toBeGreaterThan ( 0 ) ;
355+
356+ const { body } = await serveOpenApiFrom ( rest ) ;
357+ for ( const path of stale ) {
358+ expect ( body . paths [ path ] , `stale artifact path '${ path } ' leaked into the served document` ) . toBeUndefined ( ) ;
359+ }
360+ } ) ;
295361} ) ;
0 commit comments