|
17 | 17 | * so the name could only ever be refused again at bind (the binder's |
18 | 18 | * `metadata-service` refusal, pinned in objectql's |
19 | 19 | * `hook-binder-package-scope.test.ts`). |
| 20 | + * ②c the same door, the other shape with no `body` (#21689): a hook with |
| 21 | + * neither a `body` nor a `handler`, which the binder would skip at every |
| 22 | + * re-sync, is refused by the same predicate with the same envelope. One |
| 23 | + * predicate judges both shapes: a hook with no `body` object. |
| 24 | + * |
| 25 | + * The enumeration this file holds on the real door: a `body` hook saves (②b), |
| 26 | + * a `handler`-only hook is refused (②), a hook with neither is refused (②c), |
| 27 | + * and a built artifact's `handler` hook through its own door is unchanged |
| 28 | + * (the X and Z controls). |
20 | 29 | * |
21 | 30 | * Controls, the two shapes a package's own function takes: app X's hook naming |
22 | 31 | * X's own `functions` entry binds and runs, and app Z's hook naming a function |
@@ -145,6 +154,13 @@ function refusalsOf(hook: string): any[][] { |
145 | 154 | return engineLogger.error.mock.calls.filter((call: any[]) => call[2]?.hook === hook); |
146 | 155 | } |
147 | 156 |
|
| 157 | +/** The binder's skips of `hook` (a hook with nothing to bind), as the engine logged them. */ |
| 158 | +function skipsOf(hook: string): any[][] { |
| 159 | + return engineLogger.warn.mock.calls.filter( |
| 160 | + (call: any[]) => String(call[0]).includes('skipping hook') && call[1]?.hook === hook, |
| 161 | + ); |
| 162 | +} |
| 163 | + |
148 | 164 | async function waitFor(predicate: () => Promise<boolean>, ms = 15_000): Promise<boolean> { |
149 | 165 | const until = Date.now() + ms; |
150 | 166 | while (Date.now() < until) { |
@@ -248,6 +264,23 @@ describe('a hook handler name resolves inside its own package only — composed |
248 | 264 | expect(stored.status, await stored.text()).toBe(404); |
249 | 265 | }); |
250 | 266 |
|
| 267 | + it('②c the metadata door refuses a runtime-authored hook with neither a `body` nor a `handler`: VALIDATION_ERROR / 400, nothing stored', async () => { |
| 268 | + // The measured `PUT`: before #21689 it answered 200, the row was served by |
| 269 | + // name, and the binder skipped it at every re-sync. |
| 270 | + const bareHook = await asAdmin('PUT', '/meta/hook/scope_authored_bare', { |
| 271 | + name: 'scope_authored_bare', |
| 272 | + object: Y_NOTE, |
| 273 | + events: ['beforeInsert'], |
| 274 | + }); |
| 275 | + const refusal: any = await bareHook.json(); |
| 276 | + expect({ status: bareHook.status, code: refusal?.code }, JSON.stringify(refusal)) |
| 277 | + .toEqual({ status: 400, code: 'VALIDATION_ERROR' }); |
| 278 | + expect(refusal.error).toContain("'scope_authored_bare'"); |
| 279 | + expect(refusal.error).toContain('Give it a `body`'); |
| 280 | + const stored = await asAdmin('GET', '/meta/hook/scope_authored_bare'); |
| 281 | + expect(stored.status, await stored.text()).toBe(404); |
| 282 | + }); |
| 283 | + |
251 | 284 | it('②b a body hook authored through the metadata door saves, binds and runs; so does one carrying both a `body` and a `handler`', async () => { |
252 | 285 | const bodyHook = await asAdmin('PUT', '/meta/hook/scope_authored_body', { |
253 | 286 | name: 'scope_authored_body', |
@@ -279,9 +312,11 @@ describe('a hook handler name resolves inside its own package only — composed |
279 | 312 | expect(refusalsOf('scope_authored_both')).toEqual([]); |
280 | 313 | }, 30_000); |
281 | 314 |
|
282 | | - it('② nothing bound: once the re-sync has run (②b), the refused hook never reached the binder', async () => { |
| 315 | + it('② and ②c nothing bound: once the re-sync has run (②b), neither refused hook reached the binder', async () => { |
283 | 316 | // There was no row for the re-sync to bind, so the binder recorded no |
284 | | - // refusal of it either: the door refused it before anything was stored. |
| 317 | + // refusal or skip of either: the door refused both before anything was stored. |
285 | 318 | expect(refusalsOf('scope_authored_cross')).toEqual([]); |
| 319 | + expect(refusalsOf('scope_authored_bare')).toEqual([]); |
| 320 | + expect(skipsOf('scope_authored_bare')).toEqual([]); |
286 | 321 | }); |
287 | 322 | }); |
0 commit comments