diff options
author | Paul Frazee <pfrazee@gmail.com> | 2023-01-24 09:06:27 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-24 09:06:27 -0600 |
commit | 9027882fb401df2a9df6a89facb2bdb94b8b731b (patch) | |
tree | dc60ca1a2cc1be0838229f06b588f56871f2b91e /jest | |
parent | 439305b57e0c20799d87baf92c067ec8e262ea13 (diff) | |
download | voidsky-9027882fb401df2a9df6a89facb2bdb94b8b731b.tar.zst |
Account switcher (#85)
* Update the account-create and signin views to use the design system. Also: - Add borderDark to the theme - Start to an account selector in the signin flow * Dark mode fixes in signin ui * Track multiple active accounts and provide account-switching UI * Add test tooling for an in-memory pds * Add complete integration tests for login and the account switcher
Diffstat (limited to 'jest')
-rw-r--r-- | jest/jestSetup.js | 13 | ||||
-rw-r--r-- | jest/test-pds.ts | 199 | ||||
-rw-r--r-- | jest/test-utils.tsx | 13 |
3 files changed, 205 insertions, 20 deletions
diff --git a/jest/jestSetup.js b/jest/jestSetup.js index b1f110a53..8f95062a9 100644 --- a/jest/jestSetup.js +++ b/jest/jestSetup.js @@ -25,19 +25,6 @@ jest.mock('react-native-safe-area-context', () => { } }) -jest.mock('@gorhom/bottom-sheet', () => { - const react = require('react-native') - return { - __esModule: true, - default: react.View, - namedExport: { - ...require('react-native-reanimated/mock'), - ...jest.requireActual('@gorhom/bottom-sheet'), - BottomSheetFlatList: react.FlatList, - }, - } -}) - jest.mock('rn-fetch-blob', () => ({ config: jest.fn().mockReturnThis(), cancel: jest.fn(), diff --git a/jest/test-pds.ts b/jest/test-pds.ts new file mode 100644 index 000000000..4915b58e3 --- /dev/null +++ b/jest/test-pds.ts @@ -0,0 +1,199 @@ +import {AddressInfo} from 'net' +import os from 'os' +import path from 'path' +import * as crypto from '@atproto/crypto' +import PDSServer, { + Database as PDSDatabase, + MemoryBlobStore, + ServerConfig as PDSServerConfig, +} from '@atproto/pds' +import * as plc from '@atproto/plc' +import AtpApi, {ServiceClient} from '@atproto/api' + +export interface TestUser { + email: string + did: string + declarationCid: string + handle: string + password: string + api: ServiceClient +} + +export interface TestUsers { + alice: TestUser + bob: TestUser + carla: TestUser +} + +export interface TestPDS { + pdsUrl: string + users: TestUsers + close: () => Promise<void> +} + +// NOTE +// deterministic date generator +// we use this to ensure the mock dataset is always the same +// which is very useful when testing +function* dateGen() { + let start = 1657846031914 + while (true) { + yield new Date(start).toISOString() + start += 1e3 + } + return '' +} + +export async function createServer(): Promise<TestPDS> { + const keypair = await crypto.EcdsaKeypair.create() + + // run plc server + const plcDb = plc.Database.memory() + await plcDb.migrateToLatestOrThrow() + const plcServer = plc.PlcServer.create({db: plcDb}) + const plcListener = await plcServer.start() + const plcPort = (plcListener.address() as AddressInfo).port + const plcUrl = `http://localhost:${plcPort}` + + const recoveryKey = (await crypto.EcdsaKeypair.create()).did() + + const plcClient = new plc.PlcClient(plcUrl) + const serverDid = await plcClient.createDid( + keypair, + recoveryKey, + 'localhost', + 'https://pds.public.url', + ) + + const blobstoreLoc = path.join(os.tmpdir(), crypto.randomStr(5, 'base32')) + + const cfg = new PDSServerConfig({ + debugMode: true, + version: '0.0.0', + scheme: 'http', + hostname: 'localhost', + serverDid, + recoveryKey, + adminPassword: 'admin-pass', + inviteRequired: false, + didPlcUrl: plcUrl, + jwtSecret: 'jwt-secret', + availableUserDomains: ['.test'], + appUrlPasswordReset: 'app://forgot-password', + emailNoReplyAddress: 'noreply@blueskyweb.xyz', + publicUrl: 'https://pds.public.url', + imgUriSalt: '9dd04221f5755bce5f55f47464c27e1e', + imgUriKey: + 'f23ecd142835025f42c3db2cf25dd813956c178392760256211f9d315f8ab4d8', + dbPostgresUrl: process.env.DB_POSTGRES_URL, + blobstoreLocation: `${blobstoreLoc}/blobs`, + blobstoreTmp: `${blobstoreLoc}/tmp`, + }) + + const db = PDSDatabase.memory() + await db.migrateToLatestOrThrow() + const blobstore = new MemoryBlobStore() + + const pds = PDSServer.create({db, blobstore, keypair, config: cfg}) + const pdsServer = await pds.start() + const pdsPort = (pdsServer.address() as AddressInfo).port + const pdsUrl = `http://localhost:${pdsPort}` + const testUsers = await genMockData(pdsUrl) + + return { + pdsUrl, + users: testUsers, + async close() { + await pds.destroy() + await plcServer.destroy() + }, + } +} + +async function genMockData(pdsUrl: string): Promise<TestUsers> { + const date = dateGen() + + const clients = { + loggedout: AtpApi.service(pdsUrl), + alice: AtpApi.service(pdsUrl), + bob: AtpApi.service(pdsUrl), + carla: AtpApi.service(pdsUrl), + } + const users: TestUser[] = [ + { + email: 'alice@test.com', + did: '', + declarationCid: '', + handle: 'alice.test', + password: 'hunter2', + api: clients.alice, + }, + { + email: 'bob@test.com', + did: '', + declarationCid: '', + handle: 'bob.test', + password: 'hunter2', + api: clients.bob, + }, + { + email: 'carla@test.com', + did: '', + declarationCid: '', + handle: 'carla.test', + password: 'hunter2', + api: clients.carla, + }, + ] + const alice = users[0] + const bob = users[1] + const carla = users[2] + + let _i = 1 + for (const user of users) { + const res = await clients.loggedout.com.atproto.account.create({ + email: user.email, + handle: user.handle, + password: user.password, + }) + user.api.setHeader('Authorization', `Bearer ${res.data.accessJwt}`) + const {data: profile} = await user.api.app.bsky.actor.getProfile({ + actor: user.handle, + }) + user.did = res.data.did + user.declarationCid = profile.declaration.cid + await user.api.app.bsky.actor.profile.create( + {did: user.did}, + { + displayName: ucfirst(user.handle).slice(0, -5), + description: `Test user ${_i++}`, + }, + ) + } + + // everybody follows everybody + const follow = async (author: TestUser, subject: TestUser) => { + await author.api.app.bsky.graph.follow.create( + {did: author.did}, + { + subject: { + did: subject.did, + declarationCid: subject.declarationCid, + }, + createdAt: date.next().value, + }, + ) + } + await follow(alice, bob) + await follow(alice, carla) + await follow(bob, alice) + await follow(bob, carla) + await follow(carla, alice) + await follow(carla, bob) + + return {alice, bob, carla} +} + +function ucfirst(str: string): string { + return str.at(0)?.toUpperCase() + str.slice(1) +} diff --git a/jest/test-utils.tsx b/jest/test-utils.tsx index c84ee637e..5a74a6ef6 100644 --- a/jest/test-utils.tsx +++ b/jest/test-utils.tsx @@ -4,20 +4,19 @@ import {GestureHandlerRootView} from 'react-native-gesture-handler' import {RootSiblingParent} from 'react-native-root-siblings' import {SafeAreaProvider} from 'react-native-safe-area-context' import {RootStoreProvider} from '../src/state' +import {ThemeProvider} from '../src/view/lib/ThemeContext' import {mockedRootStore} from '../__mocks__/state-mock' -const customRender = (ui: any, storeMock?: any) => +const customRender = (ui: any, rootStore?: any) => render( // eslint-disable-next-line react-native/no-inline-styles <GestureHandlerRootView style={{flex: 1}}> <RootSiblingParent> <RootStoreProvider - value={ - storeMock != null - ? {...mockedRootStore, ...storeMock} - : mockedRootStore - }> - <SafeAreaProvider>{ui}</SafeAreaProvider> + value={rootStore != null ? rootStore : mockedRootStore}> + <ThemeProvider theme="light"> + <SafeAreaProvider>{ui}</SafeAreaProvider> + </ThemeProvider> </RootStoreProvider> </RootSiblingParent> </GestureHandlerRootView>, |