@@ -65,6 +65,7 @@ import { assertEngineDeleteDispatch, assertEngineUpdateDispatch } from '@objects
6565import { ObjectStackProtocolImplementation } from '@objectstack/metadata-protocol' ;
6666import { DEFAULT_METADATA_TYPE_REGISTRY } from '@objectstack/spec/kernel' ;
6767import { HttpDispatcher } from './http-dispatcher.js' ;
68+ import type { HttpDispatcherResult } from './http-dispatcher.js' ;
6869import { declaresOrgOverride , organizationIdForMetaWrite } from './meta-write-org-scope.js' ;
6970
7071const ACTIVE_ORG = 'org_alpha' ;
@@ -279,16 +280,28 @@ const VIEW = {
279280/** `allowOrgOverride: false` — the ADR-0045 publish-visibility specimen. */
280281const APP = { name : 'crm' , label : 'CRM' } ;
281282
283+ /**
284+ * The dispatcher's `response` is optional on `HttpDispatcherResult` — a route
285+ * that declines answers `{ handled: false }`. Every case here drives a route
286+ * that MUST answer, so an absent response is a failure of the harness rather
287+ * than a value to narrow around at each call site: this says so once, loudly,
288+ * and hands back a response the assertions can read.
289+ */
290+ function responseOf ( result : HttpDispatcherResult ) : NonNullable < HttpDispatcherResult [ 'response' ] > {
291+ const response = result . response ;
292+ if ( ! response ) throw new Error ( 'the dispatcher handled the route but returned no response' ) ;
293+ return response ;
294+ }
295+
282296describe ( '#7018 — the registry decides whether a metadata write carries the session org' , ( ) => {
283- let warn : ReturnType < typeof vi . spyOn > ;
284297 let error : ReturnType < typeof vi . spyOn > ;
285298
286299 beforeEach ( ( ) => {
287300 // The protocol logs degradation lines on these paths; they are not the
288301 // subject and must not drown the run. `error` is spied rather than
289302 // silenced-and-forgotten — the ADR-0045 flip reports its own failure
290303 // there (#4754), and the last case reads it back.
291- warn = vi . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => { } ) ;
304+ vi . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => { } ) ;
292305 error = vi . spyOn ( console , 'error' ) . mockImplementation ( ( ) => { } ) ;
293306 } ) ;
294307
@@ -317,9 +330,9 @@ describe('#7018 — the registry decides whether a metadata write carries the se
317330 it ( 'a NON-overridable type lands env-wide even though the session has an active org' , async ( ) => {
318331 const { engine, dispatcher } = makeStack ( ACTIVE_ORG ) ;
319332
320- const res = await dispatcher . handleMetadata ( `/flow/${ FLOW . name } ` , ctx ( ) , 'PUT' , FLOW ) ;
333+ const res = responseOf ( await dispatcher . handleMetadata ( `/flow/${ FLOW . name } ` , ctx ( ) , 'PUT' , FLOW ) ) ;
321334
322- expect ( res . response . status ) . toBe ( 200 ) ;
335+ expect ( res . status ) . toBe ( 200 ) ;
323336 const row = metaRow ( engine , 'flow' , FLOW . name ) ;
324337 expect ( row ) . toBeDefined ( ) ;
325338 // THE assertion. Before #7018 this was `'org_alpha'` — a row
@@ -337,9 +350,9 @@ describe('#7018 — the registry decides whether a metadata write carries the se
337350 fields : { subject : { type : 'text' , label : 'Subject' } } ,
338351 } ;
339352
340- const res = await dispatcher . handleMetadata ( `/object/${ OBJECT . name } ` , ctx ( ) , 'PUT' , OBJECT ) ;
353+ const res = responseOf ( await dispatcher . handleMetadata ( `/object/${ OBJECT . name } ` , ctx ( ) , 'PUT' , OBJECT ) ) ;
341354
342- expect ( res . response . status ) . toBe ( 200 ) ;
355+ expect ( res . status ) . toBe ( 200 ) ;
343356 expect ( metaRow ( engine , 'object' , OBJECT . name ) ! . organization_id ) . toBeNull ( ) ;
344357 } ) ;
345358
@@ -352,20 +365,20 @@ describe('#7018 — the registry decides whether a metadata write carries the se
352365 const withOrg = makeStack ( ACTIVE_ORG ) ;
353366 const withoutOrg = makeStack ( undefined ) ;
354367
355- const a = await withOrg . dispatcher . handleMetadata ( `/flow/${ FLOW . name } ` , ctx ( ) , 'PUT' , FLOW ) ;
356- const b = await withoutOrg . dispatcher . handleMetadata ( `/flow/${ FLOW . name } ` , ctx ( ) , 'PUT' , FLOW ) ;
368+ const a = responseOf ( await withOrg . dispatcher . handleMetadata ( `/flow/${ FLOW . name } ` , ctx ( ) , 'PUT' , FLOW ) ) ;
369+ const b = responseOf ( await withoutOrg . dispatcher . handleMetadata ( `/flow/${ FLOW . name } ` , ctx ( ) , 'PUT' , FLOW ) ) ;
357370
358- expect ( a . response . status ) . toBe ( 200 ) ;
359- expect ( a . response . body . data ) . toEqual ( b . response . body . data ) ;
360- expect ( a . response . body . data ) . toMatchObject ( { success : true , state : 'active' } ) ;
371+ expect ( a . status ) . toBe ( 200 ) ;
372+ expect ( a . body . data ) . toEqual ( b . body . data ) ;
373+ expect ( a . body . data ) . toMatchObject ( { success : true , state : 'active' } ) ;
361374 } ) ;
362375
363376 it ( 'CONTROL — an `allowOrgOverride: true` type keeps its org scoping exactly as before' , async ( ) => {
364377 const { engine, dispatcher } = makeStack ( ACTIVE_ORG ) ;
365378
366- const res = await dispatcher . handleMetadata ( `/view/${ VIEW . name } ` , ctx ( ) , 'PUT' , VIEW ) ;
379+ const res = responseOf ( await dispatcher . handleMetadata ( `/view/${ VIEW . name } ` , ctx ( ) , 'PUT' , VIEW ) ) ;
367380
368- expect ( res . response . status ) . toBe ( 200 ) ;
381+ expect ( res . status ) . toBe ( 200 ) ;
369382 // ADR-0005's per-org overlay is the point of the flag and must survive
370383 // this change untouched — `getMetaItem`/`getMetaItems` load it on demand.
371384 expect ( metaRow ( engine , 'view' , VIEW . name ) ! . organization_id ) . toBe ( ACTIVE_ORG ) ;
@@ -374,9 +387,9 @@ describe('#7018 — the registry decides whether a metadata write carries the se
374387 it ( 'CONTROL — the plural URL spelling of an overridable type is scoped the same way' , async ( ) => {
375388 const { engine, dispatcher } = makeStack ( ACTIVE_ORG ) ;
376389
377- const res = await dispatcher . handleMetadata ( `/views/${ VIEW . name } ` , ctx ( ) , 'PUT' , VIEW ) ;
390+ const res = responseOf ( await dispatcher . handleMetadata ( `/views/${ VIEW . name } ` , ctx ( ) , 'PUT' , VIEW ) ) ;
378391
379- expect ( res . response . status ) . toBe ( 200 ) ;
392+ expect ( res . status ) . toBe ( 200 ) ;
380393 expect ( metaRow ( engine , 'view' , VIEW . name ) ! . organization_id ) . toBe ( ACTIVE_ORG ) ;
381394 } ) ;
382395
@@ -403,11 +416,11 @@ describe('#7018 — the registry decides whether a metadata write carries the se
403416 success : true , publishedCount : 0 , failedCount : 0 , published : [ ] , failed : [ ] ,
404417 } ) ;
405418
406- const res = await dispatcher . handlePackages ( '/crm_pkg/publish-drafts' , 'POST' , { } , { } , ctx ( ) ) ;
419+ const res = responseOf ( await dispatcher . handlePackages ( '/crm_pkg/publish-drafts' , 'POST' , { } , { } , ctx ( ) ) ) ;
407420
408- expect ( res . response . status ) . toBe ( 200 ) ;
409- expect ( res . response . body . data . unhiddenApps ) . toEqual ( [ APP . name ] ) ;
410- expect ( res . response . body . data . unhideError ) . toBeUndefined ( ) ;
421+ expect ( res . status ) . toBe ( 200 ) ;
422+ expect ( res . body . data . unhiddenApps ) . toEqual ( [ APP . name ] ) ;
423+ expect ( res . body . data . unhideError ) . toBeUndefined ( ) ;
411424
412425 // One row, still env-wide — not a second, org-scoped row shadowing it.
413426 const appRows = engine . metaRows ( ) . filter ( ( r : any ) => r . type === 'app' && r . state === 'active' ) ;
@@ -438,12 +451,12 @@ describe('#7018 — the registry decides whether a metadata write carries the se
438451 } ) ;
439452 error . mockClear ( ) ;
440453
441- const res = await dispatcher . handlePackages ( '/crm_pkg/publish-drafts' , 'POST' , { } , { } , ctx ( ) ) ;
454+ const res = responseOf ( await dispatcher . handlePackages ( '/crm_pkg/publish-drafts' , 'POST' , { } , { } , ctx ( ) ) ) ;
442455
443- expect ( res . response . body . data . unhiddenApps ) . toEqual ( [ APP . name ] ) ;
444- const flipComplaints = error . mock . calls
456+ expect ( res . body . data . unhiddenApps ) . toEqual ( [ APP . name ] ) ;
457+ const flipComplaints = ( error . mock . calls as unknown [ ] [ ] )
445458 . map ( ( c ) => String ( c [ 0 ] ) )
446- . filter ( ( line ) => line . includes ( 'visibility flip' ) ) ;
459+ . filter ( ( line : string ) => line . includes ( 'visibility flip' ) ) ;
447460 expect ( flipComplaints ) . toEqual ( [ ] ) ;
448461 } ) ;
449462} ) ;
0 commit comments