atproto/packages/ozone/tests/age-assurance.test.ts
Foysal Ahamed 926193f825
Ozone support for age assurance compliance events (#4029)
* 🚧 WIP

*  Make age assurance state queryable

*  Split age assurance events into 2

*  Implement admin and user state overrides

*  Add blocked as a known value for age assurance state

*  Update test snapshot

*  Update test snapshot

*  Cleanup
2025-07-11 18:00:55 +02:00

246 lines
7.4 KiB
TypeScript

import {
ModeratorClient,
SeedClient,
TestNetwork,
basicSeed,
} from '@atproto/dev-env'
import { forSnapshot } from './_util'
describe('age assurance events', () => {
let network: TestNetwork
let sc: SeedClient
let modClient: ModeratorClient
beforeAll(async () => {
network = await TestNetwork.create({
dbPostgresSchema: 'ozone_age_assurance',
})
sc = network.getSeedClient()
modClient = network.ozone.getModClient()
await basicSeed(sc)
await network.processAll()
})
afterAll(async () => {
await network.close()
})
it('handles age assurance events from user', async () => {
const aliceSubject = {
$type: 'com.atproto.admin.defs#repoRef',
did: sc.dids.alice,
}
const bobSubject = {
$type: 'com.atproto.admin.defs#repoRef',
did: sc.dids.bob,
}
const alicePendingEvent = await modClient.emitEvent({
subject: aliceSubject,
event: {
$type: 'tools.ozone.moderation.defs#ageAssuranceEvent',
status: 'pending',
createdAt: new Date().toISOString(),
attemptId: 'attempt-123',
initIp: '123.456.789.012',
initUa: 'Mozilla/5.0',
},
})
const bobPendingEvent = await modClient.emitEvent({
subject: bobSubject,
event: {
$type: 'tools.ozone.moderation.defs#ageAssuranceEvent',
status: 'pending',
createdAt: new Date().toISOString(),
attemptId: 'attempt-345',
initIp: '234.567.890.123',
initUa: 'Mozilla/5.0',
},
})
const bobAssuredEvent = await modClient.emitEvent({
subject: bobSubject,
event: {
$type: 'tools.ozone.moderation.defs#ageAssuranceEvent',
status: 'assured',
createdAt: new Date().toISOString(),
attemptId: 'attempt-345',
initIp: '234.567.890.123',
initUa: 'Mozilla/5.0',
completeIp: '345.678.901.234',
completeUa: 'Mozilla/5.0',
},
})
expect(forSnapshot(alicePendingEvent)).toMatchSnapshot()
expect(forSnapshot(bobPendingEvent)).toMatchSnapshot()
expect(forSnapshot(bobAssuredEvent)).toMatchSnapshot()
// Verify that age assurance state is correctly set for each subject
const [{ subjectStatuses: aliceStatus }, { subjectStatuses: bobStatus }] =
await Promise.all([
modClient.queryStatuses({
subject: sc.dids.alice,
}),
modClient.queryStatuses({
subject: sc.dids.bob,
}),
])
expect(aliceStatus[0].ageAssuranceState).toBe('pending')
expect(bobStatus[0].ageAssuranceState).toBe('assured')
// Verify that queryEvents allow filtering by ageAssuranceState
try {
const [{ events: pendingEvents }, { events: unknownEvents }] =
await Promise.all([
modClient.queryEvents({
ageAssuranceState: 'pending',
}),
modClient.queryEvents({
ageAssuranceState: 'assured',
}),
])
expect(pendingEvents.length).toEqual(2)
pendingEvents.forEach((event) => {
expect(event.event.$type).toBe(
'tools.ozone.moderation.defs#ageAssuranceEvent',
)
expect((event.event as any).status).toBe('pending')
})
expect(unknownEvents.length).toBeGreaterThan(0)
unknownEvents.forEach((event) => {
expect(event.event.$type).toBe(
'tools.ozone.moderation.defs#ageAssuranceEvent',
)
expect((event.event as any).status).toBe('assured')
})
} catch (error) {
console.error('Error querying events:', error)
throw error
}
// Verify that queryStatuses allows filtering by ageAssuranceState
const { subjectStatuses: pendingStatuses } = await modClient.queryStatuses({
ageAssuranceState: 'pending',
})
expect(pendingStatuses.length).toEqual(1)
pendingStatuses.forEach((status) => {
expect(status.ageAssuranceState).toBe('pending')
})
})
it('age assurance event fails for record subjects', async () => {
const postSubject = {
$type: 'com.atproto.repo.strongRef',
uri: sc.posts[sc.dids.alice][0].ref.uriStr,
cid: sc.posts[sc.dids.alice][0].ref.cidStr,
}
await expect(
modClient.emitEvent({
subject: postSubject,
event: {
$type: 'tools.ozone.moderation.defs#ageAssuranceEvent',
status: 'pending',
createdAt: new Date().toISOString(),
attemptId: 'attempt-123',
},
}),
).rejects.toThrow('Invalid subject type')
})
it('admin override behavior for age assurance states', async () => {
const carolSubject = {
$type: 'com.atproto.admin.defs#repoRef',
did: sc.dids.carol,
}
// Verify that user emitted state is overridden by admin emitted state
await modClient.emitEvent({
subject: carolSubject,
event: {
$type: 'tools.ozone.moderation.defs#ageAssuranceEvent',
status: 'pending',
createdAt: new Date().toISOString(),
attemptId: 'attempt-carol-1',
},
})
const { subjectStatuses } = await modClient.queryStatuses({
subject: sc.dids.carol,
})
expect(subjectStatuses[0].ageAssuranceState).toBe('pending')
expect(subjectStatuses[0].ageAssuranceUpdatedBy).toBe('user')
await modClient.emitEvent({
subject: carolSubject,
event: {
$type: 'tools.ozone.moderation.defs#ageAssuranceOverrideEvent',
status: 'assured',
comment: 'Admin verification completed',
},
})
const { subjectStatuses: afterAdminAssurance } =
await modClient.queryStatuses({
subject: sc.dids.carol,
})
expect(afterAdminAssurance[0].ageAssuranceState).toBe('assured')
expect(afterAdminAssurance[0].ageAssuranceUpdatedBy).toBe('admin')
// Verify that user emitted state can not override admin emitted state
await modClient.emitEvent({
subject: carolSubject,
event: {
$type: 'tools.ozone.moderation.defs#ageAssuranceEvent',
status: 'pending',
createdAt: new Date().toISOString(),
attemptId: 'attempt-carol-2',
},
})
const { subjectStatuses: afterCarolsAttempt } =
await modClient.queryStatuses({
subject: sc.dids.carol,
})
expect(afterCarolsAttempt[0].ageAssuranceState).toBe('assured')
expect(afterCarolsAttempt[0].ageAssuranceUpdatedBy).toBe('admin')
// Verify that admin can reset state to allow the user to override
await modClient.emitEvent({
subject: carolSubject,
event: {
$type: 'tools.ozone.moderation.defs#ageAssuranceOverrideEvent',
status: 'reset',
comment: 'Reset to allow user to set state again',
},
})
const { subjectStatuses: afterReset } = await modClient.queryStatuses({
subject: sc.dids.carol,
})
expect(afterReset[0].ageAssuranceState).toBe('reset')
expect(afterReset[0].ageAssuranceUpdatedBy).toBe('admin')
await modClient.emitEvent({
subject: carolSubject,
event: {
$type: 'tools.ozone.moderation.defs#ageAssuranceEvent',
status: 'assured',
createdAt: new Date().toISOString(),
attemptId: 'attempt-carol-3',
},
})
const { subjectStatuses: afterCarolAssured } =
await modClient.queryStatuses({
subject: sc.dids.carol,
})
expect(afterCarolAssured[0].ageAssuranceState).toBe('assured')
expect(afterCarolAssured[0].ageAssuranceUpdatedBy).toBe('user')
})
})