@@ -34,6 +34,7 @@ const rulesDb = require('../src/model/engagement/engagementRules.db')
const sendsDb = require ( '../src/model/engagement/engagementSends.db' )
const templates = require ( '../src/engagement/templates' )
const registries = require ( '../src/modules/registries' )
const safety = require ( '../src/model/safety/userSafety.model' )
const saved = new Map ( )
@@ -71,6 +72,8 @@ function stub({
recipients = [ 10 , 11 ] ,
emailRows = [ ] ,
sendOk = true ,
blockers = { } , // authorUserId → the members who block them
readerFilter = { } , // userId → { blocked: [...], hiddenPosts: [...], hiddenThreads: [...] }
} = { } ) {
sent = [ ]
emitted = [ ]
@@ -108,6 +111,14 @@ function stub({
// No module registered: the default in most tests, so the link-building ones
// have to opt in and the absence is exercised rather than assumed.
patch ( registries , 'registeredTeamProvider' , ( ) => null )
patch ( safety , 'blockersOf' , async ( authorId ) => blockers [ authorId ] || [ ] )
patch ( safety , 'forumFilter' , async ( userId ) => {
const f = readerFilter [ userId ] || { }
return {
blockedUserIds : new Set ( f . blocked || [ ] ) ,
hidden : { team _forum _thread : new Set ( f . hiddenThreads || [ ] ) , team _forum _post : new Set ( f . hiddenPosts || [ ] ) } ,
}
} )
}
beforeEach ( ( ) => stub ( ) )
@@ -142,6 +153,32 @@ test('the author is not among the tickled, nor among the emitted audience', asyn
assert . deepEqual ( emitted [ 0 ] . recipientUserIds , [ 11 ] )
} )
// USER_SAFETY.md §4.1: the forum leaves a blocked author's posts out of a reader's
// view, so the notification must not point that reader at one.
test ( 'a member who blocks the author is not tickled or told' , async ( ) => {
stub ( { recipients : [ 10 , 11 , 12 ] , blockers : { 10 : [ 12 ] } } )
await notify . forumPost ( { team : TEAM , threadId : 7 , threadTitle : 'Hi' , type : 'discussion' , authorUserId : 10 } )
assert . deepEqual ( sent [ 0 ] . userIds , [ 11 ] )
assert . deepEqual ( emitted [ 0 ] . recipientUserIds , [ 11 ] )
} )
test ( 'a member who blocks the thread’ s starter is not told about replies in it' , async ( ) => {
// A reply by 11 in a thread 10 started; 12 blocks 10, so cannot open the thread.
stub ( { recipients : [ 10 , 11 , 12 ] , blockers : { 10 : [ 12 ] } } )
await notify . forumPost ( {
team : TEAM , threadId : 7 , threadTitle : 'Hi' , type : 'discussion' , authorUserId : 11 , threadAuthorUserId : 10 ,
} )
assert . deepEqual ( sent [ 0 ] . userIds , [ 10 ] )
assert . deepEqual ( emitted [ 0 ] . recipientUserIds , [ 10 ] )
} )
test ( 'a block lookup that fails still notifies everyone else' , async ( ) => {
patch ( safety , 'blockersOf' , async ( ) => { throw new Error ( 'database is on fire' ) } )
const res = await notify . forumPost ( { team : TEAM , threadId : 7 , threadTitle : 'Hi' , type : 'discussion' , authorUserId : 10 } )
assert . equal ( res . push , 1 )
assert . deepEqual ( emitted [ 0 ] . recipientUserIds , [ 11 ] )
} )
test ( 'roster events fire one tickle for the run, not one per member' , async ( ) => {
await notify . memberJoined ( TEAM )
await notify . leadershipChanged ( TEAM )
@@ -312,6 +349,29 @@ test('the digest renders the rule’ s digest template, not its instant one', asy
assert . match ( mails [ 0 ] . rendered . subject , /^\[notify\.digest\]/ )
} )
test ( 'a digest leaves out what the forum leaves out for that reader' , async ( ) => {
stub ( { readerFilter : { 11 : { blocked : [ 40 , 50 ] , hiddenPosts : [ 3 ] , hiddenThreads : [ 9 ] } } } )
stubDigest ( {
posts : [
{ id : 1 , thread _id : 7 , title : 'Raid' , body _html : '<p>kept</p>' , author _user _id : 20 , author _username : 'twenty' } ,
{ id : 2 , thread _id : 7 , title : 'Raid' , body _html : '<p>blocked author</p>' , author _user _id : 40 , author _username : 'forty' } ,
{ id : 3 , thread _id : 7 , title : 'Raid' , body _html : '<p>hidden post</p>' , author _user _id : 20 , author _username : 'twenty' } ,
{ id : 4 , thread _id : 9 , title : 'Hidden' , body _html : '<p>hidden thread</p>' , author _user _id : 20 , author _username : 'twenty' } ,
{ id : 5 , thread _id : 8 , title : 'Theirs' , body _html : '<p>blocked starter</p>' , author _user _id : 20 , author _username : 'twenty' , thread _created _by : 50 } ,
] ,
} )
await digest . tick ( new Date ( ) )
assert . deepEqual ( mails [ 0 ] . rendered . values . items . map ( ( i ) => i . excerpt ) , [ 'kept' ] )
} )
test ( 'a digest whose every post is filtered out sends nothing and stamps nothing' , async ( ) => {
stub ( { readerFilter : { 11 : { blocked : [ 40 ] } } } )
stubDigest ( { posts : [ { id : 2 , thread _id : 7 , title : 'Raid' , body _html : '<p>x</p>' , author _user _id : 40 , author _username : 'forty' } ] } )
const res = await digest . tick ( new Date ( ) )
assert . equal ( res . sent , 0 )
assert . equal ( world . stamped . length , 0 )
} )
test ( 'a recipient with nothing new gets no mail and no stamp' , async ( ) => {
stubDigest ( { posts : [ ] } )
const res = await digest . tick ( new Date ( ) )