27 tests on the file standing between a member and a shell
authorize.ts had none, and its own header is what says why that mattered: every deny path in the platform ends there, and the failure mode of a permissive default is not a bug report, it is someone else's session. It had none for a structural reason rather than an oversight. Every path needed two database calls, and the only file here that mocks officerdb leaks its stub into whatever runs after it — so the obvious approach would have traded one untested file for a flaky suite. So the decisions are split from the fetching. resolveEffectivePermissions, decideApiRequest and decideWsProvider are pure functions of their arguments; the async wrappers keep the role cache, the fail-closed catches and the shape callers use. No behaviour change, and the live gate still answers 401 on file-browser, users and tasks. REAL registry keys throughout, deliberately: a suite built on invented permissions keeps passing after a rename, which is the failure an authorization table can least afford. If a key here disappears this file goes red. Pinned beyond the happy paths — the owner carries no grants to consult; core is write unconditionally; a confined grant is dropped without a Linux user and all three drop together; execution and admin grants in the table are ignored even though no CHECK stops the row existing; an unknown key is ignored without disturbing what is legitimately held; self-service is exact in method AND path; a socket needs no level; an unauthenticated caller is refused every door. Two gaps are stated in the file rather than left to be found: ownerOnly has no declaring permission yet, so testing it would test the fixtures; and the grant cache's contract is a claim about call sites, not logic. Found while writing them: readOnlyWrites paths are relative to the permission's api prefix, and getting that wrong fails as "no permission covers this path" rather than as a level error — a misleading message for a misleading path, now commented at the test that hit it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
---
|
||||
id: multi-user/permissions-authorize-ts-has-no-automated
|
||||
title: "`permissions/authorize.ts` has no automated tests"
|
||||
title: '`permissions/authorize.ts` has no automated tests'
|
||||
theme: multi-user
|
||||
status: open
|
||||
status: done
|
||||
created: 2026-08-20
|
||||
date_source: assumed
|
||||
order: 11
|
||||
@@ -12,3 +12,32 @@ order: 11
|
||||
registry functions and the totality check; the resolver that does the owner bypass, the grant
|
||||
lookup, the role cache and the fail-closed catches is exercised only by hand. It is the file
|
||||
standing between a Member and a shell.
|
||||
|
||||
---
|
||||
|
||||
**Done 2026-08-22. 27 tests, no mock and no database.**
|
||||
|
||||
The reason it had none was structural: every path through the resolver needed two database calls, and the
|
||||
only file in this repository that mocks `officerdb` leaks its stub into whatever runs after it — so the
|
||||
obvious approach would have traded one untested file for a flaky suite.
|
||||
|
||||
So the decisions were split from the fetching. `resolveEffectivePermissions(user, roleGrants)`,
|
||||
`decideApiRequest(effective, method, path)` and `decideWsProvider(effective, provider)` are pure; the async
|
||||
functions keep the caching, the fail-closed catches and the shape callers use. The tests use REAL registry
|
||||
keys throughout, deliberately — a suite built on invented permissions keeps passing after a rename, which is
|
||||
the failure an authorization table can least afford.
|
||||
|
||||
What is pinned, beyond the happy paths: the owner carries no grants to consult; core is `write`
|
||||
unconditionally; a confined grant is dropped without a Linux user, and all three confined permissions drop
|
||||
together; execution and admin grants in the table are ignored even though no CHECK stops the row existing;
|
||||
an unknown key is ignored without disturbing what is legitimately held; self-service is exact in both method
|
||||
and path; `readOnlyWrites` is relative to the permission's prefix; a socket needs no level; and an
|
||||
unauthenticated caller is refused every door.
|
||||
|
||||
Two things deliberately NOT covered, and said so in the file rather than left to be noticed: `ownerOnly`,
|
||||
because no permission declares it yet and covering it would test the fixtures rather than the registry; and
|
||||
the grant cache, whose contract is a claim about call sites rather than logic.
|
||||
|
||||
Found while writing them: `readOnlyWrites` paths are relative to the permission's api prefix, and getting
|
||||
that wrong fails as "no permission covers this path" rather than as a level error — a misleading message for
|
||||
a misleading path.
|
||||
|
||||
Reference in New Issue
Block a user