@@ -0,0 +1,552 @@
// ── First-party clans → core's Teams (phase 9) ─────────────────────────────
//
// The properties this suite holds, each with a failure behind it:
//
// • a clan's identity carries its creation time (D52), so a reset clan
// database cannot hand an old clan's Team to a new one;
// • only a COMPLETE board may say a clan is gone — a board at the game's
// 100-clan ceiling (D55), or one with an unreadable row, proves nothing
// about what it leaves out;
// • leadership is learned from the board, diffed (D54);
// • `getTeams` is complete only when EVERY server vouches (D53), and refuses
// rather than answering empty when none does;
// • a roster is shown to the clan's own members and staff by default (D48),
// re-read from the users row, and a failure withholds it;
// • every feed item is members-only (D49) and carries a dedupe key core will
// not truncate into a collision.
const test = require ( 'node:test' )
const assert = require ( 'node:assert' )
const { fakeCtx , spy } = require ( './_fakes' )
const SERVER = 'main'
const T0 = 1790142840000
function member ( steamId , rank = 2 , extra = { } ) {
return { steamId , rank , role : rank === 1 ? 'Leader' : 'Member' , joinedMs : T0 , name : ` P ${ steamId . slice ( - 2 ) } ` , ... extra }
}
function clanRow ( clanId , createdMs , members , extra = { } ) {
return { clanId , createdMs , name : ` Clan ${ clanId } ` , color : '#3FA9F5' , score : 10 , maxMembers : 100 , members , ... extra }
}
/**
* The model and provider over an in-memory store, with a chosen viewer row.
*
* The store is small enough to reason about: clans and members by external id,
* and one board record per server. Every `clans.db` function the code under test
* calls is replaced; anything else it reached for would throw on the fake ctx.
*/
function setup ( { users = { } , rosterSetting = null , servers = [ { id : SERVER } ] } = { } ) {
require ( '../core' ) . _reset ( )
const ctx = fakeCtx ( {
users : { getById : async ( id ) => users [ id ] || null } ,
} )
require ( '../core' ) . init ( ctx )
const db = require ( '../model/clans/clans.db' )
const visibilityDb = require ( '../model/visibility/visibility.db' )
const serversModel = require ( '../model/servers/servers.model' )
const store = { clans : new Map ( ) , members : new Map ( ) , boards : new Map ( ) , links : new Map ( ) , online : new Set ( ) , names : [ ] }
const originals = { db : { ... db } , visibilityDb : { ... visibilityDb } , servers : { ... serversModel } }
db . getBoard = async ( serverId ) => store . boards . get ( serverId ) || null
db . listBoards = async ( ) =>
servers . map ( ( s ) => ( { serverId : s . id , serverName : s . id . toUpperCase ( ) , ... ( store . boards . get ( s . id ) || { } ) } ) )
db . putBoard = async ( b ) => {
const prev = store . boards . get ( b . serverId ) || { }
store . boards . set ( b . serverId , {
serverId : b . serverId ,
boardT : b . boardT ,
seenAt : b . advanced ? new Date ( ) : prev . seenAt || null ,
enabled : b . enabled ? 1 : 0 ,
supported : b . supported ? 1 : 0 ,
truncated : b . truncated ? 1 : 0 ,
backend : b . backend ,
reason : b . reason ,
umodClans : b . umodClans ? 1 : 0 ,
clanCount : b . clanCount ,
} )
}
db . listClansForServer = async ( serverId ) => [ ... store . clans . values ( ) ] . filter ( ( c ) => c . serverId === serverId )
db . listMembersForServer = async ( serverId ) => {
const out = [ ]
for ( const c of store . clans . values ( ) ) {
if ( c . serverId !== serverId || c . goneAt ) continue
for ( const m of store . members . get ( c . externalId ) || [ ] ) out . push ( { externalId : c . externalId , ... m } )
}
return out
}
db . upsertClan = async ( c ) => {
const prev = store . clans . get ( c . externalId )
store . clans . set ( c . externalId , { ... prev , ... c , members : undefined , goneAt : null } )
}
db . replaceMembers = spy ( async ( externalId , members ) => {
store . members . set ( externalId , members . map ( ( m ) => ( { ... m } ) ) )
} )
db . markGone = async ( ids ) => {
for ( const id of ids ) {
const c = store . clans . get ( id )
if ( c && ! c . goneAt ) c . goneAt = new Date ( )
store . members . delete ( id )
}
}
db . findClan = async ( id ) => {
const c = store . clans . get ( id )
return c ? { ... c , serverName : c . serverId . toUpperCase ( ) } : null
}
db . findByGameId = async ( serverId , clanId ) => {
const hits = [ ... store . clans . values ( ) ]
. filter ( ( c ) => c . serverId === serverId && c . clanId === clanId )
. sort ( ( a , b ) => b . createdMs - a . createdMs )
return hits [ 0 ] ? { externalId : hits [ 0 ] . externalId , name : hits [ 0 ] . name } : null
}
db . listActiveClans = async ( ) =>
[ ... store . clans . values ( ) ] . filter ( ( c ) => ! c . goneAt ) . map ( ( c ) => ( { ... c , serverName : c . serverId . toUpperCase ( ) } ) )
db . listPublicForServer = async ( serverId ) =>
[ ... store . clans . values ( ) ] . filter ( ( c ) => c . serverId === serverId && ! c . goneAt )
db . listMembers = async ( externalId ) => {
const c = store . clans . get ( externalId )
return ( store . members . get ( externalId ) || [ ] ) . map ( ( m ) => ( {
... m ,
userId : store . links . get ( m . steamId ) || null ,
online : c && store . online . has ( m . steamId ) ? 1 : 0 ,
} ) )
}
db . userIsMember = async ( externalId , userId ) =>
( store . members . get ( externalId ) || [ ] ) . some ( ( m ) => store . links . get ( m . steamId ) === userId )
db . recentClanEvents = async ( ) => store . recent || [ ]
db . rememberName = async ( steamId , name ) => store . names . push ( { steamId , name } )
visibilityDb . getSetting = async ( key ) => ( key === 'clans.roster.audience' ? rosterSetting : null )
serversModel . listForPolling = async ( ) => servers
const clans = require ( '../model/clans/clans.model' )
const provider = require ( '../model/clans/teamProvider' )
return {
ctx ,
store ,
clans ,
provider ,
restore : ( ) => {
Object . assign ( db , originals . db )
Object . assign ( visibilityDb , originals . visibilityDb )
Object . assign ( serversModel , originals . servers )
} ,
}
}
const board = ( clans , extra = { } ) => ( { kind : 'clans' , type : 'snapshot' , t : T0 , supported : true , truncated : false , enabled : true , clans , ... extra } )
// ── Identity ───────────────────────────────────────────────────────────────
test ( 'a clan is keyed on server, game id AND creation time (D52)' , ( ) => {
const { clans , restore } = setup ( )
try {
const a = clans . normaliseClan ( SERVER , clanRow ( 1 , T0 , [ member ( '76561198000000001' , 1 ) ] ) )
const b = clans . normaliseClan ( SERVER , clanRow ( 1 , T0 + 5000 , [ member ( '76561198000000001' , 1 ) ] ) )
// Same game id, different clan: a reset database re-used id 1.
assert . notStrictEqual ( a . externalId , b . externalId )
assert . strictEqual ( a . externalId , ` main:1: ${ T0 } ` )
// No id, no creation time, or no name: there is nothing to key it on.
assert . strictEqual ( clans . normaliseClan ( SERVER , { clanId : 1 , name : 'x' } ) , null )
assert . strictEqual ( clans . normaliseClan ( SERVER , { createdMs : T0 , name : 'x' } ) , null )
assert . strictEqual ( clans . normaliseClan ( SERVER , { clanId : 1 , createdMs : T0 } ) , null )
// A colour ends up in a style, so anything that is not #rrggbb is dropped.
assert . strictEqual ( clans . normaliseClan ( SERVER , clanRow ( 2 , T0 , [ ] , { color : 'red;background:url(x)' } ) ) . color , null )
// A member whose Steam id is not one is dropped, not the clan.
const partial = clans . normaliseClan ( SERVER , clanRow ( 3 , T0 , [ member ( '7656' ) , { steamId : 'robert' } ] ) )
assert . deepStrictEqual ( partial . members . map ( ( m ) => m . steamId ) , [ '7656' ] )
} finally {
restore ( )
}
} )
// ── The board ──────────────────────────────────────────────────────────────
test ( 'a first board stores its clans and asks core to reconcile' , async ( ) => {
const { ctx , store , clans , restore } = setup ( )
try {
const result = await clans . applyBoard ( SERVER , board ( [
clanRow ( 1 , T0 , [ member ( '76561198000000001' , 1 ) , member ( '76561198000000002' ) ] ) ,
] ) )
assert . strictEqual ( result . applied , true )
assert . strictEqual ( result . created , 1 )
assert . strictEqual ( store . clans . size , 1 )
assert . strictEqual ( store . members . get ( ` main:1: ${ T0 } ` ) . length , 2 )
assert . strictEqual ( ctx . teams . reconcile . calls . length , 1 )
// Leaders of a brand-new clan reach core WITH the Team, not as a delta
// against a Team core does not hold yet.
assert . strictEqual ( ctx . teams . publish . calls . length , 0 )
} finally {
restore ( )
}
} )
test ( 'a board whose t has not moved is not applied again' , async ( ) => {
const { ctx , clans , store , restore } = setup ( )
try {
const b = board ( [ clanRow ( 1 , T0 , [ member ( '76561198000000001' , 1 ) ] ) ] )
await clans . applyBoard ( SERVER , b )
store . members . clear ( )
const again = await clans . applyBoard ( SERVER , b )
assert . strictEqual ( again . applied , false )
assert . strictEqual ( store . members . size , 0 , 'nothing was rewritten' )
assert . strictEqual ( ctx . teams . reconcile . calls . length , 1 )
} finally {
restore ( )
}
} )
test ( 'an unchanged roster is not rewritten when the board moves on' , async ( ) => {
const { clans , restore } = setup ( )
const db = require ( '../model/clans/clans.db' )
try {
const members = [ member ( '76561198000000001' , 1 ) ]
await clans . applyBoard ( SERVER , board ( [ clanRow ( 1 , T0 , members ) ] ) )
const writes = db . replaceMembers . calls . length
await clans . applyBoard ( SERVER , board ( [ clanRow ( 1 , T0 , members ) ] , { t : T0 + 60000 } ) )
assert . strictEqual ( db . replaceMembers . calls . length , writes )
} finally {
restore ( )
}
} )
test ( 'a complete board says a missing clan is gone; a truncated one does not (D55)' , async ( ) => {
const { clans , store , restore } = setup ( )
try {
await clans . applyBoard ( SERVER , board ( [
clanRow ( 1 , T0 , [ member ( '76561198000000001' , 1 ) ] ) ,
clanRow ( 2 , T0 , [ member ( '76561198000000002' , 1 ) ] ) ,
] ) )
// At the ceiling: clan 2 is not listed, and that proves nothing.
await clans . applyBoard ( SERVER , board ( [ clanRow ( 1 , T0 , [ member ( '76561198000000001' , 1 ) ] ) ] , { t : T0 + 60000 , truncated : true } ) )
assert . strictEqual ( store . clans . get ( ` main:2: ${ T0 } ` ) . goneAt , null )
// A row this build could not read counts the same way.
await clans . applyBoard ( SERVER , board ( [ clanRow ( 1 , T0 , [ member ( '76561198000000001' , 1 ) ] ) , { name : 'broken' } ] , { t : T0 + 90000 } ) )
assert . strictEqual ( store . clans . get ( ` main:2: ${ T0 } ` ) . goneAt , null )
// Complete, and still not listed: now it is gone.
await clans . applyBoard ( SERVER , board ( [ clanRow ( 1 , T0 , [ member ( '76561198000000001' , 1 ) ] ) ] , { t : T0 + 120000 } ) )
assert . ok ( store . clans . get ( ` main:2: ${ T0 } ` ) . goneAt )
} finally {
restore ( )
}
} )
test ( 'a change of leader is published from the board diff (D54)' , async ( ) => {
const { ctx , clans , restore } = setup ( )
try {
await clans . applyBoard ( SERVER , board ( [ clanRow ( 1 , T0 , [ member ( '76561198000000001' , 1 ) , member ( '76561198000000002' , 2 ) ] ) ] ) )
await clans . applyBoard ( SERVER , board (
[ clanRow ( 1 , T0 , [ member ( '76561198000000001' , 2 ) , member ( '76561198000000002' , 1 ) ] ) ] ,
{ t : T0 + 60000 } ,
) )
const kinds = ctx . teams . publish . calls . map ( ( [ e ] ) => ` ${ e . kind } : ${ e . memberKey } ` ) . sort ( )
assert . deepStrictEqual ( kinds , [
'team.leader.added:76561198000000002' ,
'team.leader.removed:76561198000000001' ,
] )
} finally {
restore ( )
}
} )
test ( 'an unsupported board is recorded with its reason and touches no clan' , async ( ) => {
const { clans , store , restore } = setup ( )
try {
await clans . applyBoard ( SERVER , board ( [ clanRow ( 1 , T0 , [ member ( '76561198000000001' , 1 ) ] ) ] ) )
const result = await clans . applyBoard ( SERVER , {
kind : 'clans' , t : T0 + 60000 , supported : false , reason : 'held by a NexusClanBackend' , clans : [ ] ,
} )
assert . strictEqual ( result . applied , false )
assert . strictEqual ( store . boards . get ( SERVER ) . supported , 0 )
assert . match ( store . boards . get ( SERVER ) . reason , /Nexus/ )
assert . strictEqual ( store . clans . get ( ` main:1: ${ T0 } ` ) . goneAt , null , 'an unreadable server says nothing about its clans' )
// No board at all: a plugin older than protocol 6. Recorded, nothing touched.
await clans . applyBoard ( SERVER , undefined )
assert . match ( store . boards . get ( SERVER ) . reason , /protocol 6/ )
assert . strictEqual ( store . clans . get ( ` main:1: ${ T0 } ` ) . goneAt , null )
} finally {
restore ( )
}
} )
// ── The events ─────────────────────────────────────────────────────────────
test ( 'each clan event is published as the Team kind core takes' , async ( ) => {
const { ctx , clans , restore } = setup ( )
try {
const base = { clanId : 1 , createdMs : T0 , clanName : 'Clan 1' , t : T0 + 1 }
await clans . applyEvent ( SERVER , { kind : 'clan.created' , ... base , steamId : '76561198000000001' , name : 'Ann' } )
await clans . applyEvent ( SERVER , { kind : 'clan.member.added' , ... base , steamId : '76561198000000002' , name : 'Bob' } )
await clans . applyEvent ( SERVER , { kind : 'clan.member.left' , ... base , steamId : '76561198000000002' , name : 'Bob' } )
await clans . applyEvent ( SERVER , { kind : 'clan.member.kicked' , ... base , steamId : '76561198000000003' , bySteamId : '76561198000000001' } )
assert . deepStrictEqual ( ctx . teams . publish . calls . map ( ( [ e ] ) => [ e . kind , e . memberKey ] ) , [
[ 'team.created' , undefined ] ,
[ 'team.member.added' , '76561198000000002' ] ,
[ 'team.member.removed' , '76561198000000002' ] ,
[ 'team.member.removed' , '76561198000000003' ] ,
] )
for ( const [ e ] of ctx . teams . publish . calls ) assert . strictEqual ( e . externalId , ` main:1: ${ T0 } ` )
} finally {
restore ( )
}
} )
test ( 'every feed item is members-only, and its dedupe key fits core’ s 40 characters' , async ( ) => {
const { ctx , clans , restore } = setup ( )
try {
const base = { clanId : 1 , createdMs : T0 , clanName : 'Clan 1' , t : T0 + 1 }
await clans . applyEvent ( SERVER , { kind : 'clan.created' , ... base , steamId : '76561198000000001' , name : 'Ann' } )
await clans . applyEvent ( SERVER , { kind : 'clan.member.kicked' , ... base , steamId : '76561198000000003' , name : 'Cy' , byName : 'Ann' , bySteamId : '76561198000000001' } )
await clans . applyEvent ( SERVER , { kind : 'clan.disbanded' , ... base , steamId : '76561198000000001' } )
const items = ctx . teams . activity . push . calls . map ( ( [ batch ] ) => batch [ 0 ] )
// D49: founded and removed made lines; the disband did not.
assert . deepStrictEqual ( items . map ( ( i ) => i . kind ) , [ 'rust.clan.founded' , 'rust.clan.removed' ] )
assert . strictEqual ( items [ 0 ] . summary , 'Ann founded the clan.' )
assert . strictEqual ( items [ 1 ] . summary , 'Cy was removed from the clan by Ann.' )
assert . strictEqual ( items [ 1 ] . actorMemberKey , '76561198000000001' , 'the actor of a kick is the kicker' )
for ( const item of items ) {
assert . strictEqual ( item . visibility , 'members' )
// Core clamps a dedupe key to 40 characters. A readable one would be cut
// short into collisions; a sha1 is exactly 40.
assert . match ( item . dedupeKey , /^[0-9a-f]{40}$/ )
assert . strictEqual ( typeof item . occurredAt , 'number' , 'core reads occurredAt as epoch ms' )
}
assert . notStrictEqual ( items [ 0 ] . dedupeKey , items [ 1 ] . dedupeKey )
} finally {
restore ( )
}
} )
test ( 'the same frame offered twice carries the same key, so a re-offer is a no-op' , async ( ) => {
const { ctx , clans , store , restore } = setup ( )
try {
const frame = { kind : 'clan.member.added' , clanId : 1 , createdMs : T0 , t : T0 + 5 , steamId : '76561198000000002' , name : 'Bob' }
await clans . applyEvent ( SERVER , frame )
store . recent = [ { id : 1 , kind : frame . kind , t : frame . t , raw : JSON . stringify ( frame ) } ]
const offered = await clans . reofferActivity ( SERVER )
assert . strictEqual ( offered , 1 )
const [ first , second ] = ctx . teams . activity . push . calls . map ( ( [ batch ] ) => batch [ 0 ] . dedupeKey )
assert . strictEqual ( first , second )
} finally {
restore ( )
}
} )
test ( 'a join without a creation time is matched on the game id, newest clan first' , async ( ) => {
const { ctx , clans , restore } = setup ( )
try {
await clans . applyBoard ( SERVER , board ( [
clanRow ( 1 , T0 , [ member ( '76561198000000001' , 1 ) ] ) ,
] ) )
const result = await clans . applyEvent ( SERVER , { kind : 'clan.member.added' , clanId : 1 , t : T0 + 1 , steamId : '76561198000000009' } )
assert . strictEqual ( result . externalId , ` main:1: ${ T0 } ` )
// A clan this module has never heard of is skipped, not guessed at.
const unknown = await clans . applyEvent ( SERVER , { kind : 'clan.member.added' , clanId : 77 , t : T0 + 2 , steamId : '76561198000000009' } )
assert . strictEqual ( unknown . applied , false )
assert . ok ( ctx . teams . publish . calls . every ( ( [ e ] ) => e . externalId === ` main:1: ${ T0 } ` ) )
} finally {
restore ( )
}
} )
test ( 'a disband marks the clan gone even when the board could not say so' , async ( ) => {
const { clans , store , restore } = setup ( )
try {
await clans . applyBoard ( SERVER , board ( [ clanRow ( 1 , T0 , [ member ( '76561198000000001' , 1 ) ] ) ] , { truncated : true } ) )
await clans . applyEvent ( SERVER , { kind : 'clan.disbanded' , clanId : 1 , createdMs : T0 , t : T0 + 1 , steamId : '76561198000000001' } )
assert . ok ( store . clans . get ( ` main:1: ${ T0 } ` ) . goneAt )
} finally {
restore ( )
}
} )
// ── The provider ───────────────────────────────────────────────────────────
test ( 'getTeams is complete only when every server vouches (D53)' , async ( ) => {
const both = [ { id : 'main' } , { id : 'pvp' } ]
const { clans , provider , restore } = setup ( { servers : both } )
try {
// Neither server has a board: refuse, never "no teams".
const none = await provider . getTeams ( )
assert . strictEqual ( none . ok , false )
// One current, one never heard from: partial, so core removes nothing.
await clans . applyBoard ( 'main' , board ( [ clanRow ( 1 , T0 , [ member ( '76561198000000001' , 1 ) ] ) ] ) )
const partial = await provider . getTeams ( )
assert . strictEqual ( partial . ok , true )
assert . strictEqual ( partial . complete , false )
assert . deepStrictEqual ( partial . teams . map ( ( t ) => t . externalId ) , [ ` main:1: ${ T0 } ` ] )
assert . strictEqual ( partial . teams [ 0 ] . meta . serverId , 'main' )
// Both current: complete.
await clans . applyBoard ( 'pvp' , board ( [ ] ) )
assert . strictEqual ( ( await provider . getTeams ( ) ) . complete , true )
// One at the ceiling: partial again.
await clans . applyBoard ( 'pvp' , board ( [ ] , { t : T0 + 60000 , truncated : true } ) )
assert . strictEqual ( ( await provider . getTeams ( ) ) . complete , false )
} finally {
restore ( )
}
} )
test ( 'a board that stops advancing stops vouching' , async ( ) => {
const { store , clans , provider , restore } = setup ( )
try {
await clans . applyBoard ( SERVER , board ( [ clanRow ( 1 , T0 , [ member ( '76561198000000001' , 1 ) ] ) ] ) )
assert . strictEqual ( ( await provider . getTeams ( ) ) . ok , true )
store . boards . get ( SERVER ) . seenAt = new Date ( Date . now ( ) - clans . FRESH _MS - 1000 )
assert . strictEqual ( ( await provider . getTeams ( ) ) . ok , false )
assert . strictEqual ( ( await provider . getTeamMembers ( ` main:1: ${ T0 } ` ) ) . ok , false )
} finally {
restore ( )
}
} )
test ( 'getTeams refuses on a site with no Rust servers' , async ( ) => {
const { provider , restore } = setup ( { servers : [ ] } )
try {
const answer = await provider . getTeams ( )
assert . deepStrictEqual ( answer . ok , false )
assert . match ( answer . reason , /no Rust servers/ )
} finally {
restore ( )
}
} )
test ( 'a roster names its members, its leaders, the linked account and who is on' , async ( ) => {
const { store , clans , provider , restore } = setup ( )
try {
await clans . applyBoard ( SERVER , board ( [ clanRow ( 1 , T0 , [ member ( '76561198000000001' , 1 ) , member ( '76561198000000002' ) , member ( '76561198000000003' , null , { rank : null , role : null } ) ] ) ] ) )
store . links . set ( '76561198000000002' , 42 )
store . online . add ( '76561198000000001' )
const roster = await provider . getTeamMembers ( ` main:1: ${ T0 } ` )
assert . strictEqual ( roster . ok , true )
const byKey = Object . fromEntries ( roster . members . map ( ( m ) => [ m . memberKey , m ] ) )
assert . strictEqual ( byKey [ '76561198000000001' ] . leader , true )
assert . strictEqual ( byKey [ '76561198000000001' ] . online , true )
assert . strictEqual ( byKey [ '76561198000000002' ] . userId , 42 )
// A rank the board could not match is not a leader.
assert . strictEqual ( byKey [ '76561198000000003' ] . leader , false )
const leaders = await provider . getTeamLeaders ( ` main:1: ${ T0 } ` )
assert . deepStrictEqual ( leaders , { ok : true , leaders : [ '76561198000000001' ] } )
// A clan with a count but no stored rows is a read between two writes.
store . members . set ( ` main:1: ${ T0 } ` , [ ] )
assert . strictEqual ( ( await provider . getTeamMembers ( ` main:1: ${ T0 } ` ) ) . ok , false )
} finally {
restore ( )
}
} )
// ── Who may see a roster (D48) ─────────────────────────────────────────────
const USERS = {
1 : { id : 1 , role : 'player' , status : 'active' } , // linked to a member
2 : { id : 2 , role : 'player' , status : 'active' } , // not a member
3 : { id : 3 , role : 'moderator' , status : 'active' } ,
4 : { id : 4 , role : 'player' , status : 'banned' } , // linked to a member, banned
}
async function rosterFixture ( options ) {
const fx = setup ( { users : USERS , ... options } )
await fx . clans . applyBoard ( SERVER , board ( [ clanRow ( 1 , T0 , [ member ( '76561198000000001' , 1 ) , member ( '76561198000000004' ) ] ) ] ) )
fx . store . links . set ( '76561198000000001' , 1 )
fx . store . links . set ( '76561198000000004' , 4 )
return fx
}
const keysFor = async ( provider , viewer ) =>
( await provider . projectRoster ( ` main:1: ${ T0 } ` , [ { member _key : '76561198000000001' } , { member _key : '76561198000000004' } ] , viewer ) ) . members . length
test ( 'by default a roster is for the clan’ s own members and staff' , async ( ) => {
const { provider , restore } = await rosterFixture ( )
try {
assert . strictEqual ( await keysFor ( provider , null ) , 0 , 'anonymous' )
assert . strictEqual ( await keysFor ( provider , { userId : 2 , role : 'player' } ) , 0 , 'a stranger' )
assert . strictEqual ( await keysFor ( provider , { userId : 1 , role : 'player' } ) , 2 , 'a member' )
assert . strictEqual ( await keysFor ( provider , { userId : 3 , role : 'moderator' } ) , 2 , 'staff' )
// The row, not the claim: a banned member sees nothing, and a claimed role
// the row does not hold grants nothing.
assert . strictEqual ( await keysFor ( provider , { userId : 4 , role : 'player' } ) , 0 , 'banned' )
assert . strictEqual ( await keysFor ( provider , { userId : 2 , role : 'admin' } ) , 0 , 'a claim is not a role' )
} finally {
restore ( )
}
} )
test ( 'the operator can widen it, and an unknown setting narrows back' , async ( ) => {
const signedIn = await rosterFixture ( { rosterSetting : 'signed_in' } )
try {
assert . strictEqual ( await keysFor ( signedIn . provider , { userId : 2 , role : 'player' } ) , 2 )
assert . strictEqual ( await keysFor ( signedIn . provider , null ) , 0 )
} finally {
signedIn . restore ( )
}
const open = await rosterFixture ( { rosterSetting : 'public' } )
try {
assert . strictEqual ( await keysFor ( open . provider , null ) , 2 )
} finally {
open . restore ( )
}
const typo = await rosterFixture ( { rosterSetting : 'everyone' } )
try {
assert . strictEqual ( await keysFor ( typo . provider , { userId : 2 , role : 'player' } ) , 0 )
} finally {
typo . restore ( )
}
} )
test ( 'a roster question that cannot be answered withholds the roster' , async ( ) => {
const { provider , restore } = await rosterFixture ( )
const visibilityDb = require ( '../model/visibility/visibility.db' )
try {
visibilityDb . getSetting = async ( ) => {
throw new Error ( 'pool exhausted' )
}
const answer = await provider . projectRoster ( ` main:1: ${ T0 } ` , [ { member _key : '76561198000000001' } ] , { userId : 3 } )
// Core fails CLOSED on this one call: a refusal serves an empty roster.
assert . strictEqual ( answer . ok , false )
} finally {
restore ( )
}
} )
test ( 'the clan page carries no Steam id and no account id, and no names below the audience' , async ( ) => {
const { clans , restore } = await rosterFixture ( )
try {
const outside = await clans . getForViewer ( ` main:1: ${ T0 } ` , null )
assert . strictEqual ( outside . roster . visible , false )
assert . deepStrictEqual ( outside . roster . members , [ ] )
assert . strictEqual ( outside . clan . memberCount , 2 , 'the count is public (D58)' )
const inside = await clans . getForViewer ( ` main:1: ${ T0 } ` , { userId : 1 } )
assert . strictEqual ( inside . roster . visible , true )
assert . strictEqual ( inside . roster . members . length , 2 )
for ( const m of inside . roster . members ) {
assert . ok ( ! ( 'steamId' in m ) && ! ( 'userId' in m ) , 'no identifier leaves on a roster row' )
}
assert . strictEqual ( await clans . getForViewer ( 'main:99:1' , null ) , null )
} finally {
restore ( )
}
} )