test(teams): the refusals, which is most of what a notification feature is
A notification feature is mostly things that correctly do NOT happen, and each of these is invisible until it goes wrong in production: a departed member and a revoked guest are not recipients; a mute subtracts per Team and leaves the user's other Teams alone; the author of a post never receives the notification about it; forums switched off silences the forum streams including the digest; a Team's first roster wakes nobody; a failed send does not stamp `last_digest_at`. Two real defects came out of writing them. `Number(null)` is 0 and 0 is an integer, so a null in a caller's id list survived `filter(Number.isInteger)` and rode into an IN clause as user id 0. No row has id 0, so it was harmless — which is exactly why it would never have been noticed. Fixed in all three places that filter ids. `recipientIds: db.recipientIds` in the model captured the function OBJECT at require time, so the layer below could never be substituted. That is not only untestable; it means the model was not really the seam it claimed to be. Wrapped so `db.x` resolves at call time. The registries catalog assertion is now an exact five-element list, so a shard-content stream creeping back into core's registration fails here rather than shipping. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -309,6 +309,39 @@ test('a non-function projectRoster is rejected at registration, not at call time
|
||||
)
|
||||
})
|
||||
|
||||
// ── pageUrlTemplate: the optional fifth member (§6.4) ──────────────────────
|
||||
//
|
||||
// Data, not a method, and the only thing core can use to link to a Team page —
|
||||
// Teams have no core surface, so the module that owns the page has to say where
|
||||
// it is. Validated hard because the output goes into an email as a link.
|
||||
|
||||
test('pageUrlTemplate is optional: a provider without it registers fine', () => {
|
||||
const api = registries.stage('uo')
|
||||
assert.doesNotThrow(() => api.registerTeamProvider(ok()))
|
||||
})
|
||||
|
||||
test('a relative template is kept exactly as given', () => {
|
||||
register('uo', { ...ok(), pageUrlTemplate: '/uo/guilds/{externalId}' })
|
||||
assert.equal(registries.registeredTeamProvider().pageUrlTemplate, '/uo/guilds/{externalId}')
|
||||
})
|
||||
|
||||
test('an absolute template is refused — a module may not redirect the site’s mail', () => {
|
||||
const api = registries.stage('uo')
|
||||
for (const bad of [
|
||||
'https://evil.test/{externalId}',
|
||||
'//evil.test/x',
|
||||
'uo/guilds/{externalId}', // not rooted
|
||||
'/uo/guilds/{externalId}?x=<script>',
|
||||
42,
|
||||
]) {
|
||||
assert.throws(
|
||||
() => api.registerTeamProvider({ ...ok(), pageUrlTemplate: bad }),
|
||||
/pageUrlTemplate must be a relative path/,
|
||||
`expected "${bad}" to be refused`,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
test('an unregistered method cannot ride along into the provider core calls', () => {
|
||||
register('uo', { ...ok(), somethingElse: async () => 'hi' })
|
||||
assert.equal(registries.registeredTeamProvider().somethingElse, undefined)
|
||||
|
||||
Reference in New Issue
Block a user