+1
-1
.detoxrc.js
+1
-1
.detoxrc.js
+3
__e2e__/mock-server.ts
+3
__e2e__/mock-server.ts
__e2e__/tests/shell.test.ts
__e2e__/tests/shell.test.skip.ts
__e2e__/tests/shell.test.ts
__e2e__/tests/shell.test.skip.ts
+92
jest/dev-infra/_common.sh
+92
jest/dev-infra/_common.sh
···
1
+
#!/usr/bin/env sh
2
+
3
+
get_container_id() {
4
+
local compose_file=$1
5
+
local service=$2
6
+
if [ -z "${compose_file}" ] || [ -z "${service}" ]; then
7
+
echo "usage: get_container_id <compose_file> <service>"
8
+
exit 1
9
+
fi
10
+
11
+
docker compose -f $compose_file ps --format json --status running \
12
+
| jq -r '.[]? | select(.Service == "'${service}'") | .ID'
13
+
}
14
+
15
+
# Exports all environment variables
16
+
export_env() {
17
+
export_pg_env
18
+
export_redis_env
19
+
}
20
+
21
+
# Exports postgres environment variables
22
+
export_pg_env() {
23
+
# Based on creds in compose.yaml
24
+
export PGPORT=5433
25
+
export PGHOST=localhost
26
+
export PGUSER=pg
27
+
export PGPASSWORD=password
28
+
export PGDATABASE=postgres
29
+
export DB_POSTGRES_URL="postgresql://pg:password@127.0.0.1:5433/postgres"
30
+
}
31
+
32
+
# Exports redis environment variables
33
+
export_redis_env() {
34
+
export REDIS_HOST="127.0.0.1:6380"
35
+
}
36
+
37
+
# Main entry point
38
+
main() {
39
+
# Expect a SERVICES env var to be set with the docker service names
40
+
local services=${SERVICES}
41
+
42
+
dir=$(dirname $0)
43
+
compose_file="${dir}/docker-compose.yaml"
44
+
45
+
# whether this particular script started the container(s)
46
+
started_container=false
47
+
48
+
# trap SIGINT and performs cleanup as necessary, i.e.
49
+
# taking down containers if this script started them
50
+
trap "on_sigint ${services}" INT
51
+
on_sigint() {
52
+
local services=$@
53
+
echo # newline
54
+
if $started_container; then
55
+
docker compose -f $compose_file rm -f --stop --volumes ${services}
56
+
fi
57
+
exit $?
58
+
}
59
+
60
+
# check if all services are running already
61
+
not_running=false
62
+
for service in $services; do
63
+
container_id=$(get_container_id $compose_file $service)
64
+
if [ -z $container_id ]; then
65
+
not_running=true
66
+
break
67
+
fi
68
+
done
69
+
70
+
# if any are missing, recreate all services
71
+
if $not_running; then
72
+
docker compose -f $compose_file up --wait --force-recreate ${services}
73
+
started_container=true
74
+
else
75
+
echo "all services ${services} are already running"
76
+
fi
77
+
78
+
# setup environment variables and run args
79
+
export_env
80
+
"$@"
81
+
# save return code for later
82
+
code=$?
83
+
84
+
# performs cleanup as necessary, i.e. taking down containers
85
+
# if this script started them
86
+
echo # newline
87
+
if $started_container; then
88
+
docker compose -f $compose_file rm -f --stop --volumes ${services}
89
+
fi
90
+
91
+
exit ${code}
92
+
}
+49
jest/dev-infra/docker-compose.yaml
+49
jest/dev-infra/docker-compose.yaml
···
1
+
version: '3.8'
2
+
services:
3
+
# An ephermerally-stored postgres database for single-use test runs
4
+
db_test: &db_test
5
+
image: postgres:14.4-alpine
6
+
environment:
7
+
- POSTGRES_USER=pg
8
+
- POSTGRES_PASSWORD=password
9
+
ports:
10
+
- '5433:5432'
11
+
# Healthcheck ensures db is queryable when `docker-compose up --wait` completes
12
+
healthcheck:
13
+
test: 'pg_isready -U pg'
14
+
interval: 500ms
15
+
timeout: 10s
16
+
retries: 20
17
+
# A persistently-stored postgres database
18
+
db:
19
+
<<: *db_test
20
+
ports:
21
+
- '5432:5432'
22
+
healthcheck:
23
+
disable: true
24
+
volumes:
25
+
- atp_db:/var/lib/postgresql/data
26
+
# An ephermerally-stored redis cache for single-use test runs
27
+
redis_test: &redis_test
28
+
image: redis:7.0-alpine
29
+
ports:
30
+
- '6380:6379'
31
+
# Healthcheck ensures redis is queryable when `docker-compose up --wait` completes
32
+
healthcheck:
33
+
test: ['CMD-SHELL', '[ "$$(redis-cli ping)" = "PONG" ]']
34
+
interval: 500ms
35
+
timeout: 10s
36
+
retries: 20
37
+
# A persistently-stored redis cache
38
+
redis:
39
+
<<: *redis_test
40
+
command: redis-server --save 60 1 --loglevel warning
41
+
ports:
42
+
- '6379:6379'
43
+
healthcheck:
44
+
disable: true
45
+
volumes:
46
+
- atp_redis:/data
47
+
volumes:
48
+
atp_db:
49
+
atp_redis:
+9
jest/dev-infra/with-test-db.sh
+9
jest/dev-infra/with-test-db.sh
+10
jest/dev-infra/with-test-redis-and-db.sh
+10
jest/dev-infra/with-test-redis-and-db.sh
···
1
+
#!/usr/bin/env sh
2
+
3
+
# Example usage:
4
+
# ./with-test-redis-and-db.sh psql postgresql://pg:password@localhost:5433/postgres -c 'select 1;'
5
+
# ./with-test-redis-and-db.sh redis-cli -h localhost -p 6380 ping
6
+
7
+
dir=$(dirname $0)
8
+
. ${dir}/_common.sh
9
+
10
+
SERVICES="db_test redis_test" main "$@"
+94
-60
jest/test-pds.ts
+94
-60
jest/test-pds.ts
···
1
1
import net from 'net'
2
2
import path from 'path'
3
3
import fs from 'fs'
4
-
import {TestNetworkNoAppView} from '@atproto/dev-env'
4
+
import {TestNetwork} from '@atproto/dev-env'
5
5
import {AtUri, BskyAgent} from '@atproto/api'
6
6
7
7
export interface TestUser {
···
18
18
close: () => Promise<void>
19
19
}
20
20
21
+
class StringIdGenerator {
22
+
_nextId = [0]
23
+
constructor(
24
+
public _chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
25
+
) {}
26
+
27
+
next() {
28
+
const r = []
29
+
for (const char of this._nextId) {
30
+
r.unshift(this._chars[char])
31
+
}
32
+
this._increment()
33
+
return r.join('')
34
+
}
35
+
36
+
_increment() {
37
+
for (let i = 0; i < this._nextId.length; i++) {
38
+
const val = ++this._nextId[i]
39
+
if (val >= this._chars.length) {
40
+
this._nextId[i] = 0
41
+
} else {
42
+
return
43
+
}
44
+
}
45
+
this._nextId.push(0)
46
+
}
47
+
48
+
*[Symbol.iterator]() {
49
+
while (true) {
50
+
yield this.next()
51
+
}
52
+
}
53
+
}
54
+
55
+
const ids = new StringIdGenerator()
56
+
21
57
export async function createServer(
22
58
{inviteRequired}: {inviteRequired: boolean} = {inviteRequired: false},
23
59
): Promise<TestPDS> {
24
60
const port = await getPort()
25
61
const port2 = await getPort(port + 1)
26
62
const pdsUrl = `http://localhost:${port}`
27
-
const testNet = await TestNetworkNoAppView.create({
28
-
pds: {port, publicUrl: pdsUrl, inviteRequired},
63
+
const id = ids.next()
64
+
const testNet = await TestNetwork.create({
65
+
pds: {
66
+
port,
67
+
publicUrl: pdsUrl,
68
+
inviteRequired,
69
+
dbPostgresSchema: `pds_${id}`,
70
+
},
71
+
bsky: {
72
+
dbPostgresSchema: `bsky_${id}`,
73
+
},
29
74
plc: {port: port2},
30
75
})
31
76
···
48
93
users: Record<string, TestUser> = {}
49
94
50
95
constructor(
51
-
public testNet: TestNetworkNoAppView,
96
+
public testNet: TestNetwork,
52
97
public service: string,
53
98
public pic: Uint8Array,
54
99
) {
···
59
104
return this.testNet.pds
60
105
}
61
106
107
+
get bsky() {
108
+
return this.testNet.bsky
109
+
}
110
+
62
111
get plc() {
63
112
return this.testNet.plc
64
113
}
···
81
130
const inviteRes = await agent.api.com.atproto.server.createInviteCode(
82
131
{useCount: 1},
83
132
{
84
-
headers: {
85
-
authorization: `Basic ${btoa(
86
-
`admin:${this.pds.ctx.cfg.adminPassword}`,
87
-
)}`,
88
-
},
133
+
headers: this.pds.adminAuthHeaders('admin'),
89
134
encoding: 'application/json',
90
135
},
91
136
)
···
260
305
await agent.api.com.atproto.server.createInviteCode(
261
306
{useCount: 1, forAccount},
262
307
{
263
-
headers: {
264
-
authorization: `Basic ${btoa(
265
-
`admin:${this.pds.ctx.cfg.adminPassword}`,
266
-
)}`,
267
-
},
308
+
headers: this.pds.adminAuthHeaders('admin'),
268
309
encoding: 'application/json',
269
310
},
270
311
)
···
275
316
if (!did) {
276
317
throw new Error(`Invalid user: ${user}`)
277
318
}
278
-
const ctx = this.pds.ctx
319
+
const ctx = this.bsky.ctx
279
320
if (!ctx) {
280
-
throw new Error('Invalid PDS')
321
+
throw new Error('Invalid appview')
281
322
}
282
-
283
-
await ctx.db.db
284
-
.insertInto('label')
285
-
.values([
286
-
{
287
-
src: ctx.cfg.labelerDid,
288
-
uri: did,
289
-
cid: '',
290
-
val: label,
291
-
neg: 0,
292
-
cts: new Date().toISOString(),
293
-
},
294
-
])
295
-
.execute()
323
+
const labelSrvc = ctx.services.label(ctx.db.getPrimary())
324
+
await labelSrvc.createLabels([
325
+
{
326
+
src: ctx.cfg.labelerDid,
327
+
uri: did,
328
+
cid: '',
329
+
val: label,
330
+
neg: false,
331
+
cts: new Date().toISOString(),
332
+
},
333
+
])
296
334
}
297
335
298
336
async labelProfile(label: string, user: string) {
···
307
345
rkey: 'self',
308
346
})
309
347
310
-
const ctx = this.pds.ctx
348
+
const ctx = this.bsky.ctx
311
349
if (!ctx) {
312
-
throw new Error('Invalid PDS')
350
+
throw new Error('Invalid appview')
313
351
}
314
-
await ctx.db.db
315
-
.insertInto('label')
316
-
.values([
317
-
{
318
-
src: ctx.cfg.labelerDid,
319
-
uri: profile.uri,
320
-
cid: profile.cid,
321
-
val: label,
322
-
neg: 0,
323
-
cts: new Date().toISOString(),
324
-
},
325
-
])
326
-
.execute()
352
+
const labelSrvc = ctx.services.label(ctx.db.getPrimary())
353
+
await labelSrvc.createLabels([
354
+
{
355
+
src: ctx.cfg.labelerDid,
356
+
uri: profile.uri,
357
+
cid: profile.cid,
358
+
val: label,
359
+
neg: false,
360
+
cts: new Date().toISOString(),
361
+
},
362
+
])
327
363
}
328
364
329
365
async labelPost(label: string, {uri, cid}: {uri: string; cid: string}) {
330
-
const ctx = this.pds.ctx
366
+
const ctx = this.bsky.ctx
331
367
if (!ctx) {
332
-
throw new Error('Invalid PDS')
368
+
throw new Error('Invalid appview')
333
369
}
334
-
await ctx.db.db
335
-
.insertInto('label')
336
-
.values([
337
-
{
338
-
src: ctx.cfg.labelerDid,
339
-
uri,
340
-
cid,
341
-
val: label,
342
-
neg: 0,
343
-
cts: new Date().toISOString(),
344
-
},
345
-
])
346
-
.execute()
370
+
const labelSrvc = ctx.services.label(ctx.db.getPrimary())
371
+
await labelSrvc.createLabels([
372
+
{
373
+
src: ctx.cfg.labelerDid,
374
+
uri,
375
+
cid,
376
+
val: label,
377
+
neg: false,
378
+
cts: new Date().toISOString(),
379
+
},
380
+
])
347
381
}
348
382
349
383
async createMuteList(user: string, name: string): Promise<string> {
+2
-2
package.json
+2
-2
package.json
···
18
18
"test-coverage": "jest --coverage",
19
19
"lint": "eslint ./src --ext .js,.jsx,.ts,.tsx",
20
20
"typecheck": "tsc --project ./tsconfig.check.json",
21
-
"e2e:mock-server": "ts-node __e2e__/mock-server.ts",
21
+
"e2e:mock-server": "./jest/dev-infra/with-test-redis-and-db.sh ts-node __e2e__/mock-server.ts",
22
22
"e2e:metro": "RN_SRC_EXT=e2e.ts,e2e.tsx expo run:ios",
23
23
"e2e:build": "detox build -c ios.sim.debug",
24
24
"e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all",
···
186
186
"babel-loader": "^9.1.2",
187
187
"babel-plugin-module-resolver": "^5.0.0",
188
188
"babel-plugin-react-native-web": "^0.18.12",
189
-
"detox": "^20.11.3",
189
+
"detox": "^20.13.0",
190
190
"eslint": "^8.19.0",
191
191
"eslint-plugin-detox": "^1.0.0",
192
192
"eslint-plugin-ft-flow": "^2.0.3",
+24
src/state/models/ui/reminders.e2e.ts
+24
src/state/models/ui/reminders.e2e.ts
···
1
+
import {makeAutoObservable} from 'mobx'
2
+
import {RootStoreModel} from '../root-store'
3
+
4
+
export class Reminders {
5
+
constructor(public rootStore: RootStoreModel) {
6
+
makeAutoObservable(
7
+
this,
8
+
{serialize: false, hydrate: false},
9
+
{autoBind: true},
10
+
)
11
+
}
12
+
13
+
serialize() {
14
+
return {}
15
+
}
16
+
17
+
hydrate(_v: unknown) {}
18
+
19
+
get shouldRequestEmailConfirmation() {
20
+
return false
21
+
}
22
+
23
+
setEmailConfirmationRequested() {}
24
+
}
+4
-4
yarn.lock
+4
-4
yarn.lock
···
8135
8135
address "^1.0.1"
8136
8136
debug "^2.6.0"
8137
8137
8138
-
detox@^20.11.3:
8139
-
version "20.11.3"
8140
-
resolved "https://registry.yarnpkg.com/detox/-/detox-20.11.3.tgz#56d5ea869977f5a747e1be0901b279ab953f8b7b"
8141
-
integrity sha512-kdoRAtDLFxXpjt1QlniI+WryMtf7Y8mrZ33Ql8cTR9qoCS/CThi4pweYAQm8yUPqAv1ZtT3eIm3EzRwjEosgLA==
8138
+
detox@^20.13.0:
8139
+
version "20.13.0"
8140
+
resolved "https://registry.yarnpkg.com/detox/-/detox-20.13.0.tgz#923111638dfdb16089eea4f07bf4f0b56468d097"
8141
+
integrity sha512-p9MUcoHWFTqSDaoaN+/hnJYdzNYqdelUr/sxzy3zLoS/qehnVJv2yG9pYqz/+gKpJaMIpw2+TVw9imdAx5JpaA==
8142
8142
dependencies:
8143
8143
ajv "^8.6.3"
8144
8144
bunyan "^1.8.12"