import assert from 'node:assert' import { afterAll, beforeAll, describe, expect, it } from 'vitest' import { AppBskyActorDefs, AtpAgent, ids } from '@atproto/api' import { SeedClient, TestNetwork, verificationsSeed } from '@atproto/dev-env' interface ProfileViewTestCase { description: string // The DIDs are only set during test setup, so data that depends on those DIDs // needs to be lazily evaluated by using a function. getDid: () => string getExpected: () => AppBskyActorDefs.VerificationState | undefined getExpectedUrisPrefixes?: () => string[] } describe('verification views', () => { let network: TestNetwork let agent: AtpAgent let labelerDid: string let sc: SeedClient // account dids, for convenience let alice: string let bob: string let carol: string let dan: string let eve: string let frank: string let gus: string let impersonator: string let verifier1: string let verifier2: string let verifier3: string let handleinvalid: string let handleempty: string beforeAll(async () => { network = await TestNetwork.create({ dbPostgresSchema: 'bsky_views_verification', }) agent = network.bsky.getAgent() sc = network.getSeedClient() await verificationsSeed(sc) await network.processAll() labelerDid = network.bsky.ctx.cfg.modServiceDid alice = sc.dids.alice bob = sc.dids.bob carol = sc.dids.carol dan = sc.dids.dan eve = sc.dids.eve frank = sc.dids.frank gus = sc.dids.gus impersonator = sc.dids.impersonator verifier1 = sc.dids.verifier1 verifier2 = sc.dids.verifier2 verifier3 = sc.dids.verifier3 handleinvalid = sc.dids.handleinvalid handleempty = sc.dids.handleempty await network.bsky.db.db .updateTable('actor') .set({ trustedVerifier: true }) .where('did', 'in', [verifier1, verifier2, verifier3]) .execute() }, 20_000) // @NOTE seeding can take a while afterAll(async () => { await network.close() }) describe('profile views', () => { const testCases: ProfileViewTestCase[] = [ { description: 'returns trusted verifier that has verifications', getDid: () => verifier1, getExpected: () => ({ verifications: [ { createdAt: expect.any(String), isValid: true, issuer: verifier2, uri: expect.any(String), }, ], verifiedStatus: 'valid', trustedVerifierStatus: 'valid', }), getExpectedUrisPrefixes: () => [ `at://${verifier2}/app.bsky.graph.verification/`, ], }, { description: 'returns trusted verifier that has no verifications', getDid: () => verifier2, getExpected: () => ({ verifications: [], verifiedStatus: 'none', trustedVerifierStatus: 'valid', }), }, { description: 'returns trusted verifier with impersonation', getDid: () => verifier3, getExpected: () => ({ verifications: [], verifiedStatus: 'none', trustedVerifierStatus: 'invalid', }), }, { description: 'returns verified with multiple verifications', getDid: () => bob, getExpected: () => ({ verifications: [ { createdAt: expect.any(String), isValid: true, issuer: verifier1, uri: expect.any(String), }, { createdAt: expect.any(String), isValid: true, issuer: verifier2, uri: expect.any(String), }, ], verifiedStatus: 'valid', trustedVerifierStatus: 'none', }), getExpectedUrisPrefixes: () => [ `at://${verifier1}/app.bsky.graph.verification/`, `at://${verifier2}/app.bsky.graph.verification/`, ], }, { description: 'returns verified with mixed valid/invalid verifications', getDid: () => carol, getExpected: () => ({ verifications: [ { createdAt: expect.any(String), isValid: true, issuer: verifier1, uri: expect.any(String), }, { createdAt: expect.any(String), isValid: false, issuer: verifier2, uri: expect.any(String), }, ], verifiedStatus: 'valid', trustedVerifierStatus: 'none', }), getExpectedUrisPrefixes: () => [ `at://${verifier1}/app.bsky.graph.verification/`, `at://${verifier2}/app.bsky.graph.verification/`, ], }, { description: 'returns verified excluding non-verifier verifications', getDid: () => dan, getExpected: () => ({ verifications: [ { createdAt: expect.any(String), isValid: true, issuer: verifier1, uri: expect.any(String), }, // It has a verification by a non-verifier, which is not included. ], verifiedStatus: 'valid', trustedVerifierStatus: 'none', }), getExpectedUrisPrefixes: () => [ `at://${verifier1}/app.bsky.graph.verification/`, ], }, { description: 'returns undefined for user with no verifications at all', getDid: () => eve, getExpected: () => undefined, }, { description: 'returns unverified with only invalid verifications from verifiers', getDid: () => frank, getExpected: () => ({ verifications: [ { createdAt: expect.any(String), isValid: false, issuer: verifier2, uri: expect.any(String), }, ], verifiedStatus: 'invalid', trustedVerifierStatus: 'none', }), getExpectedUrisPrefixes: () => [ `at://${verifier2}/app.bsky.graph.verification/`, ], }, { description: 'returns unverified for user with only verifications by non-verifiers', getDid: () => gus, getExpected: () => undefined, }, { description: 'returns invalid verified for impersonator, but includes verifications', getDid: () => impersonator, getExpected: () => ({ verifications: [ { createdAt: expect.any(String), isValid: true, issuer: verifier1, uri: expect.any(String), }, ], verifiedStatus: 'invalid', trustedVerifierStatus: 'none', }), getExpectedUrisPrefixes: () => [ `at://${verifier1}/app.bsky.graph.verification/`, ], }, { description: 'returns undefined for user with invalid handle even if they have verifications', getDid: () => handleinvalid, getExpected: () => undefined, }, { description: 'returns undefined for user with empty handle even if they have verifications', getDid: () => handleempty, getExpected: () => undefined, }, ] it.each(testCases)( '$description', async ({ getDid, getExpected, getExpectedUrisPrefixes = () => [] }) => { const profile = await getProfile(getDid()) expect(profile.verification).toStrictEqual(getExpected()) const urlPrefixes = getExpectedUrisPrefixes() expect(urlPrefixes.length).toBe( profile.verification?.verifications.length ?? 0, ) urlPrefixes.forEach((prefix, i) => { assert(profile.verification) expect( profile.verification.verifications[i].uri.startsWith(prefix), ).toBe(true) }) }, ) }) const getProfile = async (actor: string) => { const res = await agent.app.bsky.actor.getProfile( { actor }, { headers: { ...(await network.serviceHeaders(alice, ids.AppBskyActorGetProfile)), 'atproto-accept-labelers': `${labelerDid};redact`, }, }, ) return res.data } })