Update testrunner to use new dev-env [WIP] (#1575)
* Update testrunner to use new dev-env * Fix label testcase * Vendor the dev-infra scripts from the atproto repo for the dev-env server runner * Bump detox to fix the ios sim control issue * Use iphone 15 pro for tests * Ensure the reminders never trigger during tests * Skip the shell tests due to a crash bug with detox and the drawerzio/stable
parent
aad8d12ede
commit
0b44af38ea
|
@ -41,7 +41,7 @@ module.exports = {
|
||||||
simulator: {
|
simulator: {
|
||||||
type: 'ios.simulator',
|
type: 'ios.simulator',
|
||||||
device: {
|
device: {
|
||||||
type: 'iPhone 15',
|
type: 'iPhone 15 Pro',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
attached: {
|
attached: {
|
||||||
|
|
|
@ -502,6 +502,9 @@ async function main() {
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// flush caches
|
||||||
|
await server.mocker.testNet.processAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('Ready')
|
console.log('Ready')
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
get_container_id() {
|
||||||
|
local compose_file=$1
|
||||||
|
local service=$2
|
||||||
|
if [ -z "${compose_file}" ] || [ -z "${service}" ]; then
|
||||||
|
echo "usage: get_container_id <compose_file> <service>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker compose -f $compose_file ps --format json --status running \
|
||||||
|
| jq -r '.[]? | select(.Service == "'${service}'") | .ID'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Exports all environment variables
|
||||||
|
export_env() {
|
||||||
|
export_pg_env
|
||||||
|
export_redis_env
|
||||||
|
}
|
||||||
|
|
||||||
|
# Exports postgres environment variables
|
||||||
|
export_pg_env() {
|
||||||
|
# Based on creds in compose.yaml
|
||||||
|
export PGPORT=5433
|
||||||
|
export PGHOST=localhost
|
||||||
|
export PGUSER=pg
|
||||||
|
export PGPASSWORD=password
|
||||||
|
export PGDATABASE=postgres
|
||||||
|
export DB_POSTGRES_URL="postgresql://pg:password@127.0.0.1:5433/postgres"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Exports redis environment variables
|
||||||
|
export_redis_env() {
|
||||||
|
export REDIS_HOST="127.0.0.1:6380"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main entry point
|
||||||
|
main() {
|
||||||
|
# Expect a SERVICES env var to be set with the docker service names
|
||||||
|
local services=${SERVICES}
|
||||||
|
|
||||||
|
dir=$(dirname $0)
|
||||||
|
compose_file="${dir}/docker-compose.yaml"
|
||||||
|
|
||||||
|
# whether this particular script started the container(s)
|
||||||
|
started_container=false
|
||||||
|
|
||||||
|
# trap SIGINT and performs cleanup as necessary, i.e.
|
||||||
|
# taking down containers if this script started them
|
||||||
|
trap "on_sigint ${services}" INT
|
||||||
|
on_sigint() {
|
||||||
|
local services=$@
|
||||||
|
echo # newline
|
||||||
|
if $started_container; then
|
||||||
|
docker compose -f $compose_file rm -f --stop --volumes ${services}
|
||||||
|
fi
|
||||||
|
exit $?
|
||||||
|
}
|
||||||
|
|
||||||
|
# check if all services are running already
|
||||||
|
not_running=false
|
||||||
|
for service in $services; do
|
||||||
|
container_id=$(get_container_id $compose_file $service)
|
||||||
|
if [ -z $container_id ]; then
|
||||||
|
not_running=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# if any are missing, recreate all services
|
||||||
|
if $not_running; then
|
||||||
|
docker compose -f $compose_file up --wait --force-recreate ${services}
|
||||||
|
started_container=true
|
||||||
|
else
|
||||||
|
echo "all services ${services} are already running"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# setup environment variables and run args
|
||||||
|
export_env
|
||||||
|
"$@"
|
||||||
|
# save return code for later
|
||||||
|
code=$?
|
||||||
|
|
||||||
|
# performs cleanup as necessary, i.e. taking down containers
|
||||||
|
# if this script started them
|
||||||
|
echo # newline
|
||||||
|
if $started_container; then
|
||||||
|
docker compose -f $compose_file rm -f --stop --volumes ${services}
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit ${code}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
# An ephermerally-stored postgres database for single-use test runs
|
||||||
|
db_test: &db_test
|
||||||
|
image: postgres:14.4-alpine
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=pg
|
||||||
|
- POSTGRES_PASSWORD=password
|
||||||
|
ports:
|
||||||
|
- '5433:5432'
|
||||||
|
# Healthcheck ensures db is queryable when `docker-compose up --wait` completes
|
||||||
|
healthcheck:
|
||||||
|
test: 'pg_isready -U pg'
|
||||||
|
interval: 500ms
|
||||||
|
timeout: 10s
|
||||||
|
retries: 20
|
||||||
|
# A persistently-stored postgres database
|
||||||
|
db:
|
||||||
|
<<: *db_test
|
||||||
|
ports:
|
||||||
|
- '5432:5432'
|
||||||
|
healthcheck:
|
||||||
|
disable: true
|
||||||
|
volumes:
|
||||||
|
- atp_db:/var/lib/postgresql/data
|
||||||
|
# An ephermerally-stored redis cache for single-use test runs
|
||||||
|
redis_test: &redis_test
|
||||||
|
image: redis:7.0-alpine
|
||||||
|
ports:
|
||||||
|
- '6380:6379'
|
||||||
|
# Healthcheck ensures redis is queryable when `docker-compose up --wait` completes
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD-SHELL', '[ "$$(redis-cli ping)" = "PONG" ]']
|
||||||
|
interval: 500ms
|
||||||
|
timeout: 10s
|
||||||
|
retries: 20
|
||||||
|
# A persistently-stored redis cache
|
||||||
|
redis:
|
||||||
|
<<: *redis_test
|
||||||
|
command: redis-server --save 60 1 --loglevel warning
|
||||||
|
ports:
|
||||||
|
- '6379:6379'
|
||||||
|
healthcheck:
|
||||||
|
disable: true
|
||||||
|
volumes:
|
||||||
|
- atp_redis:/data
|
||||||
|
volumes:
|
||||||
|
atp_db:
|
||||||
|
atp_redis:
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Example usage:
|
||||||
|
# ./with-test-db.sh psql postgresql://pg:password@localhost:5433/postgres -c 'select 1;'
|
||||||
|
|
||||||
|
dir=$(dirname $0)
|
||||||
|
. ${dir}/_common.sh
|
||||||
|
|
||||||
|
SERVICES="db_test" main "$@"
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Example usage:
|
||||||
|
# ./with-test-redis-and-db.sh psql postgresql://pg:password@localhost:5433/postgres -c 'select 1;'
|
||||||
|
# ./with-test-redis-and-db.sh redis-cli -h localhost -p 6380 ping
|
||||||
|
|
||||||
|
dir=$(dirname $0)
|
||||||
|
. ${dir}/_common.sh
|
||||||
|
|
||||||
|
SERVICES="db_test redis_test" main "$@"
|
154
jest/test-pds.ts
154
jest/test-pds.ts
|
@ -1,7 +1,7 @@
|
||||||
import net from 'net'
|
import net from 'net'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import {TestNetworkNoAppView} from '@atproto/dev-env'
|
import {TestNetwork} from '@atproto/dev-env'
|
||||||
import {AtUri, BskyAgent} from '@atproto/api'
|
import {AtUri, BskyAgent} from '@atproto/api'
|
||||||
|
|
||||||
export interface TestUser {
|
export interface TestUser {
|
||||||
|
@ -18,14 +18,59 @@ export interface TestPDS {
|
||||||
close: () => Promise<void>
|
close: () => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class StringIdGenerator {
|
||||||
|
_nextId = [0]
|
||||||
|
constructor(
|
||||||
|
public _chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||||
|
) {}
|
||||||
|
|
||||||
|
next() {
|
||||||
|
const r = []
|
||||||
|
for (const char of this._nextId) {
|
||||||
|
r.unshift(this._chars[char])
|
||||||
|
}
|
||||||
|
this._increment()
|
||||||
|
return r.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
_increment() {
|
||||||
|
for (let i = 0; i < this._nextId.length; i++) {
|
||||||
|
const val = ++this._nextId[i]
|
||||||
|
if (val >= this._chars.length) {
|
||||||
|
this._nextId[i] = 0
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._nextId.push(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
*[Symbol.iterator]() {
|
||||||
|
while (true) {
|
||||||
|
yield this.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ids = new StringIdGenerator()
|
||||||
|
|
||||||
export async function createServer(
|
export async function createServer(
|
||||||
{inviteRequired}: {inviteRequired: boolean} = {inviteRequired: false},
|
{inviteRequired}: {inviteRequired: boolean} = {inviteRequired: false},
|
||||||
): Promise<TestPDS> {
|
): Promise<TestPDS> {
|
||||||
const port = await getPort()
|
const port = await getPort()
|
||||||
const port2 = await getPort(port + 1)
|
const port2 = await getPort(port + 1)
|
||||||
const pdsUrl = `http://localhost:${port}`
|
const pdsUrl = `http://localhost:${port}`
|
||||||
const testNet = await TestNetworkNoAppView.create({
|
const id = ids.next()
|
||||||
pds: {port, publicUrl: pdsUrl, inviteRequired},
|
const testNet = await TestNetwork.create({
|
||||||
|
pds: {
|
||||||
|
port,
|
||||||
|
publicUrl: pdsUrl,
|
||||||
|
inviteRequired,
|
||||||
|
dbPostgresSchema: `pds_${id}`,
|
||||||
|
},
|
||||||
|
bsky: {
|
||||||
|
dbPostgresSchema: `bsky_${id}`,
|
||||||
|
},
|
||||||
plc: {port: port2},
|
plc: {port: port2},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -48,7 +93,7 @@ class Mocker {
|
||||||
users: Record<string, TestUser> = {}
|
users: Record<string, TestUser> = {}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public testNet: TestNetworkNoAppView,
|
public testNet: TestNetwork,
|
||||||
public service: string,
|
public service: string,
|
||||||
public pic: Uint8Array,
|
public pic: Uint8Array,
|
||||||
) {
|
) {
|
||||||
|
@ -59,6 +104,10 @@ class Mocker {
|
||||||
return this.testNet.pds
|
return this.testNet.pds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get bsky() {
|
||||||
|
return this.testNet.bsky
|
||||||
|
}
|
||||||
|
|
||||||
get plc() {
|
get plc() {
|
||||||
return this.testNet.plc
|
return this.testNet.plc
|
||||||
}
|
}
|
||||||
|
@ -81,11 +130,7 @@ class Mocker {
|
||||||
const inviteRes = await agent.api.com.atproto.server.createInviteCode(
|
const inviteRes = await agent.api.com.atproto.server.createInviteCode(
|
||||||
{useCount: 1},
|
{useCount: 1},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: this.pds.adminAuthHeaders('admin'),
|
||||||
authorization: `Basic ${btoa(
|
|
||||||
`admin:${this.pds.ctx.cfg.adminPassword}`,
|
|
||||||
)}`,
|
|
||||||
},
|
|
||||||
encoding: 'application/json',
|
encoding: 'application/json',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -260,11 +305,7 @@ class Mocker {
|
||||||
await agent.api.com.atproto.server.createInviteCode(
|
await agent.api.com.atproto.server.createInviteCode(
|
||||||
{useCount: 1, forAccount},
|
{useCount: 1, forAccount},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: this.pds.adminAuthHeaders('admin'),
|
||||||
authorization: `Basic ${btoa(
|
|
||||||
`admin:${this.pds.ctx.cfg.adminPassword}`,
|
|
||||||
)}`,
|
|
||||||
},
|
|
||||||
encoding: 'application/json',
|
encoding: 'application/json',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -275,24 +316,21 @@ class Mocker {
|
||||||
if (!did) {
|
if (!did) {
|
||||||
throw new Error(`Invalid user: ${user}`)
|
throw new Error(`Invalid user: ${user}`)
|
||||||
}
|
}
|
||||||
const ctx = this.pds.ctx
|
const ctx = this.bsky.ctx
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
throw new Error('Invalid PDS')
|
throw new Error('Invalid appview')
|
||||||
}
|
}
|
||||||
|
const labelSrvc = ctx.services.label(ctx.db.getPrimary())
|
||||||
await ctx.db.db
|
await labelSrvc.createLabels([
|
||||||
.insertInto('label')
|
{
|
||||||
.values([
|
src: ctx.cfg.labelerDid,
|
||||||
{
|
uri: did,
|
||||||
src: ctx.cfg.labelerDid,
|
cid: '',
|
||||||
uri: did,
|
val: label,
|
||||||
cid: '',
|
neg: false,
|
||||||
val: label,
|
cts: new Date().toISOString(),
|
||||||
neg: 0,
|
},
|
||||||
cts: new Date().toISOString(),
|
])
|
||||||
},
|
|
||||||
])
|
|
||||||
.execute()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async labelProfile(label: string, user: string) {
|
async labelProfile(label: string, user: string) {
|
||||||
|
@ -307,43 +345,39 @@ class Mocker {
|
||||||
rkey: 'self',
|
rkey: 'self',
|
||||||
})
|
})
|
||||||
|
|
||||||
const ctx = this.pds.ctx
|
const ctx = this.bsky.ctx
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
throw new Error('Invalid PDS')
|
throw new Error('Invalid appview')
|
||||||
}
|
}
|
||||||
await ctx.db.db
|
const labelSrvc = ctx.services.label(ctx.db.getPrimary())
|
||||||
.insertInto('label')
|
await labelSrvc.createLabels([
|
||||||
.values([
|
{
|
||||||
{
|
src: ctx.cfg.labelerDid,
|
||||||
src: ctx.cfg.labelerDid,
|
uri: profile.uri,
|
||||||
uri: profile.uri,
|
cid: profile.cid,
|
||||||
cid: profile.cid,
|
val: label,
|
||||||
val: label,
|
neg: false,
|
||||||
neg: 0,
|
cts: new Date().toISOString(),
|
||||||
cts: new Date().toISOString(),
|
},
|
||||||
},
|
])
|
||||||
])
|
|
||||||
.execute()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async labelPost(label: string, {uri, cid}: {uri: string; cid: string}) {
|
async labelPost(label: string, {uri, cid}: {uri: string; cid: string}) {
|
||||||
const ctx = this.pds.ctx
|
const ctx = this.bsky.ctx
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
throw new Error('Invalid PDS')
|
throw new Error('Invalid appview')
|
||||||
}
|
}
|
||||||
await ctx.db.db
|
const labelSrvc = ctx.services.label(ctx.db.getPrimary())
|
||||||
.insertInto('label')
|
await labelSrvc.createLabels([
|
||||||
.values([
|
{
|
||||||
{
|
src: ctx.cfg.labelerDid,
|
||||||
src: ctx.cfg.labelerDid,
|
uri,
|
||||||
uri,
|
cid,
|
||||||
cid,
|
val: label,
|
||||||
val: label,
|
neg: false,
|
||||||
neg: 0,
|
cts: new Date().toISOString(),
|
||||||
cts: new Date().toISOString(),
|
},
|
||||||
},
|
])
|
||||||
])
|
|
||||||
.execute()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async createMuteList(user: string, name: string): Promise<string> {
|
async createMuteList(user: string, name: string): Promise<string> {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
"test-coverage": "jest --coverage",
|
"test-coverage": "jest --coverage",
|
||||||
"lint": "eslint ./src --ext .js,.jsx,.ts,.tsx",
|
"lint": "eslint ./src --ext .js,.jsx,.ts,.tsx",
|
||||||
"typecheck": "tsc --project ./tsconfig.check.json",
|
"typecheck": "tsc --project ./tsconfig.check.json",
|
||||||
"e2e:mock-server": "ts-node __e2e__/mock-server.ts",
|
"e2e:mock-server": "./jest/dev-infra/with-test-redis-and-db.sh ts-node __e2e__/mock-server.ts",
|
||||||
"e2e:metro": "RN_SRC_EXT=e2e.ts,e2e.tsx expo run:ios",
|
"e2e:metro": "RN_SRC_EXT=e2e.ts,e2e.tsx expo run:ios",
|
||||||
"e2e:build": "detox build -c ios.sim.debug",
|
"e2e:build": "detox build -c ios.sim.debug",
|
||||||
"e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all",
|
"e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all",
|
||||||
|
@ -186,7 +186,7 @@
|
||||||
"babel-loader": "^9.1.2",
|
"babel-loader": "^9.1.2",
|
||||||
"babel-plugin-module-resolver": "^5.0.0",
|
"babel-plugin-module-resolver": "^5.0.0",
|
||||||
"babel-plugin-react-native-web": "^0.18.12",
|
"babel-plugin-react-native-web": "^0.18.12",
|
||||||
"detox": "^20.11.3",
|
"detox": "^20.13.0",
|
||||||
"eslint": "^8.19.0",
|
"eslint": "^8.19.0",
|
||||||
"eslint-plugin-detox": "^1.0.0",
|
"eslint-plugin-detox": "^1.0.0",
|
||||||
"eslint-plugin-ft-flow": "^2.0.3",
|
"eslint-plugin-ft-flow": "^2.0.3",
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import {makeAutoObservable} from 'mobx'
|
||||||
|
import {RootStoreModel} from '../root-store'
|
||||||
|
|
||||||
|
export class Reminders {
|
||||||
|
constructor(public rootStore: RootStoreModel) {
|
||||||
|
makeAutoObservable(
|
||||||
|
this,
|
||||||
|
{serialize: false, hydrate: false},
|
||||||
|
{autoBind: true},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
hydrate(_v: unknown) {}
|
||||||
|
|
||||||
|
get shouldRequestEmailConfirmation() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
setEmailConfirmationRequested() {}
|
||||||
|
}
|
|
@ -8135,10 +8135,10 @@ detect-port-alt@^1.1.6:
|
||||||
address "^1.0.1"
|
address "^1.0.1"
|
||||||
debug "^2.6.0"
|
debug "^2.6.0"
|
||||||
|
|
||||||
detox@^20.11.3:
|
detox@^20.13.0:
|
||||||
version "20.11.3"
|
version "20.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/detox/-/detox-20.11.3.tgz#56d5ea869977f5a747e1be0901b279ab953f8b7b"
|
resolved "https://registry.yarnpkg.com/detox/-/detox-20.13.0.tgz#923111638dfdb16089eea4f07bf4f0b56468d097"
|
||||||
integrity sha512-kdoRAtDLFxXpjt1QlniI+WryMtf7Y8mrZ33Ql8cTR9qoCS/CThi4pweYAQm8yUPqAv1ZtT3eIm3EzRwjEosgLA==
|
integrity sha512-p9MUcoHWFTqSDaoaN+/hnJYdzNYqdelUr/sxzy3zLoS/qehnVJv2yG9pYqz/+gKpJaMIpw2+TVw9imdAx5JpaA==
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv "^8.6.3"
|
ajv "^8.6.3"
|
||||||
bunyan "^1.8.12"
|
bunyan "^1.8.12"
|
||||||
|
|
Loading…
Reference in New Issue