Revamp dev env ()

* wip

* wip

* fully refactor

* msgs & ports

* revamp appview tests

* revamp pds tests

* ensure plc close

* ensure pds appview is hooked up when not running appview

* move service setup to dev env

* move admin auth headers to dev-env

* fix service headers

* missing port

* fixing ports

* start services in order

* startup plc immediately

* rm dead code

* fix up build

* rm log
This commit is contained in:
Daniel Holmgren 2023-05-15 12:28:36 -05:00 committed by GitHub
parent 231729db72
commit 47d30c1796
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 988 additions and 1456 deletions

@ -50,6 +50,7 @@ export class BskyAppView {
config: ServerConfig
imgInvalidator?: ImageInvalidator
}): BskyAppView {
console.log('CREATING bsky')
const { db, config } = opts
let maybeImgInvalidator = opts.imgInvalidator
const app = express()
@ -103,6 +104,7 @@ export class BskyAppView {
})
}
console.log('services')
const services = createServices({
imgUriBuilder,
imgInvalidator,
@ -120,6 +122,8 @@ export class BskyAppView {
labeler,
})
console.log('server')
let server = createServer({
validateResponse: config.debugMode,
payload: {
@ -139,6 +143,7 @@ export class BskyAppView {
app.use(server.xrpc.router)
app.use(error.handler)
console.log('PROVIDER: ', config.repoProvider)
const sub = config.repoProvider
? new RepoSubscription(ctx, config.repoProvider, config.repoSubLockId)
: undefined

@ -1,36 +1,12 @@
import { wait } from '@atproto/common'
import { AtUri } from '@atproto/uri'
import { lexToJson } from '@atproto/lexicon'
import { TestEnvInfo } from '@atproto/dev-env'
import { CID } from 'multiformats/cid'
import * as uint8arrays from 'uint8arrays'
import {
FeedViewPost,
PostView,
isThreadViewPost,
} from '../src/lexicon/types/app/bsky/feed/defs'
import { isViewRecord } from '../src/lexicon/types/app/bsky/embed/record'
import { createServiceJwt } from '@atproto/xrpc-server'
// for pds
export const adminAuth = () => {
return (
'Basic ' +
uint8arrays.toString(
uint8arrays.fromString('admin:admin-pass', 'utf8'),
'base64pad',
)
)
}
export const appViewHeaders = async (did: string, env: TestEnvInfo) => {
const jwt = await createServiceJwt({
iss: did,
aud: env.bsky.ctx.cfg.serverDid,
keypair: env.pds.ctx.repoSigningKey,
})
return { authorization: `Bearer ${jwt}` }
}
// Swap out identifiers and dates with stable
// values for the purpose of snapshot testing
@ -166,25 +142,6 @@ export const paginateAll = async <T extends { cursor?: string }>(
return results
}
export const processAll = async (server: TestEnvInfo, timeout = 5000) => {
const { bsky, pds } = server
const sub = bsky.sub
if (!sub) return
const { db } = pds.ctx.db
const start = Date.now()
while (Date.now() - start < timeout) {
await wait(50)
if (!sub) return
const state = await sub.getState()
const { lastSeq } = await db
.selectFrom('repo_seq')
.select(db.fn.max('repo_seq.seq').as('lastSeq'))
.executeTakeFirstOrThrow()
if (state.cursor === lastSeq) return
}
throw new Error(`Sequence was not processed within ${timeout}ms`)
}
// @NOTE mutates
export const stripViewer = <T extends { viewer?: Record<string, unknown> }>(
val: T,

@ -1,37 +1,35 @@
import axios, { AxiosInstance } from 'axios'
import { CID } from 'multiformats/cid'
import { AtpAgent } from '@atproto/api'
import { verifyCidForBytes } from '@atproto/common'
import { runTestEnv, TestEnvInfo } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { SeedClient } from './seeds/client'
import basicSeed from './seeds/basic'
import { randomBytes } from '@atproto/crypto'
import { processAll } from './_util'
describe('blob resolver', () => {
let testEnv: TestEnvInfo
let network: TestNetwork
let client: AxiosInstance
let fileDid: string
let fileCid: CID
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_blob_resolver',
})
const pdsAgent = new AtpAgent({ service: testEnv.pds.url })
const pdsAgent = network.pds.getClient()
const sc = new SeedClient(pdsAgent)
await basicSeed(sc)
await processAll(testEnv)
await network.processAll()
fileDid = sc.dids.carol
fileCid = sc.posts[fileDid][0].images[0].image.ref
client = axios.create({
baseURL: testEnv.bsky.url,
baseURL: network.bsky.url,
validateStatus: () => true,
})
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
it('resolves blob with good signature check.', async () => {
@ -80,8 +78,8 @@ describe('blob resolver', () => {
})
it('fails on blob with bad signature check.', async () => {
await testEnv.pds.ctx.blobstore.delete(fileCid)
await testEnv.pds.ctx.blobstore.putPermanent(fileCid, randomBytes(100))
await network.pds.ctx.blobstore.delete(fileCid)
await network.pds.ctx.blobstore.putPermanent(fileCid, randomBytes(100))
const tryGetBlob = client.get(`/blob/${fileDid}/${fileCid.toString()}`)
await expect(tryGetBlob).rejects.toThrow(
'maxContentLength size of -1 exceeded',

@ -1,22 +1,22 @@
import { once } from 'events'
import { wait } from '@atproto/common'
import { TestEnvInfo, runTestEnv } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { Database } from '../src'
import { Leader } from '../src/db/leader'
describe('db', () => {
let testEnv: TestEnvInfo
let network: TestNetwork
let db: Database
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_db',
})
db = testEnv.bsky.ctx.db
db = network.bsky.ctx.db
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
describe('transaction()', () => {

@ -1,6 +1,5 @@
import AtpAgent from '@atproto/api'
import { TestEnvInfo, runTestEnv } from '@atproto/dev-env'
import { processAll } from './_util'
import { TestNetwork } from '@atproto/dev-env'
import { SeedClient } from './seeds/client'
import userSeed from './seeds/users'
import { DidResolver } from '@atproto/did-resolver'
@ -8,7 +7,7 @@ import DidSqlCache from '../src/did-cache'
import { wait } from '@atproto/common'
describe('did cache', () => {
let testEnv: TestEnvInfo
let network: TestNetwork
let sc: SeedClient
let didResolver: DidResolver
let didCache: DidSqlCache
@ -19,15 +18,15 @@ describe('did cache', () => {
let dan: string
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_did_cache',
})
didResolver = testEnv.bsky.ctx.didResolver
didCache = testEnv.bsky.ctx.didCache
const pdsAgent = new AtpAgent({ service: testEnv.pds.url })
didResolver = network.bsky.ctx.didResolver
didCache = network.bsky.ctx.didCache
const pdsAgent = new AtpAgent({ service: network.pds.url })
sc = new SeedClient(pdsAgent)
await userSeed(sc)
await processAll(testEnv)
await network.processAll()
alice = sc.dids.alice
bob = sc.dids.bob
carol = sc.dids.carol
@ -35,7 +34,7 @@ describe('did cache', () => {
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
it('caches dids on lookup', async () => {
@ -85,9 +84,9 @@ describe('did cache', () => {
})
it('accurately reports expired dids & refreshes the cache', async () => {
const didCache = new DidSqlCache(testEnv.bsky.ctx.db, 1, 60000)
const didCache = new DidSqlCache(network.bsky.ctx.db, 1, 60000)
const shortCacheResolver = new DidResolver(
{ plcUrl: testEnv.bsky.ctx.cfg.didPlcUrl },
{ plcUrl: network.bsky.ctx.cfg.didPlcUrl },
didCache,
)
const doc = await shortCacheResolver.resolveDid(alice)
@ -114,9 +113,9 @@ describe('did cache', () => {
})
it('does not return expired dids & refreshes the cache', async () => {
const didCache = new DidSqlCache(testEnv.bsky.ctx.db, 0, 1)
const didCache = new DidSqlCache(network.bsky.ctx.db, 0, 1)
const shortExpireResolver = new DidResolver(
{ plcUrl: testEnv.bsky.ctx.cfg.didPlcUrl },
{ plcUrl: network.bsky.ctx.cfg.didPlcUrl },
didCache,
)
const doc = await shortExpireResolver.resolveDid(alice)

@ -1,28 +1,28 @@
import { AtUri } from '@atproto/uri'
import { cidForCbor, TID } from '@atproto/common'
import { WriteOpAction } from '@atproto/repo'
import { runTestEnv, TestEnvInfo } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { Database } from '../src'
import * as lex from '../src/lexicon/lexicons'
import { Services } from '../src/services'
describe('duplicate record', () => {
let testEnv: TestEnvInfo
let network: TestNetwork
let did: string
let db: Database
let services: Services
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_duplicates',
})
db = testEnv.bsky.ctx.db
services = testEnv.bsky.ctx.services
db = network.bsky.ctx.db
services = network.bsky.ctx.services
did = 'did:example:alice'
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
const countRecords = async (db: Database, table: string) => {

@ -2,20 +2,19 @@ import axios, { AxiosInstance } from 'axios'
import { CID } from 'multiformats/cid'
import { AtpAgent } from '@atproto/api'
import { cidForCbor } from '@atproto/common'
import { runTestEnv, TestEnvInfo } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { getInfo } from '../../src/image/sharp'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
import { processAll } from '../_util'
describe('image processing server', () => {
let testEnv: TestEnvInfo
let network: TestNetwork
let client: AxiosInstance
let fileDid: string
let fileCid: CID
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_image_processing_server',
bsky: {
imgUriKey:
@ -23,25 +22,25 @@ describe('image processing server', () => {
imgUriSalt: '9dd04221f5755bce5f55f47464c27e1e',
},
})
const pdsAgent = new AtpAgent({ service: testEnv.pds.url })
const pdsAgent = new AtpAgent({ service: network.pds.url })
const sc = new SeedClient(pdsAgent)
await basicSeed(sc)
await processAll(testEnv)
await network.processAll()
fileDid = sc.dids.carol
fileCid = sc.posts[fileDid][0].images[0].image.ref
client = axios.create({
baseURL: `${testEnv.bsky.url}/image`,
baseURL: `${network.bsky.url}/image`,
validateStatus: () => true,
})
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
it('processes image from blob resolver.', async () => {
const res = await client.get(
testEnv.bsky.ctx.imgUriBuilder.getSignedPath({
network.bsky.ctx.imgUriBuilder.getSignedPath({
did: fileDid,
cid: fileCid,
format: 'jpeg',
@ -71,7 +70,7 @@ describe('image processing server', () => {
})
it('caches results.', async () => {
const path = testEnv.bsky.ctx.imgUriBuilder.getSignedPath({
const path = network.bsky.ctx.imgUriBuilder.getSignedPath({
did: fileDid,
cid: fileCid,
format: 'jpeg',
@ -89,7 +88,7 @@ describe('image processing server', () => {
})
it('errors on bad signature.', async () => {
const path = testEnv.bsky.ctx.imgUriBuilder.getSignedPath({
const path = network.bsky.ctx.imgUriBuilder.getSignedPath({
did: fileDid,
cid: fileCid,
format: 'jpeg',
@ -106,7 +105,7 @@ describe('image processing server', () => {
it('errors on missing file.', async () => {
const missingCid = await cidForCbor('missing-file')
const res = await client.get(
testEnv.bsky.ctx.imgUriBuilder.getSignedPath({
network.bsky.ctx.imgUriBuilder.getSignedPath({
did: fileDid,
cid: missingCid,
format: 'jpeg',

@ -6,8 +6,8 @@ import { WriteOpAction } from '@atproto/repo'
import { AtUri } from '@atproto/uri'
import { Client } from '@did-plc/lib'
import AtpAgent, { AppBskyActorProfile, AppBskyFeedPost } from '@atproto/api'
import { runTestEnv, TestEnvInfo } from '@atproto/dev-env'
import { appViewHeaders, forSnapshot, processAll } from './_util'
import { TestNetwork } from '@atproto/dev-env'
import { forSnapshot } from './_util'
import { SeedClient } from './seeds/client'
import usersSeed from './seeds/users'
import basicSeed from './seeds/basic'
@ -15,30 +15,30 @@ import { ids } from '../src/lexicon/lexicons'
import { Database } from '../src/db'
describe('indexing', () => {
let testEnv: TestEnvInfo
let network: TestNetwork
let agent: AtpAgent
let pdsAgent: AtpAgent
let sc: SeedClient
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_indexing',
})
agent = new AtpAgent({ service: testEnv.bsky.url })
pdsAgent = new AtpAgent({ service: testEnv.pds.url })
agent = network.bsky.getClient()
pdsAgent = network.pds.getClient()
sc = new SeedClient(pdsAgent)
await usersSeed(sc)
// Data in tests is not processed from subscription
await processAll(testEnv)
await testEnv.bsky.sub.destroy()
await network.processAll()
await network.bsky.sub.destroy()
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
it('indexes posts.', async () => {
const { db, services } = testEnv.bsky.ctx
const { db, services } = network.bsky.ctx
const createdAt = new Date().toISOString()
const createRecord = await prepareCreate({
did: sc.dids.alice,
@ -95,7 +95,7 @@ describe('indexing', () => {
const getAfterCreate = await agent.api.app.bsky.feed.getPostThread(
{ uri: uri.toString() },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
expect(forSnapshot(getAfterCreate.data)).toMatchSnapshot()
const createNotifications = await getNotifications(db, uri)
@ -107,7 +107,7 @@ describe('indexing', () => {
const getAfterUpdate = await agent.api.app.bsky.feed.getPostThread(
{ uri: uri.toString() },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
expect(forSnapshot(getAfterUpdate.data)).toMatchSnapshot()
const updateNotifications = await getNotifications(db, uri)
@ -119,7 +119,7 @@ describe('indexing', () => {
const getAfterDelete = agent.api.app.bsky.feed.getPostThread(
{ uri: uri.toString() },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
await expect(getAfterDelete).rejects.toThrow(/Post not found:/)
const deleteNotifications = await getNotifications(db, uri)
@ -134,7 +134,7 @@ describe('indexing', () => {
})
it('indexes profiles.', async () => {
const { db, services } = testEnv.bsky.ctx
const { db, services } = network.bsky.ctx
const createRecord = await prepareCreate({
did: sc.dids.dan,
collection: ids.AppBskyActorProfile,
@ -167,7 +167,7 @@ describe('indexing', () => {
const getAfterCreate = await agent.api.app.bsky.actor.getProfile(
{ actor: sc.dids.dan },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
expect(forSnapshot(getAfterCreate.data)).toMatchSnapshot()
@ -178,7 +178,7 @@ describe('indexing', () => {
const getAfterUpdate = await agent.api.app.bsky.actor.getProfile(
{ actor: sc.dids.dan },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
expect(forSnapshot(getAfterUpdate.data)).toMatchSnapshot()
@ -189,33 +189,33 @@ describe('indexing', () => {
const getAfterDelete = await agent.api.app.bsky.actor.getProfile(
{ actor: sc.dids.dan },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
expect(forSnapshot(getAfterDelete.data)).toMatchSnapshot()
})
describe('indexRepo', () => {
beforeAll(async () => {
testEnv.bsky.sub.resume()
network.bsky.sub.resume()
await basicSeed(sc, false)
await processAll(testEnv)
await testEnv.bsky.sub.destroy()
await network.processAll()
await network.bsky.sub.destroy()
})
it('preserves indexes when no record changes.', async () => {
const { db, services } = testEnv.bsky.ctx
const { db, services } = network.bsky.ctx
// Mark originals
const { data: origProfile } = await agent.api.app.bsky.actor.getProfile(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
const { data: origFeed } = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
const { data: origFollows } = await agent.api.app.bsky.graph.getFollows(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
// Index
const { data: head } = await pdsAgent.api.com.atproto.sync.getHead({
@ -227,15 +227,15 @@ describe('indexing', () => {
// Check
const { data: profile } = await agent.api.app.bsky.actor.getProfile(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
const { data: feed } = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
const { data: follows } = await agent.api.app.bsky.graph.getFollows(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
expect(forSnapshot([origProfile, origFeed, origFollows])).toEqual(
forSnapshot([profile, feed, follows]),
@ -243,7 +243,7 @@ describe('indexing', () => {
})
it('updates indexes when records change.', async () => {
const { db, services } = testEnv.bsky.ctx
const { db, services } = network.bsky.ctx
// Update profile
await pdsAgent.api.com.atproto.repo.putRecord(
{
@ -272,15 +272,15 @@ describe('indexing', () => {
// Check
const { data: profile } = await agent.api.app.bsky.actor.getProfile(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
const { data: feed } = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
const { data: follows } = await agent.api.app.bsky.graph.getFollows(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
expect(profile.description).toEqual('freshening things up')
expect(feed.feed[0].post.uri).toEqual(newPost.ref.uriStr)
@ -290,8 +290,8 @@ describe('indexing', () => {
})
it('skips invalid records.', async () => {
const { db, services } = testEnv.bsky.ctx
const { db: pdsDb, services: pdsServices } = testEnv.pds.ctx
const { db, services } = network.bsky.ctx
const { db: pdsDb, services: pdsServices } = network.pds.ctx
// Create a good and a bad post record
const writes = await Promise.all([
pdsRepo.prepareCreate({
@ -319,12 +319,12 @@ describe('indexing', () => {
// Check
const getGoodPost = agent.api.app.bsky.feed.getPostThread(
{ uri: writes[0].uri.toString(), depth: 0 },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
await expect(getGoodPost).resolves.toBeDefined()
const getBadPost = agent.api.app.bsky.feed.getPostThread(
{ uri: writes[1].uri.toString(), depth: 0 },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
await expect(getBadPost).rejects.toThrow('Post not found')
})
@ -334,15 +334,15 @@ describe('indexing', () => {
const getIndexedHandle = async (did) => {
const res = await agent.api.app.bsky.actor.getProfile(
{ actor: did },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
return res.data.handle
}
it('indexes handle for a fresh did', async () => {
const { db, services } = testEnv.bsky.ctx
const { db, services } = network.bsky.ctx
const now = new Date().toISOString()
const sessionAgent = new AtpAgent({ service: testEnv.pds.url })
const sessionAgent = new AtpAgent({ service: network.pds.url })
const {
data: { did },
} = await sessionAgent.createAccount({
@ -356,9 +356,9 @@ describe('indexing', () => {
})
it('reindexes handle for existing did when forced', async () => {
const { db, services } = testEnv.bsky.ctx
const { db, services } = network.bsky.ctx
const now = new Date().toISOString()
const sessionAgent = new AtpAgent({ service: testEnv.pds.url })
const sessionAgent = new AtpAgent({ service: network.pds.url })
const {
data: { did },
} = await sessionAgent.createAccount({
@ -382,10 +382,10 @@ describe('indexing', () => {
describe('tombstoneActor', () => {
it('does not unindex actor when their did is not tombstoned', async () => {
const { db, services } = testEnv.bsky.ctx
const { db, services } = network.bsky.ctx
const { data: profileBefore } = await agent.api.app.bsky.actor.getProfile(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(sc.dids.bob, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.bob) },
)
// Attempt indexing tombstone
await db.transaction((tx) =>
@ -393,28 +393,28 @@ describe('indexing', () => {
)
const { data: profileAfter } = await agent.api.app.bsky.actor.getProfile(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(sc.dids.bob, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.bob) },
)
expect(profileAfter).toEqual(profileBefore)
})
it('unindexes actor when their did is tombstoned', async () => {
const { db, services } = testEnv.bsky.ctx
const { db, services } = network.bsky.ctx
const getProfileBefore = agent.api.app.bsky.actor.getProfile(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(sc.dids.bob, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.bob) },
)
await expect(getProfileBefore).resolves.toBeDefined()
// Tombstone alice's did
const plcClient = new Client(testEnv.plc.url)
await plcClient.tombstone(sc.dids.alice, testEnv.pds.ctx.plcRotationKey)
const plcClient = new Client(network.plc.url)
await plcClient.tombstone(sc.dids.alice, network.pds.ctx.plcRotationKey)
// Index tombstone
await db.transaction((tx) =>
services.indexing(tx).tombstoneActor(sc.dids.alice),
)
const getProfileAfter = agent.api.app.bsky.actor.getProfile(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(sc.dids.bob, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.bob) },
)
await expect(getProfileAfter).rejects.toThrow('Profile not found')
})

@ -7,14 +7,13 @@ import { keywordLabeling } from '../../src/labeler/util'
import { cidForCbor, streamToBytes, TID } from '@atproto/common'
import * as ui8 from 'uint8arrays'
import { LabelService } from '../../src/services/label'
import { TestEnvInfo, runTestEnv } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { DidResolver } from '@atproto/did-resolver'
import { SeedClient } from '../seeds/client'
import usersSeed from '../seeds/users'
import { processAll } from '../_util'
describe('labeler', () => {
let testEnv: TestEnvInfo
let network: TestNetwork
let labeler: Labeler
let labelSrvc: LabelService
let ctx: AppContext
@ -27,18 +26,18 @@ describe('labeler', () => {
const profileUri = () => AtUri.make(alice, 'app.bsky.actor.profile', 'self')
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'labeler',
})
ctx = testEnv.bsky.ctx
const pdsCtx = testEnv.pds.ctx
ctx = network.bsky.ctx
const pdsCtx = network.pds.ctx
labelerDid = ctx.cfg.labelerDid
labeler = new TestLabeler(ctx)
labelSrvc = ctx.services.label(ctx.db)
const pdsAgent = new AtpAgent({ service: testEnv.pds.url })
const pdsAgent = new AtpAgent({ service: network.pds.url })
const sc = new SeedClient(pdsAgent)
await usersSeed(sc)
await processAll(testEnv)
await network.processAll()
alice = sc.dids.alice
const repoSvc = pdsCtx.services.repo(pdsCtx.db)
const storeBlob = async (bytes: Uint8Array) => {
@ -70,7 +69,7 @@ describe('labeler', () => {
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
it('labels text in posts', async () => {

@ -1,7 +1,7 @@
import { TestEnvInfo, runTestEnv } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import AtpAgent, { ComAtprotoAdminTakeModerationAction } from '@atproto/api'
import { AtUri } from '@atproto/uri'
import { adminAuth, appViewHeaders, forSnapshot, processAll } from './_util'
import { forSnapshot } from './_util'
import { ImageRef, RecordRef, SeedClient } from './seeds/client'
import basicSeed from './seeds/basic'
import {
@ -15,23 +15,23 @@ import {
} from '../src/lexicon/types/com/atproto/moderation/defs'
describe('moderation', () => {
let testEnv: TestEnvInfo
let network: TestNetwork
let agent: AtpAgent
let sc: SeedClient
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'moderation',
})
agent = new AtpAgent({ service: testEnv.bsky.url })
const pdsAgent = new AtpAgent({ service: testEnv.pds.url })
agent = network.bsky.getClient()
const pdsAgent = network.pds.getClient()
sc = new SeedClient(pdsAgent)
await basicSeed(sc)
await processAll(testEnv)
await network.processAll()
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
describe('reporting', () => {
@ -46,7 +46,7 @@ describe('moderation', () => {
},
},
{
headers: await appViewHeaders(sc.dids.alice, testEnv),
headers: await network.serviceHeaders(sc.dids.alice),
encoding: 'application/json',
},
)
@ -61,7 +61,7 @@ describe('moderation', () => {
},
},
{
headers: await appViewHeaders(sc.dids.carol, testEnv),
headers: await network.serviceHeaders(sc.dids.carol),
encoding: 'application/json',
},
)
@ -78,7 +78,7 @@ describe('moderation', () => {
},
},
{
headers: await appViewHeaders(sc.dids.alice, testEnv),
headers: await network.serviceHeaders(sc.dids.alice),
encoding: 'application/json',
},
)
@ -99,7 +99,7 @@ describe('moderation', () => {
},
},
{
headers: await appViewHeaders(sc.dids.alice, testEnv),
headers: await network.serviceHeaders(sc.dids.alice),
encoding: 'application/json',
},
)
@ -115,7 +115,7 @@ describe('moderation', () => {
},
},
{
headers: await appViewHeaders(sc.dids.carol, testEnv),
headers: await network.serviceHeaders(sc.dids.carol),
encoding: 'application/json',
},
)
@ -138,7 +138,7 @@ describe('moderation', () => {
},
},
{
headers: await appViewHeaders(sc.dids.alice, testEnv),
headers: await network.serviceHeaders(sc.dids.alice),
encoding: 'application/json',
},
)
@ -155,7 +155,7 @@ describe('moderation', () => {
},
},
{
headers: await appViewHeaders(sc.dids.carol, testEnv),
headers: await network.serviceHeaders(sc.dids.carol),
encoding: 'application/json',
},
)
@ -175,7 +175,7 @@ describe('moderation', () => {
},
},
{
headers: await appViewHeaders(sc.dids.alice, testEnv),
headers: await network.serviceHeaders(sc.dids.alice),
encoding: 'application/json',
},
)
@ -192,7 +192,7 @@ describe('moderation', () => {
},
},
{
headers: await appViewHeaders(sc.dids.carol, testEnv),
headers: await network.serviceHeaders(sc.dids.carol),
encoding: 'application/json',
},
)
@ -209,7 +209,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
const { data: actionResolvedReports } =
@ -221,7 +221,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
@ -236,7 +236,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})
@ -252,7 +252,7 @@ describe('moderation', () => {
},
},
{
headers: await appViewHeaders(sc.dids.alice, testEnv),
headers: await network.serviceHeaders(sc.dids.alice),
encoding: 'application/json',
},
)
@ -269,7 +269,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
@ -281,7 +281,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
@ -298,7 +298,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})
@ -317,7 +317,7 @@ describe('moderation', () => {
},
},
{
headers: await appViewHeaders(sc.dids.alice, testEnv),
headers: await network.serviceHeaders(sc.dids.alice),
encoding: 'application/json',
},
)
@ -335,7 +335,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
@ -347,7 +347,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
@ -364,7 +364,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})
@ -386,7 +386,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
expect(action1).toEqual(
@ -413,7 +413,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
expect(action2).toEqual(
@ -435,7 +435,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
await agent.api.com.atproto.admin.reverseModerationAction(
@ -446,7 +446,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})
@ -467,7 +467,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
const flagPromise = agent.api.com.atproto.admin.takeModerationAction(
@ -483,7 +483,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
await expect(flagPromise).rejects.toThrow(
@ -499,7 +499,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
const { data: flag } =
@ -516,7 +516,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
@ -529,7 +529,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})
@ -548,7 +548,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
const flagPromise = agent.api.com.atproto.admin.takeModerationAction(
@ -563,7 +563,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
await expect(flagPromise).rejects.toThrow(
@ -579,7 +579,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
const { data: flag } =
@ -595,7 +595,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
@ -608,7 +608,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})
@ -632,7 +632,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
const flagPromise = agent.api.com.atproto.admin.takeModerationAction(
@ -649,7 +649,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
await expect(flagPromise).rejects.toThrow(
@ -664,7 +664,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
const { data: flag } =
@ -682,7 +682,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
@ -695,13 +695,13 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})
it('negates an existing label and reverses.', async () => {
const { ctx } = testEnv.bsky
const { ctx } = network.bsky
const post = sc.posts[sc.dids.bob][0].ref
const labelingService = ctx.services.label(ctx.db)
await labelingService.formatAndCreate(
@ -731,7 +731,7 @@ describe('moderation', () => {
})
it('no-ops when negating an already-negated label and reverses.', async () => {
const { ctx } = testEnv.bsky
const { ctx } = network.bsky
const post = sc.posts[sc.dids.bob][0].ref
const labelingService = ctx.services.label(ctx.db)
const action = await actionWithLabels({
@ -774,7 +774,7 @@ describe('moderation', () => {
})
it('no-ops when creating an existing label and reverses.', async () => {
const { ctx } = testEnv.bsky
const { ctx } = network.bsky
const post = sc.posts[sc.dids.bob][0].ref
const labelingService = ctx.services.label(ctx.db)
await labelingService.formatAndCreate(
@ -814,7 +814,7 @@ describe('moderation', () => {
})
it('creates and negates labels on a repo and reverses.', async () => {
const { ctx } = testEnv.bsky
const { ctx } = network.bsky
const labelingService = ctx.services.label(ctx.db)
await labelingService.formatAndCreate(
ctx.cfg.labelerDid,
@ -849,7 +849,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
return result.data
@ -864,7 +864,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
}
@ -872,7 +872,7 @@ describe('moderation', () => {
async function getRecordLabels(uri: string) {
const result = await agent.api.com.atproto.admin.getRecord(
{ uri },
{ headers: { authorization: adminAuth() } },
{ headers: network.pds.adminAuthHeaders() },
)
const labels = result.data.labels ?? []
return labels.map((l) => l.val)
@ -881,7 +881,7 @@ describe('moderation', () => {
async function getRepoLabels(did: string) {
const result = await agent.api.com.atproto.admin.getRepo(
{ did },
{ headers: { authorization: adminAuth() } },
{ headers: network.pds.adminAuthHeaders() },
)
const labels = result.data.labels ?? []
return labels.map((l) => l.val)
@ -894,7 +894,7 @@ describe('moderation', () => {
let imageUri: string
let actionId: number
beforeAll(async () => {
const { ctx } = testEnv.bsky
const { ctx } = network.bsky
post = sc.posts[sc.dids.carol][0]
blob = post.images[1]
imageUri = ctx.imgUriBuilder
@ -903,7 +903,7 @@ describe('moderation', () => {
sc.dids.carol,
blob.image.ref.toString(),
)
.replace(ctx.cfg.publicUrl || '', testEnv.bsky.url)
.replace(ctx.cfg.publicUrl || '', network.bsky.url)
// Warm image server cache
await fetch(imageUri)
const cached = await fetch(imageUri)
@ -922,7 +922,7 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
actionId = takeAction.data.id
@ -930,7 +930,7 @@ describe('moderation', () => {
it('prevents resolution of blob', async () => {
const blobPath = `/blob/${sc.dids.carol}/${blob.image.ref.toString()}`
const resolveBlob = await fetch(`${testEnv.bsky.url}${blobPath}`)
const resolveBlob = await fetch(`${network.bsky.url}${blobPath}`)
expect(resolveBlob.status).toEqual(404)
expect(await resolveBlob.json()).toEqual({
error: 'NotFoundError',
@ -953,13 +953,13 @@ describe('moderation', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
// Can resolve blob
const blobPath = `/blob/${sc.dids.carol}/${blob.image.ref.toString()}`
const resolveBlob = await fetch(`${testEnv.bsky.url}${blobPath}`)
const resolveBlob = await fetch(`${network.bsky.url}${blobPath}`)
expect(resolveBlob.status).toEqual(200)
// Can fetch through image server

@ -1,27 +1,27 @@
import { AddressInfo } from 'net'
import express from 'express'
import axios, { AxiosError } from 'axios'
import { runTestEnv, TestEnvInfo } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { handler as errorHandler } from '../src/error'
import { Database } from '../src'
describe('server', () => {
let testEnv: TestEnvInfo
let network: TestNetwork
let db: Database
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_server',
})
db = testEnv.bsky.ctx.db
db = network.bsky.ctx.db
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
it('preserves 404s.', async () => {
const promise = axios.get(`${testEnv.bsky.url}/unknown`)
const promise = axios.get(`${network.bsky.url}/unknown`)
await expect(promise).rejects.toThrow('failed with status code 404')
})
@ -49,7 +49,7 @@ describe('server', () => {
})
it('healthcheck succeeds when database is available.', async () => {
const { data, status } = await axios.get(`${testEnv.bsky.url}/xrpc/_health`)
const { data, status } = await axios.get(`${network.bsky.url}/xrpc/_health`)
expect(status).toEqual(200)
expect(data).toEqual({ version: '0.0.0' })
})
@ -59,7 +59,7 @@ describe('server', () => {
let error: AxiosError
try {
await axios.post(
`${testEnv.bsky.url}/xrpc/com.atproto.repo.createRecord`,
`${network.bsky.url}/xrpc/com.atproto.repo.createRecord`,
{
data: 'x'.repeat(100 * 1024), // 100kb
},
@ -81,11 +81,11 @@ describe('server', () => {
})
it('healthcheck fails when database is unavailable.', async () => {
await testEnv.bsky.sub.destroy()
await network.bsky.sub.destroy()
await db.close()
let error: AxiosError
try {
await axios.get(`${testEnv.bsky.url}/xrpc/_health`)
await axios.get(`${network.bsky.url}/xrpc/_health`)
throw new Error('Healthcheck should have failed')
} catch (err) {
if (axios.isAxiosError(err)) {

@ -1,30 +1,30 @@
import AtpAgent from '@atproto/api'
import basicSeed from '../seeds/basic'
import { SeedClient } from '../seeds/client'
import { runTestEnv, TestEnvInfo } from '@atproto/dev-env'
import { forSnapshot, processAll } from '../_util'
import { TestNetwork } from '@atproto/dev-env'
import { forSnapshot } from '../_util'
import { AppContext, Database } from '../../src'
import { DatabaseSchemaType } from '../../src/db/database-schema'
import { ids } from '../../src/lexicon/lexicons'
describe('sync', () => {
let testEnv: TestEnvInfo
let network: TestNetwork
let ctx: AppContext
let pdsAgent: AtpAgent
let sc: SeedClient
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_subscription_repo',
})
ctx = testEnv.bsky.ctx
pdsAgent = new AtpAgent({ service: testEnv.pds.url })
ctx = network.bsky.ctx
pdsAgent = network.pds.getClient()
sc = new SeedClient(pdsAgent)
await basicSeed(sc)
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
it('indexes permit history being replayed.', async () => {
@ -41,7 +41,7 @@ describe('sync', () => {
await updateProfile(pdsAgent, alice, { displayName: 'ali!' })
await updateProfile(pdsAgent, bob, { displayName: 'robert!' })
await processAll(testEnv)
await network.processAll()
// Table comparator
const getTableDump = async () => {
@ -60,10 +60,10 @@ describe('sync', () => {
const originalTableDump = await getTableDump()
// Reprocess repos via sync subscription, on top of existing indices
await testEnv.bsky.sub?.destroy()
await testEnv.bsky.sub?.resetState()
testEnv.bsky.sub?.resume()
await processAll(testEnv)
await network.bsky.sub?.destroy()
await network.bsky.sub?.resetState()
network.bsky.sub?.resume()
await network.processAll()
// Permissive of indexedAt times changing
expect(forSnapshot(await getTableDump())).toEqual(

@ -1,39 +1,31 @@
import AtpAgent from '@atproto/api'
import { wait } from '@atproto/common'
import { CloseFn, runTestEnv } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/defs'
import {
adminAuth,
forSnapshot,
paginateAll,
processAll,
stripViewer,
} from '../_util'
import { forSnapshot, paginateAll, stripViewer } from '../_util'
import { SeedClient } from '../seeds/client'
import usersBulkSeed from '../seeds/users-bulk'
import { appViewHeaders } from '../_util'
describe('pds actor search views', () => {
let network: TestNetwork
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
let headers: { [s: string]: string }
beforeAll(async () => {
const testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_views_actor_search',
})
close = testEnv.close
agent = new AtpAgent({ service: testEnv.bsky.url })
const pdsAgent = new AtpAgent({ service: testEnv.pds.url })
agent = network.bsky.getClient()
const pdsAgent = network.pds.getClient()
sc = new SeedClient(pdsAgent)
await wait(50) // allow pending sub to be established
await testEnv.bsky.sub.destroy()
await network.bsky.sub?.destroy()
await usersBulkSeed(sc)
// Skip did/handle resolution for expediency
const { db } = testEnv.bsky.ctx
const { db } = network.bsky.ctx
const now = new Date().toISOString()
await db.db
.insertInto('actor')
@ -48,13 +40,13 @@ describe('pds actor search views', () => {
.execute()
// Process remaining profiles
testEnv.bsky.sub.resume()
await processAll(testEnv, 50000)
headers = await appViewHeaders(Object.values(sc.dids)[0], testEnv)
network.bsky.sub?.resume()
await network.processAll(50000)
headers = await network.serviceHeaders(Object.values(sc.dids)[0])
})
afterAll(async () => {
await close()
await network.close()
})
it('typeahead gives relevant results', async () => {
@ -237,7 +229,7 @@ describe('pds actor search views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
const result = await agent.api.app.bsky.actor.searchActorsTypeahead(

@ -1,20 +1,13 @@
import AtpAgent from '@atproto/api'
import { TestEnvInfo, runTestEnv } from '@atproto/dev-env'
import {
appViewHeaders,
adminAuth,
forSnapshot,
paginateAll,
processAll,
stripViewerFromPost,
} from '../_util'
import { TestNetwork } from '@atproto/dev-env'
import { forSnapshot, paginateAll, stripViewerFromPost } from '../_util'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/defs'
describe('pds author feed views', () => {
let network: TestNetwork
let agent: AtpAgent
let testEnv: TestEnvInfo
let sc: SeedClient
// account dids, for convenience
@ -24,15 +17,15 @@ describe('pds author feed views', () => {
let dan: string
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_views_author_feed',
})
agent = new AtpAgent({ service: testEnv.bsky.url })
const pdsAgent = new AtpAgent({ service: testEnv.pds.url })
agent = network.bsky.getClient()
const pdsAgent = network.pds.getClient()
sc = new SeedClient(pdsAgent)
await basicSeed(sc)
await processAll(testEnv)
await testEnv.bsky.ctx.labeler.processAll()
await network.processAll()
await network.bsky.ctx.labeler.processAll()
alice = sc.dids.alice
bob = sc.dids.bob
carol = sc.dids.carol
@ -40,7 +33,7 @@ describe('pds author feed views', () => {
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
// @TODO(bsky) blocked by actor takedown via labels.
@ -49,28 +42,28 @@ describe('pds author feed views', () => {
it('fetches full author feeds for self (sorted, minimal viewer state).', async () => {
const aliceForAlice = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: sc.accounts[alice].handle },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(aliceForAlice.data.feed)).toMatchSnapshot()
const bobForBob = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: sc.accounts[bob].handle },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
expect(forSnapshot(bobForBob.data.feed)).toMatchSnapshot()
const carolForCarol = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: sc.accounts[carol].handle },
{ headers: await appViewHeaders(carol, testEnv) },
{ headers: await network.serviceHeaders(carol) },
)
expect(forSnapshot(carolForCarol.data.feed)).toMatchSnapshot()
const danForDan = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: sc.accounts[dan].handle },
{ headers: await appViewHeaders(dan, testEnv) },
{ headers: await network.serviceHeaders(dan) },
)
expect(forSnapshot(danForDan.data.feed)).toMatchSnapshot()
@ -79,7 +72,7 @@ describe('pds author feed views', () => {
it("reflects fetching user's state in the feed.", async () => {
const aliceForCarol = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: sc.accounts[alice].handle },
{ headers: await appViewHeaders(carol, testEnv) },
{ headers: await network.serviceHeaders(carol) },
)
aliceForCarol.data.feed.forEach((postView) => {
@ -100,7 +93,7 @@ describe('pds author feed views', () => {
cursor,
limit: 2,
},
{ headers: await appViewHeaders(dan, testEnv) },
{ headers: await network.serviceHeaders(dan) },
)
return res.data
}
@ -112,7 +105,7 @@ describe('pds author feed views', () => {
const full = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: sc.accounts[alice].handle },
{ headers: await appViewHeaders(dan, testEnv) },
{ headers: await network.serviceHeaders(dan) },
)
expect(full.data.feed.length).toEqual(4)
@ -122,7 +115,7 @@ describe('pds author feed views', () => {
it('fetches results unauthed.', async () => {
const { data: authed } = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: sc.accounts[alice].handle },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
const { data: unauthed } = await agent.api.app.bsky.feed.getAuthorFeed({
actor: sc.accounts[alice].handle,
@ -148,7 +141,7 @@ describe('pds author feed views', () => {
it('blocked by actor takedown.', async () => {
const { data: preBlock } = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: alice },
{ headers: await appViewHeaders(carol, testEnv) },
{ headers: await network.serviceHeaders(carol) },
)
expect(preBlock.feed.length).toBeGreaterThan(0)
@ -166,13 +159,13 @@ describe('pds author feed views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
const { data: postBlock } = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: alice },
{ headers: await appViewHeaders(carol, testEnv) },
{ headers: await network.serviceHeaders(carol) },
)
expect(postBlock.feed.length).toEqual(0)
@ -186,7 +179,7 @@ describe('pds author feed views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})
@ -194,7 +187,7 @@ describe('pds author feed views', () => {
it('blocked by record takedown.', async () => {
const { data: preBlock } = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: alice },
{ headers: await appViewHeaders(carol, testEnv) },
{ headers: await network.serviceHeaders(carol) },
)
expect(preBlock.feed.length).toBeGreaterThan(0)
@ -215,13 +208,13 @@ describe('pds author feed views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
const { data: postBlock } = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: alice },
{ headers: await appViewHeaders(carol, testEnv) },
{ headers: await network.serviceHeaders(carol) },
)
expect(postBlock.feed.length).toEqual(preBlock.feed.length - 1)
@ -236,7 +229,7 @@ describe('pds author feed views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})

@ -1,39 +1,32 @@
import AtpAgent from '@atproto/api'
import { TestEnvInfo, runTestEnv } from '@atproto/dev-env'
import {
adminAuth,
appViewHeaders,
forSnapshot,
paginateAll,
processAll,
stripViewer,
} from '../_util'
import { TestNetwork } from '@atproto/dev-env'
import { forSnapshot, paginateAll, stripViewer } from '../_util'
import { SeedClient } from '../seeds/client'
import followsSeed from '../seeds/follows'
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/defs'
describe('pds follow views', () => {
let agent: AtpAgent
let testEnv: TestEnvInfo
let network: TestNetwork
let sc: SeedClient
// account dids, for convenience
let alice: string
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_views_follows',
})
agent = new AtpAgent({ service: testEnv.bsky.url })
const pdsAgent = new AtpAgent({ service: testEnv.pds.url })
agent = network.bsky.getClient()
const pdsAgent = network.pds.getClient()
sc = new SeedClient(pdsAgent)
await followsSeed(sc)
await processAll(testEnv)
await network.processAll()
alice = sc.dids.alice
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
// TODO(bsky) blocks followers by actor takedown via labels
@ -42,35 +35,35 @@ describe('pds follow views', () => {
it('fetches followers', async () => {
const aliceFollowers = await agent.api.app.bsky.graph.getFollowers(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(aliceFollowers.data)).toMatchSnapshot()
const bobFollowers = await agent.api.app.bsky.graph.getFollowers(
{ actor: sc.dids.bob },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(bobFollowers.data)).toMatchSnapshot()
const carolFollowers = await agent.api.app.bsky.graph.getFollowers(
{ actor: sc.dids.carol },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(carolFollowers.data)).toMatchSnapshot()
const danFollowers = await agent.api.app.bsky.graph.getFollowers(
{ actor: sc.dids.dan },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(danFollowers.data)).toMatchSnapshot()
const eveFollowers = await agent.api.app.bsky.graph.getFollowers(
{ actor: sc.dids.eve },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(eveFollowers.data)).toMatchSnapshot()
@ -79,11 +72,11 @@ describe('pds follow views', () => {
it('fetches followers by handle', async () => {
const byDid = await agent.api.app.bsky.graph.getFollowers(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
const byHandle = await agent.api.app.bsky.graph.getFollowers(
{ actor: sc.accounts[alice].handle },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(byHandle.data).toEqual(byDid.data)
})
@ -97,7 +90,7 @@ describe('pds follow views', () => {
cursor,
limit: 2,
},
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
return res.data
}
@ -109,7 +102,7 @@ describe('pds follow views', () => {
const full = await agent.api.app.bsky.graph.getFollowers(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(full.data.followers.length).toEqual(4)
@ -119,7 +112,7 @@ describe('pds follow views', () => {
it('fetches followers unauthed', async () => {
const { data: authed } = await agent.api.app.bsky.graph.getFollowers(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
const { data: unauthed } = await agent.api.app.bsky.graph.getFollowers({
actor: sc.dids.alice,
@ -142,13 +135,13 @@ describe('pds follow views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
const aliceFollowers = await agent.api.app.bsky.graph.getFollowers(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(aliceFollowers.data.followers.map((f) => f.did)).not.toContain(
@ -163,7 +156,7 @@ describe('pds follow views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})
@ -171,35 +164,35 @@ describe('pds follow views', () => {
it('fetches follows', async () => {
const aliceFollowers = await agent.api.app.bsky.graph.getFollows(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(aliceFollowers.data)).toMatchSnapshot()
const bobFollowers = await agent.api.app.bsky.graph.getFollows(
{ actor: sc.dids.bob },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(bobFollowers.data)).toMatchSnapshot()
const carolFollowers = await agent.api.app.bsky.graph.getFollows(
{ actor: sc.dids.carol },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(carolFollowers.data)).toMatchSnapshot()
const danFollowers = await agent.api.app.bsky.graph.getFollows(
{ actor: sc.dids.dan },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(danFollowers.data)).toMatchSnapshot()
const eveFollowers = await agent.api.app.bsky.graph.getFollows(
{ actor: sc.dids.eve },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(eveFollowers.data)).toMatchSnapshot()
@ -208,11 +201,11 @@ describe('pds follow views', () => {
it('fetches follows by handle', async () => {
const byDid = await agent.api.app.bsky.graph.getFollows(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
const byHandle = await agent.api.app.bsky.graph.getFollows(
{ actor: sc.accounts[alice].handle },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(byHandle.data).toEqual(byDid.data)
})
@ -226,7 +219,7 @@ describe('pds follow views', () => {
cursor,
limit: 2,
},
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
return res.data
}
@ -238,7 +231,7 @@ describe('pds follow views', () => {
const full = await agent.api.app.bsky.graph.getFollows(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(full.data.follows.length).toEqual(4)
@ -248,7 +241,7 @@ describe('pds follow views', () => {
it('fetches follows unauthed', async () => {
const { data: authed } = await agent.api.app.bsky.graph.getFollows(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
const { data: unauthed } = await agent.api.app.bsky.graph.getFollows({
actor: sc.dids.alice,
@ -271,13 +264,13 @@ describe('pds follow views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
const aliceFollows = await agent.api.app.bsky.graph.getFollows(
{ actor: sc.dids.alice },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(aliceFollows.data.follows.map((f) => f.did)).not.toContain(
@ -292,7 +285,7 @@ describe('pds follow views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})

@ -1,20 +1,13 @@
import AtpAgent from '@atproto/api'
import { TestEnvInfo, runTestEnv } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { SeedClient } from '../seeds/client'
import likesSeed from '../seeds/likes'
import {
appViewHeaders,
constantDate,
forSnapshot,
paginateAll,
processAll,
stripViewer,
} from '../_util'
import { constantDate, forSnapshot, paginateAll, stripViewer } from '../_util'
describe('pds like views', () => {
let network: TestNetwork
let agent: AtpAgent
let pdsAgent: AtpAgent
let testEnv: TestEnvInfo
let sc: SeedClient
// account dids, for convenience
@ -22,20 +15,20 @@ describe('pds like views', () => {
let bob: string
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_views_likes',
})
agent = new AtpAgent({ service: testEnv.bsky.url })
pdsAgent = new AtpAgent({ service: testEnv.pds.url })
agent = network.bsky.getClient()
pdsAgent = network.pds.getClient()
sc = new SeedClient(pdsAgent)
await likesSeed(sc)
await processAll(testEnv)
await network.processAll()
alice = sc.dids.alice
bob = sc.dids.bob
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
const getCursors = (items: { createdAt?: string }[]) =>
@ -49,7 +42,7 @@ describe('pds like views', () => {
it('fetches post likes', async () => {
const alicePost = await agent.api.app.bsky.feed.getLikes(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(alicePost.data)).toMatchSnapshot()
@ -61,7 +54,7 @@ describe('pds like views', () => {
it('fetches reply likes', async () => {
const bobReply = await agent.api.app.bsky.feed.getLikes(
{ uri: sc.replies[bob][0].ref.uriStr },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(bobReply.data)).toMatchSnapshot()
@ -79,7 +72,7 @@ describe('pds like views', () => {
cursor,
limit: 2,
},
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
return res.data
}
@ -91,7 +84,7 @@ describe('pds like views', () => {
const full = await agent.api.app.bsky.feed.getLikes(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(full.data.likes.length).toEqual(4)
@ -101,7 +94,7 @@ describe('pds like views', () => {
it('fetches post likes unauthed', async () => {
const { data: authed } = await agent.api.app.bsky.feed.getLikes(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
const { data: unauthed } = await agent.api.app.bsky.feed.getLikes({
uri: sc.posts[alice][1].ref.uriStr,

@ -1,19 +1,13 @@
import AtpAgent from '@atproto/api'
import { TestEnvInfo, runTestEnv } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/defs'
import {
adminAuth,
appViewHeaders,
forSnapshot,
paginateAll,
processAll,
} from '../_util'
import { forSnapshot, paginateAll } from '../_util'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
import { Notification } from '../../src/lexicon/types/app/bsky/notification/listNotifications'
describe('notification views', () => {
let testEnv: TestEnvInfo
let network: TestNetwork
let agent: AtpAgent
let sc: SeedClient
@ -21,20 +15,20 @@ describe('notification views', () => {
let alice: string
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_views_notifications',
})
agent = new AtpAgent({ service: testEnv.bsky.url })
const pdsAgent = new AtpAgent({ service: testEnv.pds.url })
agent = network.bsky.getClient()
const pdsAgent = network.pds.getClient()
sc = new SeedClient(pdsAgent)
await basicSeed(sc)
await processAll(testEnv)
await testEnv.bsky.ctx.labeler.processAll()
await network.processAll()
await network.bsky.ctx.labeler.processAll()
alice = sc.dids.alice
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
const sort = (notifs: Notification[]) => {
@ -59,14 +53,14 @@ describe('notification views', () => {
const notifCountAlice =
await agent.api.app.bsky.notification.getUnreadCount(
{},
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(notifCountAlice.data.count).toBe(11)
const notifCountBob = await agent.api.app.bsky.notification.getUnreadCount(
{},
{ headers: await appViewHeaders(sc.dids.bob, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.bob) },
)
expect(notifCountBob.data.count).toBe(4)
@ -81,19 +75,19 @@ describe('notification views', () => {
sc.replies[alice][0].ref,
'indeed',
)
await processAll(testEnv)
await network.processAll()
const notifCountAlice =
await agent.api.app.bsky.notification.getUnreadCount(
{},
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(notifCountAlice.data.count).toBe(12)
const notifCountBob = await agent.api.app.bsky.notification.getUnreadCount(
{},
{ headers: await appViewHeaders(sc.dids.bob, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.bob) },
)
expect(notifCountBob.data.count).toBe(5)
@ -104,11 +98,11 @@ describe('notification views', () => {
const first = await sc.reply(sc.dids.bob, root.ref, root.ref, 'first')
await sc.deletePost(sc.dids.alice, root.ref.uri)
const second = await sc.reply(sc.dids.carol, root.ref, first.ref, 'second')
await processAll(testEnv)
await network.processAll()
const notifsAlice = await agent.api.app.bsky.notification.listNotifications(
{},
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
const hasNotif = notifsAlice.data.notifications.some(
(notif) => notif.uri === second.ref.uriStr,
@ -118,14 +112,14 @@ describe('notification views', () => {
// cleanup
await sc.deletePost(sc.dids.bob, first.ref.uri)
await sc.deletePost(sc.dids.carol, second.ref.uri)
await processAll(testEnv)
await network.processAll()
})
it('generates notifications for quotes', async () => {
// Dan was quoted by alice
const notifsDan = await agent.api.app.bsky.notification.listNotifications(
{},
{ headers: await appViewHeaders(sc.dids.dan, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.dan) },
)
expect(forSnapshot(sort(notifsDan.data.notifications))).toMatchSnapshot()
})
@ -133,7 +127,7 @@ describe('notification views', () => {
it('fetches notifications without a last-seen', async () => {
const notifRes = await agent.api.app.bsky.notification.listNotifications(
{},
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
const notifs = notifRes.data.notifications
@ -151,7 +145,7 @@ describe('notification views', () => {
const paginator = async (cursor?: string) => {
const res = await agent.api.app.bsky.notification.listNotifications(
{ cursor, limit: 6 },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
return res.data
}
@ -163,7 +157,7 @@ describe('notification views', () => {
const full = await agent.api.app.bsky.notification.listNotifications(
{},
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(full.data.notifications.length).toEqual(12)
@ -173,12 +167,12 @@ describe('notification views', () => {
it('fetches notification count with a last-seen', async () => {
const full = await agent.api.app.bsky.notification.listNotifications(
{},
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
const seenAt = full.data.notifications[3].indexedAt
const notifCount = await agent.api.app.bsky.notification.getUnreadCount(
{ seenAt },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(notifCount.data.count).toBe(
@ -190,12 +184,12 @@ describe('notification views', () => {
it('fetches notifications with a last-seen', async () => {
const full = await agent.api.app.bsky.notification.listNotifications(
{},
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
const seenAt = full.data.notifications[3].indexedAt
const notifRes = await agent.api.app.bsky.notification.listNotifications(
{ seenAt },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
const notifs = notifRes.data.notifications
@ -223,7 +217,7 @@ describe('notification views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
),
),
@ -231,11 +225,11 @@ describe('notification views', () => {
const notifRes = await agent.api.app.bsky.notification.listNotifications(
{},
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
const notifCount = await agent.api.app.bsky.notification.getUnreadCount(
{},
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
const notifs = sort(notifRes.data.notifications)
@ -254,7 +248,7 @@ describe('notification views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
),
),

@ -1,11 +1,10 @@
import AtpAgent from '@atproto/api'
import { runTestEnv, TestEnvInfo } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
import { appViewHeaders, processAll } from '../_util'
describe('popular views', () => {
let testEnv: TestEnvInfo
let network: TestNetwork
let agent: AtpAgent
let sc: SeedClient
@ -23,11 +22,11 @@ describe('popular views', () => {
}
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_views_popular',
})
agent = new AtpAgent({ service: testEnv.bsky.url })
const pdsAgent = new AtpAgent({ service: testEnv.pds.url })
agent = network.bsky.getClient()
const pdsAgent = network.pds.getClient()
sc = new SeedClient(pdsAgent)
await basicSeed(sc)
await sc.createAccount('eve', {
@ -42,7 +41,7 @@ describe('popular views', () => {
handle: 'frank.test',
password: 'frank-pass',
})
await processAll(testEnv)
await network.processAll()
alice = sc.dids.alice
bob = sc.dids.bob
carol = sc.dids.carol
@ -52,7 +51,7 @@ describe('popular views', () => {
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
it('returns well liked posts', async () => {
@ -79,11 +78,11 @@ describe('popular views', () => {
await sc.like(dan, three.ref)
await sc.like(eve, three.ref)
await sc.like(frank, three.ref)
await processAll(testEnv)
await network.processAll()
const res = await agent.api.app.bsky.unspecced.getPopular(
{},
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
const feedUris = res.data.feed.map((i) => i.post.uri).sort()
const expected = [one.ref.uriStr, two.ref.uriStr, three.ref.uriStr].sort()

@ -1,32 +1,27 @@
import AtpAgent from '@atproto/api'
import { TestEnvInfo, runTestEnv } from '@atproto/dev-env'
import {
appViewHeaders,
forSnapshot,
processAll,
stripViewerFromPost,
} from '../_util'
import { TestNetwork } from '@atproto/dev-env'
import { forSnapshot, stripViewerFromPost } from '../_util'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
describe('pds posts views', () => {
let testEnv: TestEnvInfo
let network: TestNetwork
let agent: AtpAgent
let sc: SeedClient
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_views_posts',
})
agent = new AtpAgent({ service: testEnv.bsky.url })
const pdsAgent = new AtpAgent({ service: testEnv.pds.url })
agent = network.bsky.getClient()
const pdsAgent = network.pds.getClient()
sc = new SeedClient(pdsAgent)
await basicSeed(sc)
await processAll(testEnv)
await network.processAll()
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
it('fetches posts', async () => {
@ -40,7 +35,7 @@ describe('pds posts views', () => {
]
const posts = await agent.api.app.bsky.feed.getPosts(
{ uris },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
expect(posts.data.posts.length).toBe(uris.length)
@ -59,7 +54,7 @@ describe('pds posts views', () => {
const authed = await agent.api.app.bsky.feed.getPosts(
{ uris },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
const unauthed = await agent.api.app.bsky.feed.getPosts({
uris,

@ -1,20 +1,14 @@
import fs from 'fs/promises'
import AtpAgent from '@atproto/api'
import { runTestEnv, TestEnvInfo } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/defs'
import {
adminAuth,
appViewHeaders,
forSnapshot,
processAll,
stripViewer,
} from '../_util'
import { forSnapshot, stripViewer } from '../_util'
import { ids } from '../../src/lexicon/lexicons'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
describe('pds profile views', () => {
let testEnv: TestEnvInfo
let network: TestNetwork
let agent: AtpAgent
let pdsAgent: AtpAgent
let sc: SeedClient
@ -25,21 +19,21 @@ describe('pds profile views', () => {
let dan: string
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_views_profile',
})
agent = new AtpAgent({ service: testEnv.bsky.url })
pdsAgent = new AtpAgent({ service: testEnv.pds.url })
agent = network.bsky.getClient()
pdsAgent = network.pds.getClient()
sc = new SeedClient(pdsAgent)
await basicSeed(sc)
await processAll(testEnv)
await network.processAll()
alice = sc.dids.alice
bob = sc.dids.bob
dan = sc.dids.dan
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
// @TODO(bsky) blocked by actor takedown via labels.
@ -47,7 +41,7 @@ describe('pds profile views', () => {
it('fetches own profile', async () => {
const aliceForAlice = await agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(aliceForAlice.data)).toMatchSnapshot()
@ -56,7 +50,7 @@ describe('pds profile views', () => {
it("fetches other's profile, with a follow", async () => {
const aliceForBob = await agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
expect(forSnapshot(aliceForBob.data)).toMatchSnapshot()
@ -65,7 +59,7 @@ describe('pds profile views', () => {
it("fetches other's profile, without a follow", async () => {
const danForBob = await agent.api.app.bsky.actor.getProfile(
{ actor: dan },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
expect(forSnapshot(danForBob.data)).toMatchSnapshot()
@ -85,7 +79,7 @@ describe('pds profile views', () => {
'missing.test',
],
},
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
expect(profiles.map((p) => p.handle)).toEqual([
@ -126,11 +120,11 @@ describe('pds profile views', () => {
avatar: avatarRes.data.blob,
banner: bannerRes.data.blob,
})
await processAll(testEnv)
await network.processAll()
const aliceForAlice = await agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(aliceForAlice.data)).toMatchSnapshot()
@ -140,13 +134,13 @@ describe('pds profile views', () => {
const byDid = await agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{
headers: await appViewHeaders(bob, testEnv),
headers: await network.serviceHeaders(bob),
},
)
const byHandle = await agent.api.app.bsky.actor.getProfile(
{ actor: sc.accounts[alice].handle },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
expect(byHandle.data).toEqual(byDid.data)
@ -155,7 +149,7 @@ describe('pds profile views', () => {
it('fetches profile unauthed', async () => {
const { data: authed } = await agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
const { data: unauthed } = await agent.api.app.bsky.actor.getProfile({
actor: alice,
@ -168,7 +162,7 @@ describe('pds profile views', () => {
{
actors: [alice, 'bob.test', 'missing.test'],
},
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
const { data: unauthed } = await agent.api.app.bsky.actor.getProfiles({
actors: [alice, 'bob.test', 'missing.test'],
@ -191,12 +185,12 @@ describe('pds profile views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
const promise = agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
await expect(promise).rejects.toThrow('Account has been taken down')
@ -210,7 +204,7 @@ describe('pds profile views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})

@ -1,18 +1,12 @@
import AtpAgent from '@atproto/api'
import { TestEnvInfo, runTestEnv } from '@atproto/dev-env'
import {
appViewHeaders,
forSnapshot,
paginateAll,
processAll,
stripViewer,
} from '../_util'
import { TestNetwork } from '@atproto/dev-env'
import { forSnapshot, paginateAll, stripViewer } from '../_util'
import { SeedClient } from '../seeds/client'
import repostsSeed from '../seeds/reposts'
describe('pds repost views', () => {
let network: TestNetwork
let agent: AtpAgent
let testEnv: TestEnvInfo
let sc: SeedClient
// account dids, for convenience
@ -20,26 +14,26 @@ describe('pds repost views', () => {
let bob: string
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_views_reposts',
})
agent = new AtpAgent({ service: testEnv.bsky.url })
const pdsAgent = new AtpAgent({ service: testEnv.pds.url })
agent = network.bsky.getClient()
const pdsAgent = network.pds.getClient()
sc = new SeedClient(pdsAgent)
await repostsSeed(sc)
await processAll(testEnv)
await network.processAll()
alice = sc.dids.alice
bob = sc.dids.bob
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
it('fetches reposted-by for a post', async () => {
const view = await agent.api.app.bsky.feed.getRepostedBy(
{ uri: sc.posts[alice][2].ref.uriStr },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(view.data.uri).toEqual(sc.posts[sc.dids.alice][2].ref.uriStr)
expect(forSnapshot(view.data.repostedBy)).toMatchSnapshot()
@ -48,7 +42,7 @@ describe('pds repost views', () => {
it('fetches reposted-by for a reply', async () => {
const view = await agent.api.app.bsky.feed.getRepostedBy(
{ uri: sc.replies[bob][0].ref.uriStr },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(view.data.uri).toEqual(sc.replies[sc.dids.bob][0].ref.uriStr)
expect(forSnapshot(view.data.repostedBy)).toMatchSnapshot()
@ -63,7 +57,7 @@ describe('pds repost views', () => {
cursor,
limit: 2,
},
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
return res.data
}
@ -75,7 +69,7 @@ describe('pds repost views', () => {
const full = await agent.api.app.bsky.feed.getRepostedBy(
{ uri: sc.posts[alice][2].ref.uriStr },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(full.data.repostedBy.length).toEqual(4)
@ -85,7 +79,7 @@ describe('pds repost views', () => {
it('fetches reposted-by unauthed', async () => {
const { data: authed } = await agent.api.app.bsky.feed.getRepostedBy(
{ uri: sc.posts[alice][2].ref.uriStr },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
const { data: unauthed } = await agent.api.app.bsky.feed.getRepostedBy({
uri: sc.posts[alice][2].ref.uriStr,

@ -1,33 +1,33 @@
import AtpAgent from '@atproto/api'
import { TestEnvInfo, runTestEnv } from '@atproto/dev-env'
import { appViewHeaders, processAll, stripViewer } from '../_util'
import { TestNetwork } from '@atproto/dev-env'
import { stripViewer } from '../_util'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
describe('pds user search views', () => {
let network: TestNetwork
let agent: AtpAgent
let testEnv: TestEnvInfo
let sc: SeedClient
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_views_suggestions',
})
agent = new AtpAgent({ service: testEnv.bsky.url })
const pdsAgent = new AtpAgent({ service: testEnv.pds.url })
agent = network.bsky.getClient()
const pdsAgent = network.pds.getClient()
sc = new SeedClient(pdsAgent)
await basicSeed(sc)
await processAll(testEnv)
await network.processAll()
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
it('actor suggestion gives users', async () => {
const result = await agent.api.app.bsky.actor.getSuggestions(
{ limit: 3 },
{ headers: await appViewHeaders(sc.dids.carol, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.carol) },
)
const handles = result.data.actors.map((u) => u.handle)
@ -49,7 +49,7 @@ describe('pds user search views', () => {
it('does not suggest followed users', async () => {
const result = await agent.api.app.bsky.actor.getSuggestions(
{ limit: 3 },
{ headers: await appViewHeaders(sc.dids.alice, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.alice) },
)
// alice follows everyone
@ -59,11 +59,11 @@ describe('pds user search views', () => {
it('paginates', async () => {
const result1 = await agent.api.app.bsky.actor.getSuggestions(
{ limit: 1 },
{ headers: await appViewHeaders(sc.dids.carol, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.carol) },
)
const result2 = await agent.api.app.bsky.actor.getSuggestions(
{ limit: 1, cursor: result1.data.cursor },
{ headers: await appViewHeaders(sc.dids.carol, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.carol) },
)
expect(result1.data.actors.length).toBe(1)
@ -78,7 +78,7 @@ describe('pds user search views', () => {
it('fetches suggestions unauthed', async () => {
const { data: authed } = await agent.api.app.bsky.actor.getSuggestions(
{},
{ headers: await appViewHeaders(sc.dids.carol, testEnv) },
{ headers: await network.serviceHeaders(sc.dids.carol) },
)
const { data: unauthed } = await agent.api.app.bsky.actor.getSuggestions({})
const omitViewerFollows = ({ did }) => {

@ -1,20 +1,14 @@
import AtpAgent, { AppBskyFeedGetPostThread } from '@atproto/api'
import { runTestEnv, TestEnvInfo } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/defs'
import { Database } from '../../src'
import {
adminAuth,
appViewHeaders,
forSnapshot,
processAll,
stripViewerFromThread,
} from '../_util'
import { forSnapshot, stripViewerFromThread } from '../_util'
import { RecordRef, SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
import threadSeed, { walk, item, Item } from '../seeds/thread'
describe('pds thread views', () => {
let testEnv: TestEnvInfo
let network: TestNetwork
let agent: AtpAgent
let db: Database
let sc: SeedClient
@ -25,12 +19,12 @@ describe('pds thread views', () => {
let carol: string
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_views_thread',
})
db = testEnv.bsky.ctx.db
agent = new AtpAgent({ service: testEnv.bsky.url })
const pdsAgent = new AtpAgent({ service: testEnv.pds.url })
db = network.bsky.ctx.db
agent = network.bsky.getClient()
const pdsAgent = network.pds.getClient()
sc = new SeedClient(pdsAgent)
await basicSeed(sc)
alice = sc.dids.alice
@ -41,18 +35,18 @@ describe('pds thread views', () => {
beforeAll(async () => {
// Add a repost of a reply so that we can confirm myState in the thread
await sc.repost(bob, sc.replies[alice][0].ref)
await processAll(testEnv)
await testEnv.bsky.ctx.labeler.processAll()
await network.processAll()
await network.bsky.ctx.labeler.processAll()
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
it('fetches deep post thread', async () => {
const thread = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
expect(forSnapshot(thread.data.thread)).toMatchSnapshot()
@ -61,7 +55,7 @@ describe('pds thread views', () => {
it('fetches shallow post thread', async () => {
const thread = await agent.api.app.bsky.feed.getPostThread(
{ depth: 1, uri: sc.posts[alice][1].ref.uriStr },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
expect(forSnapshot(thread.data.thread)).toMatchSnapshot()
@ -70,7 +64,7 @@ describe('pds thread views', () => {
it('fetches ancestors', async () => {
const thread = await agent.api.app.bsky.feed.getPostThread(
{ depth: 1, uri: sc.replies[alice][0].ref.uriStr },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
expect(forSnapshot(thread.data.thread)).toMatchSnapshot()
@ -79,7 +73,7 @@ describe('pds thread views', () => {
it('fails for an unknown post', async () => {
const promise = agent.api.app.bsky.feed.getPostThread(
{ uri: 'at://did:example:fake/does.not.exist/self' },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
await expect(promise).rejects.toThrow(
@ -90,7 +84,7 @@ describe('pds thread views', () => {
it('fetches post thread unauthed', async () => {
const { data: authed } = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
const { data: unauthed } = await agent.api.app.bsky.feed.getPostThread({
uri: sc.posts[alice][1].ref.uriStr,
@ -125,26 +119,26 @@ describe('pds thread views', () => {
'Reply reply',
)
indexes.aliceReplyReply = sc.replies[alice].length - 1
await processAll(testEnv)
await network.processAll()
const thread1 = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.posts[alice][indexes.aliceRoot].ref.uriStr },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
expect(forSnapshot(thread1.data.thread)).toMatchSnapshot()
await sc.deletePost(bob, sc.replies[bob][indexes.bobReply].ref.uri)
await processAll(testEnv)
await network.processAll()
const thread2 = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.posts[alice][indexes.aliceRoot].ref.uriStr },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
expect(forSnapshot(thread2.data.thread)).toMatchSnapshot()
const thread3 = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.replies[alice][indexes.aliceReplyReply].ref.uriStr },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
expect(forSnapshot(thread3.data.thread)).toMatchSnapshot()
})
@ -157,7 +151,7 @@ describe('pds thread views', () => {
]
await threadSeed(sc, sc.dids.alice, threads)
await processAll(testEnv)
await network.processAll()
let closureSize = 0
const itemByUri: Record<string, Item> = {}
@ -222,14 +216,14 @@ describe('pds thread views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
// Same as shallow post thread test, minus alice
const promise = agent.api.app.bsky.feed.getPostThread(
{ depth: 1, uri: sc.posts[alice][1].ref.uriStr },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
await expect(promise).rejects.toThrow(
@ -245,7 +239,7 @@ describe('pds thread views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})
@ -264,14 +258,14 @@ describe('pds thread views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
// Same as deep post thread test, minus carol
const thread = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
expect(forSnapshot(thread.data.thread)).toMatchSnapshot()
@ -285,7 +279,7 @@ describe('pds thread views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})
@ -304,14 +298,14 @@ describe('pds thread views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
// Same as ancestor post thread test, minus bob
const thread = await agent.api.app.bsky.feed.getPostThread(
{ depth: 1, uri: sc.replies[alice][0].ref.uriStr },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
expect(forSnapshot(thread.data.thread)).toMatchSnapshot()
@ -325,7 +319,7 @@ describe('pds thread views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})
@ -346,13 +340,13 @@ describe('pds thread views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
const promise = agent.api.app.bsky.feed.getPostThread(
{ depth: 1, uri: postRef.uriStr },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
await expect(promise).rejects.toThrow(
@ -368,7 +362,7 @@ describe('pds thread views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})
@ -376,7 +370,7 @@ describe('pds thread views', () => {
it('blocks ancestors by record', async () => {
const threadPreTakedown = await agent.api.app.bsky.feed.getPostThread(
{ depth: 1, uri: sc.replies[alice][0].ref.uriStr },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
const parent = threadPreTakedown.data.thread.parent?.['post']
@ -395,14 +389,14 @@ describe('pds thread views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
// Same as ancestor post thread test, minus parent post
const thread = await agent.api.app.bsky.feed.getPostThread(
{ depth: 1, uri: sc.replies[alice][0].ref.uriStr },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
expect(forSnapshot(thread.data.thread)).toMatchSnapshot()
@ -416,7 +410,7 @@ describe('pds thread views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
)
})
@ -424,7 +418,7 @@ describe('pds thread views', () => {
it('blocks replies by record', async () => {
const threadPreTakedown = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
const post1 = threadPreTakedown.data.thread.replies?.[0].post
const post2 = threadPreTakedown.data.thread.replies?.[1].replies[0].post
@ -444,7 +438,7 @@ describe('pds thread views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
),
),
@ -453,7 +447,7 @@ describe('pds thread views', () => {
// Same as deep post thread test, minus some replies
const thread = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: await appViewHeaders(bob, testEnv) },
{ headers: await network.serviceHeaders(bob) },
)
expect(forSnapshot(thread.data.thread)).toMatchSnapshot()
@ -469,7 +463,7 @@ describe('pds thread views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
),
),

@ -1,22 +1,15 @@
import AtpAgent from '@atproto/api'
import { TestEnvInfo, runTestEnv } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/defs'
import {
adminAuth,
appViewHeaders,
forSnapshot,
getOriginator,
paginateAll,
processAll,
} from '../_util'
import { forSnapshot, getOriginator, paginateAll } from '../_util'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
import { FeedAlgorithm } from '../../src/api/app/bsky/util/feed'
import { FeedViewPost } from '../../src/lexicon/types/app/bsky/feed/defs'
describe('timeline views', () => {
let network: TestNetwork
let agent: AtpAgent
let testEnv: TestEnvInfo
let sc: SeedClient
// account dids, for convenience
@ -26,15 +19,15 @@ describe('timeline views', () => {
let dan: string
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'bsky_views_home_feed',
})
agent = new AtpAgent({ service: testEnv.bsky.url })
const pdsAgent = new AtpAgent({ service: testEnv.pds.url })
agent = network.bsky.getClient()
const pdsAgent = network.pds.getClient()
sc = new SeedClient(pdsAgent)
await basicSeed(sc)
await processAll(testEnv)
await testEnv.bsky.ctx.labeler.processAll()
await network.processAll()
await network.bsky.ctx.labeler.processAll()
alice = sc.dids.alice
bob = sc.dids.bob
carol = sc.dids.carol
@ -42,27 +35,27 @@ describe('timeline views', () => {
// Label posts as "kind" to check labels on embed views
const labelPostA = sc.posts[bob][0].ref
const labelPostB = sc.posts[carol][0].ref
await testEnv.bsky.ctx.services
.label(testEnv.bsky.ctx.db)
await network.bsky.ctx.services
.label(network.bsky.ctx.db)
.formatAndCreate(
testEnv.bsky.ctx.cfg.labelerDid,
network.bsky.ctx.cfg.labelerDid,
labelPostA.uriStr,
labelPostA.cidStr,
{ create: ['kind'] },
)
await testEnv.bsky.ctx.services
.label(testEnv.bsky.ctx.db)
await network.bsky.ctx.services
.label(network.bsky.ctx.db)
.formatAndCreate(
testEnv.bsky.ctx.cfg.labelerDid,
network.bsky.ctx.cfg.labelerDid,
labelPostB.uriStr,
labelPostB.cidStr,
{ create: ['kind'] },
)
await testEnv.bsky.ctx.labeler.processAll()
await network.bsky.ctx.labeler.processAll()
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
// @TODO(bsky) blocks posts, reposts, replies by actor takedown via labels
@ -80,7 +73,7 @@ describe('timeline views', () => {
const aliceTL = await agent.api.app.bsky.feed.getTimeline(
{ algorithm: FeedAlgorithm.ReverseChronological },
{
headers: await appViewHeaders(alice, testEnv),
headers: await network.serviceHeaders(alice),
},
)
@ -90,7 +83,7 @@ describe('timeline views', () => {
const bobTL = await agent.api.app.bsky.feed.getTimeline(
{ algorithm: FeedAlgorithm.ReverseChronological },
{
headers: await appViewHeaders(bob, testEnv),
headers: await network.serviceHeaders(bob),
},
)
@ -100,7 +93,7 @@ describe('timeline views', () => {
const carolTL = await agent.api.app.bsky.feed.getTimeline(
{ algorithm: FeedAlgorithm.ReverseChronological },
{
headers: await appViewHeaders(carol, testEnv),
headers: await network.serviceHeaders(carol),
},
)
@ -110,7 +103,7 @@ describe('timeline views', () => {
const danTL = await agent.api.app.bsky.feed.getTimeline(
{ algorithm: FeedAlgorithm.ReverseChronological },
{
headers: await appViewHeaders(dan, testEnv),
headers: await network.serviceHeaders(dan),
},
)
@ -122,13 +115,13 @@ describe('timeline views', () => {
const defaultTL = await agent.api.app.bsky.feed.getTimeline(
{},
{
headers: await appViewHeaders(alice, testEnv),
headers: await network.serviceHeaders(alice),
},
)
const reverseChronologicalTL = await agent.api.app.bsky.feed.getTimeline(
{ algorithm: FeedAlgorithm.ReverseChronological },
{
headers: await appViewHeaders(alice, testEnv),
headers: await network.serviceHeaders(alice),
},
)
expect(defaultTL.data.feed).toEqual(reverseChronologicalTL.data.feed)
@ -143,7 +136,7 @@ describe('timeline views', () => {
cursor,
limit: 4,
},
{ headers: await appViewHeaders(carol, testEnv) },
{ headers: await network.serviceHeaders(carol) },
)
return res.data
}
@ -157,7 +150,7 @@ describe('timeline views', () => {
{
algorithm: FeedAlgorithm.ReverseChronological,
},
{ headers: await appViewHeaders(carol, testEnv) },
{ headers: await network.serviceHeaders(carol) },
)
expect(full.data.feed.length).toEqual(7)
@ -179,7 +172,7 @@ describe('timeline views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
),
),
@ -187,7 +180,7 @@ describe('timeline views', () => {
const aliceTL = await agent.api.app.bsky.feed.getTimeline(
{ algorithm: FeedAlgorithm.ReverseChronological },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(aliceTL.data.feed)).toMatchSnapshot()
@ -203,7 +196,7 @@ describe('timeline views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
),
),
@ -228,7 +221,7 @@ describe('timeline views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
),
),
@ -236,7 +229,7 @@ describe('timeline views', () => {
const aliceTL = await agent.api.app.bsky.feed.getTimeline(
{ algorithm: FeedAlgorithm.ReverseChronological },
{ headers: await appViewHeaders(alice, testEnv) },
{ headers: await network.serviceHeaders(alice) },
)
expect(forSnapshot(aliceTL.data.feed)).toMatchSnapshot()
@ -252,7 +245,7 @@ describe('timeline views', () => {
},
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
headers: network.pds.adminAuthHeaders(),
},
),
),

@ -3,7 +3,7 @@ const { copy } = require('esbuild-plugin-copy')
require('esbuild')
.build({
logLevel: 'info',
entryPoints: ['src/index.ts', 'src/cli.ts'],
entryPoints: ['src/index.ts', 'src/bin.ts'],
bundle: true,
sourcemap: true,
outdir: 'dist',

@ -1,8 +1,8 @@
{
"name": "@atproto/dev-env",
"version": "0.1.0",
"main": "src/test-env.ts",
"bin": "dist/cli.js",
"main": "src/index.ts",
"bin": "dist/bin.js",
"license": "MIT",
"repository": {
"type": "git",
@ -12,7 +12,7 @@
"scripts": {
"build": "node ./build.js",
"postbuild": "tsc --build tsconfig.build.json",
"start": "node dist/cli.js",
"start": "node dist/bin.js",
"prettier": "prettier --check src/",
"prettier:fix": "prettier --write src/",
"lint": "eslint . --ext .ts,.tsx",
@ -27,13 +27,15 @@
"@atproto/did-resolver": "*",
"@atproto/pds": "*",
"@atproto/uri": "*",
"@atproto/xrpc-server": "*",
"@did-plc/lib": "^0.0.1",
"@did-plc/server": "^0.0.1",
"better-sqlite3": "^7.6.2",
"chalk": "^5.0.1",
"dotenv": "^16.0.1",
"get-port": "^6.1.2",
"sharp": "^0.31.2"
"sharp": "^0.31.2",
"uint8arrays": "3.0.0"
},
"devDependencies": {
"ts-node": "^10.8.1"

@ -0,0 +1,29 @@
import { generateMockSetup } from './mock'
import { TestNetworkNoAppView } from './network-no-appview'
const run = async () => {
console.log(`
protocol
[ created by Bluesky ]`)
const network = await TestNetworkNoAppView.create({
pds: { port: 2583 },
plc: { port: 2582 },
})
await generateMockSetup(network)
console.log(
`👤 DID Placeholder server started http://localhost:${network.plc.port}`,
)
console.log(
`🌞 Personal Data server started http://localhost:${network.pds.port}`,
)
}
run()

@ -0,0 +1,98 @@
import getPort from 'get-port'
import * as bsky from '@atproto/bsky'
import { DAY, HOUR } from '@atproto/common-web'
import { AtpAgent } from '@atproto/api'
import { Secp256k1Keypair } from '@atproto/crypto'
import { Client as PlcClient } from '@did-plc/lib'
import { BskyConfig } from './types'
export class TestBsky {
constructor(
public url: string,
public port: number,
public server: bsky.BskyAppView,
) {}
static async create(cfg: BskyConfig): Promise<TestBsky> {
const serviceKeypair = await Secp256k1Keypair.create()
const plcClient = new PlcClient(cfg.plcUrl)
const port = cfg.port || (await getPort())
const url = `http://localhost:${port}`
const serverDid = await plcClient.createDid({
signingKey: serviceKeypair.did(),
rotationKeys: [serviceKeypair.did()],
handle: 'bsky.test',
pds: `http://localhost:${port}`,
signer: serviceKeypair,
})
const config = new bsky.ServerConfig({
version: '0.0.0',
port,
didPlcUrl: cfg.plcUrl,
publicUrl: 'https://bsky.public.url',
serverDid,
imgUriSalt: '9dd04221f5755bce5f55f47464c27e1e',
imgUriKey:
'f23ecd142835025f42c3db2cf25dd813956c178392760256211f9d315f8ab4d8',
didCacheStaleTTL: HOUR,
didCacheMaxTTL: DAY,
...cfg,
// Each test suite gets its own lock id for the repo subscription
repoSubLockId: uniqueLockId(),
adminPassword: 'admin-pass',
labelerDid: 'did:example:labeler',
labelerKeywords: { label_me: 'test-label', label_me_2: 'test-label-2' },
})
const db = bsky.Database.postgres({
url: cfg.dbPostgresUrl,
schema: cfg.dbPostgresSchema,
})
// Separate migration db in case migration changes some connection state that we need in the tests, e.g. "alter database ... set ..."
const migrationDb = bsky.Database.postgres({
url: cfg.dbPostgresUrl,
schema: cfg.dbPostgresSchema,
})
if (cfg.migration) {
await migrationDb.migrateToOrThrow(cfg.migration)
} else {
await migrationDb.migrateToLatestOrThrow()
}
await migrationDb.close()
const server = bsky.BskyAppView.create({ db, config })
await server.start()
return new TestBsky(url, port, server)
}
get ctx(): bsky.AppContext {
return this.server.ctx
}
get sub() {
if (!this.server.sub) {
throw new Error('No subscription on dev-env server')
}
return this.server.sub
}
getClient() {
return new AtpAgent({ service: this.url })
}
async close() {
await this.server.destroy()
}
}
const usedLockIds = new Set()
const uniqueLockId = () => {
let lockId: number
do {
lockId = 1000 + Math.ceil(1000 * Math.random())
} while (usedLockIds.has(lockId))
usedLockIds.add(lockId)
return lockId
}

@ -1,143 +0,0 @@
import assert from 'assert'
import repl from 'repl'
import fs from 'fs'
import os from 'os'
import { join } from 'path'
import chalk from 'chalk'
import { DevEnv, DevEnvServer } from './index.js'
import * as env from './env.js'
import { ServerType } from './types.js'
import { genServerCfg } from './util'
import { generateMockSetup } from './mock'
const pkg = JSON.parse(
fs.readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'),
)
let devEnv: DevEnv | undefined
const users: Map<string, DevEnvServer> = new Map()
// commands
// =
const envApi = {
get env() {
return devEnv
},
get api() {
const client = devEnv
?.listOfType(ServerType.PersonalDataServer)[0]
.getClient()
if (!client) {
throw new Error('No PDS is active')
}
return client
},
status() {
assert(devEnv)
console.log(chalk.bold(' port server'))
for (const server of devEnv.servers.values()) {
console.log(`${server.description} ${chalk.gray(server.url)}`)
}
},
async startPds(port?: number) {
assert(devEnv)
await devEnv.add(await genServerCfg(ServerType.PersonalDataServer, port))
},
async startBsky(port?: number) {
assert(devEnv)
await devEnv.add(await genServerCfg(ServerType.BskyAppView, port))
},
async stop(port: number) {
assert(devEnv)
const inst = devEnv.servers.get(port)
if (!inst) {
console.error('No server found at port', port)
} else {
await devEnv.remove(inst)
}
},
async mkuser(handle: string, serverPort?: number) {
assert(devEnv)
assert(handle && typeof handle === 'string', 'Handle is required')
if (!handle.endsWith('.test')) {
handle += '.test'
}
if (users.has(handle)) {
throw new Error(`${handle} already exists`)
}
const handleNoTld = handle.slice(0, handle.length - '.test'.length)
const servers = devEnv.listOfType(ServerType.PersonalDataServer)
let pds: DevEnvServer
if (!serverPort) {
if (servers.length > 0) {
pds = servers[0]
} else {
throw new Error('Start a PDS first')
}
} else {
const inst = servers.find((s) => s.port === serverPort)
if (!inst) throw new Error(`No PDS running on port ${serverPort}`)
pds = inst
}
console.log(`Creating ${handle} on ${pds.description}`)
// create the PDS account
const client = pds.getClient()
const pdsRes = await client.api.com.atproto.server.createAccount({
email: handleNoTld + '@test.com',
handle,
password: handleNoTld + '-pass',
})
users.set(handle, pds)
},
user(handle: string) {
const pds = users.get(handle)
if (!pds) throw new Error('User not found')
return pds.getClient()
},
}
// start
// =
console.log(`
protocol
[ v${pkg.version} | created by Bluesky ]
Initializing...`)
async function start() {
devEnv = await DevEnv.create(env.load())
await generateMockSetup(devEnv)
console.log('Test environment generated.')
console.log('Type .help if you get lost')
// create repl
const inst = repl.start() //'atp $ ')
Object.assign(inst.context, envApi)
inst.setupHistory(join(os.homedir(), '.atp-dev-env-history'), () => {})
inst.on('exit', async () => {
console.log(`Shutting down...`)
await devEnv?.shutdown()
process.exit(0)
})
return inst
}
start()

@ -1,22 +0,0 @@
import dotenv from 'dotenv'
import { StartParams, ServerType, ServerConfig } from './types.js'
const getPorts = (type: ServerType, name: string): ServerConfig[] => {
const portsStr = process.env[name]
if (!portsStr) return []
return portsStr
.split(',')
.map((str) => parseInt(str))
.filter(Boolean)
.map((port) => ({ type, port }))
}
export function load(): StartParams {
dotenv.config()
return {
servers: [
...getPorts(ServerType.PersonalDataServer, 'PERSONAL_DATA_SERVERS'),
],
}
}

@ -1,273 +1,7 @@
import http from 'http'
import chalk from 'chalk'
import crytpo from 'crypto'
import PDSServer, {
Database as PDSDatabase,
MemoryBlobStore,
ServerConfig as PDSServerConfig,
AppContext as PDSContext,
} from '@atproto/pds'
import * as bsky from '@atproto/bsky'
import * as plc from '@did-plc/lib'
import { PlcServer, Database as PlcDatabase } from '@did-plc/server'
import * as crypto from '@atproto/crypto'
import AtpAgent from '@atproto/api'
import { ServerType, ServerConfig, StartParams, PORTS } from './types.js'
import { DAY, HOUR } from '@atproto/common'
import { mockNetworkUtilities } from './test-env'
export * from './test-env'
interface Startable {
start(): Promise<http.Server>
}
interface Destroyable {
destroy(): Promise<void>
}
export class DevEnvServer {
inst?: Destroyable
constructor(
private env: DevEnv,
public type: ServerType,
public port: number,
) {}
get name() {
return {
[ServerType.PersonalDataServer]: '🌞 Personal Data server',
[ServerType.DidPlaceholder]: '👤 DID Placeholder server',
[ServerType.BskyAppView]: '🌀 Bsky App View server',
}[this.type]
}
get description() {
return `[${chalk.bold(this.port)}] ${this.name}`
}
get url() {
return `http://localhost:${this.port}`
}
get ctx(): PDSContext | undefined {
if (this.inst instanceof PDSServer) {
return this.inst.ctx
}
}
async start() {
if (this.inst) {
throw new Error('Already started')
}
const startServer = async (server: Startable): Promise<void> => {
try {
await server.start()
console.log(`${this.description} started ${chalk.gray(this.url)}`)
} catch (err) {
console.log(`${this.description} failed to start:`, err)
}
}
switch (this.type) {
case ServerType.PersonalDataServer: {
if (!this.env.plcUrl) {
throw new Error('Must be running a PLC server to start a PDS')
}
const db = await PDSDatabase.memory()
await db.migrateToLatestOrThrow()
const keypair = await crypto.EcdsaKeypair.create()
const blobstore = new MemoryBlobStore()
const plcClient = new plc.Client(this.env.plcUrl)
const serverDid = await plcClient.createDid({
signingKey: keypair.did(),
rotationKeys: [keypair.did()],
handle: 'localhost',
pds: `http://localhost:${this.port}`,
signer: keypair,
})
const pds = PDSServer.create({
db,
blobstore,
repoSigningKey: keypair,
plcRotationKey: keypair,
config: new PDSServerConfig({
debugMode: true,
version: '0.0.0',
scheme: 'http',
hostname: 'localhost',
port: this.port,
didPlcUrl: this.env.plcUrl,
serverDid,
recoveryKey: keypair.did(),
jwtSecret: crytpo.randomBytes(8).toString('base64'),
availableUserDomains: ['.test'],
appUrlPasswordReset: 'app://password-reset',
// @TODO setup ethereal.email creds and set emailSmtpUrl here
emailNoReplyAddress: 'noreply@blueskyweb.xyz',
adminPassword: 'password',
inviteRequired: false,
userInviteInterval: null,
imgUriSalt: '9dd04221f5755bce5f55f47464c27e1e',
imgUriKey:
'f23ecd142835025f42c3db2cf25dd813956c178392760256211f9d315f8ab4d8',
privacyPolicyUrl: 'https://example.com/privacy',
termsOfServiceUrl: 'https://example.com/tos',
labelerDid: 'did:example:labeler',
labelerKeywords: {},
maxSubscriptionBuffer: 200,
repoBackfillLimitMs: HOUR,
appViewRepoProvider: process.env.APP_VIEW_REPO_PROVIDER,
}),
})
await startServer(pds)
this.inst = pds
break
}
case ServerType.DidPlaceholder: {
const db = PlcDatabase.mock()
const plcServer = PlcServer.create({ db, port: this.port })
await startServer(plcServer)
this.inst = plcServer
break
}
case ServerType.BskyAppView: {
if (!this.env.pdsUrl || !this.env.plcUrl) {
throw new Error(
'Must be running a PDS and PLC servers to start AppView',
)
}
const keypair = await crypto.Secp256k1Keypair.create()
const plcClient = new plc.Client(this.env.plcUrl)
const serverDid = await plcClient.createDid({
signingKey: keypair.did(),
rotationKeys: [keypair.did()],
handle: 'localhost',
pds: `http://localhost:${this.port}`,
signer: keypair,
})
const config = new bsky.ServerConfig({
version: '0.0.0',
serverDid,
didPlcUrl: this.env.plcUrl,
publicUrl: 'https://bsky.public.url',
imgUriSalt: '9dd04221f5755bce5f55f47464c27e1e',
imgUriKey:
'f23ecd142835025f42c3db2cf25dd813956c178392760256211f9d315f8ab4d8',
adminPassword: 'password',
labelerDid: 'did:example:labeler',
dbPostgresUrl: process.env.DB_POSTGRES_URL || '',
dbPostgresSchema: process.env.DB_POSTGRES_SCHEMA,
repoProvider: this.env.pdsUrl.replace('http://', 'ws://'),
port: this.port,
labelerKeywords: {},
didCacheMaxTTL: DAY,
didCacheStaleTTL: HOUR,
})
const db = bsky.Database.postgres({
url: config.dbPostgresUrl,
schema: config.dbPostgresSchema,
})
await db.migrateToLatestOrThrow()
const server = bsky.BskyAppView.create({ db, config })
await startServer(server)
this.inst = server
break
}
default:
throw new Error(`Unsupported server type: ${this.type}`)
}
}
async close() {
if (this.inst) {
console.log(`Closing ${this.description}`)
await this.inst.destroy()
}
}
getClient(): AtpAgent {
return new AtpAgent({ service: `http://localhost:${this.port}` })
}
}
export class DevEnv {
plcUrl: string | undefined
pdsUrl: string | undefined
servers: Map<number, DevEnvServer> = new Map()
static async create(params: StartParams): Promise<DevEnv> {
const devEnv = new DevEnv()
for (const cfg of params.servers || []) {
await devEnv.add(cfg)
}
return devEnv
}
async add(cfg: ServerConfig) {
if (this.servers.has(cfg.port)) {
throw new Error(`Port ${cfg.port} is in use`)
} else if (cfg.type === ServerType.DidPlaceholder && this.plcUrl) {
throw new Error('There should only be one plc server')
}
const server = new DevEnvServer(this, cfg.type, cfg.port)
await server.start()
this.servers.set(cfg.port, server)
if (cfg.type === ServerType.DidPlaceholder) {
this.plcUrl = `http://localhost:${cfg.port}`
}
if (cfg.type === ServerType.PersonalDataServer) {
this.pdsUrl = `http://localhost:${cfg.port}`
}
if (cfg.type === ServerType.BskyAppView) {
const pds = this.servers.get(PORTS[ServerType.PersonalDataServer])
if (pds) {
await mockNetworkUtilities({
port: pds.port,
url: pds.url,
close: pds.close,
ctx: pds.ctx!,
})
}
}
}
async remove(server: number | DevEnvServer) {
const port = typeof server === 'number' ? server : server.port
const inst = this.servers.get(port)
if (inst) {
await inst.close()
this.servers.delete(port)
}
}
async shutdown() {
for (const server of this.servers.values()) {
await server.close()
}
}
hasType(type: ServerType) {
for (const s of this.servers.values()) {
if (s.type === type) {
return true
}
}
return false
}
listOfType(type: ServerType): DevEnvServer[] {
return Array.from(this.servers.values()).filter((s) => s.type === type)
}
}
export * from './bsky'
export * from './network'
export * from './network-no-appview'
export * from './pds'
export * from './plc'
export * from './types'
export * from './util'

@ -4,9 +4,7 @@ import {
REASONSPAM,
REASONOTHER,
} from '@atproto/api/src/client/types/com/atproto/moderation/defs'
import { DevEnv } from '../index'
import { ServerType } from '../types'
import { genServerCfg } from '../util'
import { TestNetworkNoAppView } from '../index'
import { postTexts, replyTexts } from './data'
import labeledImgB64 from './labeled-img-b64'
@ -24,16 +22,8 @@ function* dateGen() {
return ''
}
async function createNeededServers(env: DevEnv, numNeeded = 1) {
await env.add(await genServerCfg(ServerType.DidPlaceholder))
while (env.listOfType(ServerType.PersonalDataServer).length < numNeeded) {
await env.add(await genServerCfg(ServerType.PersonalDataServer))
}
}
export async function generateMockSetup(env: DevEnv) {
export async function generateMockSetup(env: TestNetworkNoAppView) {
const date = dateGen()
await createNeededServers(env)
const rand = (n: number) => Math.floor(Math.random() * n)
const picka = <T>(arr: Array<T>): T => {
@ -44,10 +34,10 @@ export async function generateMockSetup(env: DevEnv) {
}
const clients = {
loggedout: env.listOfType(ServerType.PersonalDataServer)[0].getClient(),
alice: env.listOfType(ServerType.PersonalDataServer)[0].getClient(),
bob: env.listOfType(ServerType.PersonalDataServer)[0].getClient(),
carla: env.listOfType(ServerType.PersonalDataServer)[0].getClient(),
loggedout: env.pds.getClient(),
alice: env.pds.getClient(),
bob: env.pds.getClient(),
carla: env.pds.getClient(),
}
interface User {
email: string
@ -195,7 +185,7 @@ export async function generateMockSetup(env: DevEnv) {
},
)
const ctx = env.listOfType(ServerType.PersonalDataServer)[0].ctx
const ctx = env.pds.ctx
if (ctx) {
await ctx.db.db
.insertInto('label')

@ -0,0 +1,33 @@
import { TestServerParams } from './types'
import { TestPlc } from './plc'
import { TestPds } from './pds'
import { mockNetworkUtilities } from './util'
export class TestNetworkNoAppView {
constructor(public plc: TestPlc, public pds: TestPds) {}
static async create(
params: Partial<TestServerParams> = {},
): Promise<TestNetworkNoAppView> {
const dbPostgresUrl = params.dbPostgresUrl || process.env.DB_POSTGRES_URL
const dbPostgresSchema =
params.dbPostgresSchema || process.env.DB_POSTGRES_SCHEMA
const plc = await TestPlc.create(params.plc ?? {})
const pds = await TestPds.create({
dbPostgresUrl,
dbPostgresSchema,
plcUrl: plc.url,
...params.pds,
})
mockNetworkUtilities(pds)
return new TestNetworkNoAppView(plc, pds)
}
async close() {
await this.pds.close()
await this.plc.close()
}
}

@ -0,0 +1,84 @@
import assert from 'assert'
import getPort from 'get-port'
import { wait } from '@atproto/common-web'
import { createServiceJwt } from '@atproto/xrpc-server'
import { TestServerParams } from './types'
import { TestPlc } from './plc'
import { TestPds } from './pds'
import { TestBsky } from './bsky'
import { mockNetworkUtilities } from './util'
import { TestNetworkNoAppView } from './network-no-appview'
export class TestNetwork extends TestNetworkNoAppView {
constructor(public plc: TestPlc, public pds: TestPds, public bsky: TestBsky) {
super(plc, pds)
}
static async create(
params: Partial<TestServerParams> = {},
): Promise<TestNetwork> {
const dbPostgresUrl = params.dbPostgresUrl || process.env.DB_POSTGRES_URL
assert(dbPostgresUrl, 'Missing postgres url for tests')
const dbPostgresSchema =
params.dbPostgresSchema || process.env.DB_POSTGRES_SCHEMA
const plc = await TestPlc.create(params.plc ?? {})
const bskyPort = params.bsky?.port ?? (await getPort())
const pdsPort = params.pds?.port ?? (await getPort())
const bsky = await TestBsky.create({
port: bskyPort,
plcUrl: plc.url,
repoProvider: `ws://localhost:${pdsPort}`,
dbPostgresSchema: `appview_${dbPostgresSchema}`,
dbPostgresUrl,
...params.bsky,
})
const pds = await TestPds.create({
port: pdsPort,
dbPostgresUrl,
dbPostgresSchema,
plcUrl: plc.url,
bskyAppViewEndpoint: bsky.url,
bskyAppViewDid: bsky.ctx.cfg.serverDid,
...params.pds,
})
mockNetworkUtilities(pds)
return new TestNetwork(plc, pds, bsky)
}
async processAll(timeout = 5000) {
if (!this.bsky) return
const sub = this.bsky.sub
if (!sub) return
const { db } = this.pds.ctx.db
const start = Date.now()
while (Date.now() - start < timeout) {
await wait(50)
if (!sub) return
const state = await sub.getState()
const { lastSeq } = await db
.selectFrom('repo_seq')
.select(db.fn.max('repo_seq.seq').as('lastSeq'))
.executeTakeFirstOrThrow()
if (state.cursor === lastSeq) return
}
throw new Error(`Sequence was not processed within ${timeout}ms`)
}
async serviceHeaders(did: string) {
const jwt = await createServiceJwt({
iss: did,
aud: this.bsky.ctx.cfg.serverDid,
keypair: this.pds.ctx.repoSigningKey,
})
return { authorization: `Bearer ${jwt}` }
}
async close() {
await this.bsky.close()
await this.pds.close()
await this.plc.close()
}
}

114
packages/dev-env/src/pds.ts Normal file

@ -0,0 +1,114 @@
import getPort from 'get-port'
import * as ui8 from 'uint8arrays'
import * as pds from '@atproto/pds'
import { Secp256k1Keypair } from '@atproto/crypto'
import { MessageDispatcher } from '@atproto/pds/src/event-stream/message-queue'
import { AtpAgent } from '@atproto/api'
import { Client as PlcClient } from '@did-plc/lib'
import { PdsConfig } from './types'
export class TestPds {
constructor(
public url: string,
public port: number,
public server: pds.PDS,
) {}
static async create(cfg: PdsConfig): Promise<TestPds> {
const repoSigningKey = await Secp256k1Keypair.create()
const plcRotationKey = await Secp256k1Keypair.create()
const recoveryKey = await Secp256k1Keypair.create()
const port = cfg.port || (await getPort())
const url = `http://localhost:${port}`
const plcClient = new PlcClient(cfg.plcUrl)
const serverDid = await plcClient.createDid({
signingKey: repoSigningKey.did(),
rotationKeys: [recoveryKey.did(), plcRotationKey.did()],
handle: 'pds.test',
pds: `http://localhost:${port}`,
signer: plcRotationKey,
})
const config = new pds.ServerConfig({
debugMode: true,
version: '0.0.0',
scheme: 'http',
port,
hostname: 'localhost',
serverDid,
recoveryKey: recoveryKey.did(),
adminPassword: 'admin-pass',
inviteRequired: false,
userInviteInterval: null,
didPlcUrl: cfg.plcUrl,
jwtSecret: 'jwt-secret',
availableUserDomains: ['.test'],
appUrlPasswordReset: 'app://forgot-password',
emailNoReplyAddress: 'noreply@blueskyweb.xyz',
publicUrl: 'https://pds.public.url',
imgUriSalt: '9dd04221f5755bce5f55f47464c27e1e',
imgUriKey:
'f23ecd142835025f42c3db2cf25dd813956c178392760256211f9d315f8ab4d8',
dbPostgresUrl: cfg.dbPostgresUrl,
maxSubscriptionBuffer: 200,
repoBackfillLimitMs: 1000 * 60 * 60, // 1hr
labelerDid: 'did:example:labeler',
labelerKeywords: { label_me: 'test-label', label_me_2: 'test-label-2' },
...cfg,
})
const blobstore = new pds.MemoryBlobStore()
const db = config.dbPostgresUrl
? pds.Database.postgres({
url: config.dbPostgresUrl,
schema: config.dbPostgresSchema,
})
: pds.Database.memory()
await db.migrateToLatestOrThrow()
if (config.bskyAppViewEndpoint) {
// Disable communication to app view within pds
MessageDispatcher.prototype.send = async () => {}
}
const server = pds.PDS.create({
db,
blobstore,
repoSigningKey,
plcRotationKey,
config,
})
await server.start()
return new TestPds(url, port, server)
}
get ctx(): pds.AppContext {
return this.server.ctx
}
getClient(): AtpAgent {
return new AtpAgent({ service: `http://localhost:${this.port}` })
}
adminAuth(): string {
return (
'Basic ' +
ui8.toString(
ui8.fromString(`admin:${this.ctx.cfg.adminPassword}`, 'utf8'),
'base64pad',
)
)
}
adminAuthHeaders() {
return {
authorization: this.adminAuth(),
}
}
async close() {
await this.server.destroy()
}
}

@ -0,0 +1,33 @@
import getPort from 'get-port'
import { Client as PlcClient } from '@did-plc/lib'
import * as plc from '@did-plc/server'
import { PlcConfig } from './types'
export class TestPlc {
constructor(
public url: string,
public port: number,
public server: plc.PlcServer,
) {}
static async create(cfg: PlcConfig): Promise<TestPlc> {
const db = plc.Database.mock()
const port = cfg.port || (await getPort())
const url = `http://localhost:${port}`
const server = plc.PlcServer.create({ db, port, ...cfg })
await server.start()
return new TestPlc(url, port, server)
}
get ctx(): plc.AppContext {
return this.server.ctx
}
getClient(): PlcClient {
return new PlcClient(this.url)
}
async close() {
await this.server.destroy()
}
}

@ -1,327 +0,0 @@
import assert from 'assert'
import { AddressInfo } from 'net'
import * as crypto from '@atproto/crypto'
import * as pds from '@atproto/pds'
import * as plc from '@did-plc/server'
import { Client as PlcClient } from '@did-plc/lib'
import * as bsky from '@atproto/bsky'
import { AtpAgent } from '@atproto/api'
import { DidResolver } from '@atproto/did-resolver'
import { defaultFetchHandler } from '@atproto/xrpc'
import { MessageDispatcher } from '@atproto/pds/src/event-stream/message-queue'
import { RepoSubscription } from '@atproto/bsky/src/subscription/repo'
import getPort from 'get-port'
import { DAY, HOUR, wait } from '@atproto/common-web'
export type CloseFn = () => Promise<void>
type ServerInfo = {
port: number
url: string
close: CloseFn
}
export type PlcServerInfo = ServerInfo & {
ctx: plc.AppContext
}
export type PdsServerInfo = ServerInfo & {
ctx: pds.AppContext
}
export type BskyServerInfo = ServerInfo & {
ctx: bsky.AppContext
sub: RepoSubscription
}
export type TestEnvInfo = {
bsky: BskyServerInfo
pds: PdsServerInfo
plc: PlcServerInfo
close: CloseFn
}
export type PlcConfig = {
port?: number
version?: string
}
export type PdsConfig = Partial<pds.ServerConfig> & {
plcUrl: string
migration?: string
}
export type BskyConfig = Partial<bsky.ServerConfig> & {
plcUrl: string
repoProvider: string
dbPostgresUrl: string
migration?: string
}
export type TestServerParams = {
dbPostgresUrl: string
dbPostgresSchema: string
pds: Partial<pds.ServerConfig>
bsky: Partial<bsky.ServerConfig>
}
export const runTestEnv = async (
params: Partial<TestServerParams> = {},
): Promise<TestEnvInfo> => {
const dbPostgresUrl = params.dbPostgresUrl || process.env.DB_POSTGRES_URL
assert(dbPostgresUrl, 'Missing postgres url for tests')
const dbPostgresSchema =
params.dbPostgresSchema || process.env.DB_POSTGRES_SCHEMA
const plc = await runPlc({})
const pdsPort = await getPort()
const bsky = await runBsky({
plcUrl: plc.url,
repoProvider: `ws://localhost:${pdsPort}`,
dbPostgresSchema: `appview_${dbPostgresSchema}`,
dbPostgresUrl,
})
const pds = await runPds({
port: pdsPort,
dbPostgresUrl,
dbPostgresSchema,
plcUrl: plc.url,
bskyAppViewEndpoint: `http://localhost:${bsky.port}`,
bskyAppViewDid: bsky.ctx.cfg.serverDid,
})
mockNetworkUtilities(pds)
return {
bsky,
pds,
plc,
close: async () => {
await bsky.close()
await pds.close()
await plc.close()
},
}
}
export const runPlc = async (cfg: PlcConfig): Promise<PlcServerInfo> => {
const db = plc.Database.mock()
const server = plc.PlcServer.create({ db, ...cfg })
const listener = await server.start()
const port = (listener.address() as AddressInfo).port
const url = `http://localhost:${port}`
return {
port,
url,
ctx: server.ctx,
close: async () => {
await server.destroy()
},
}
}
export const runPds = async (cfg: PdsConfig): Promise<PdsServerInfo> => {
const repoSigningKey = await crypto.Secp256k1Keypair.create()
const plcRotationKey = await crypto.Secp256k1Keypair.create()
const recoveryKey = await crypto.Secp256k1Keypair.create()
const port = cfg.port || (await getPort())
const plcClient = new PlcClient(cfg.plcUrl)
const serverDid = await plcClient.createDid({
signingKey: repoSigningKey.did(),
rotationKeys: [recoveryKey.did(), plcRotationKey.did()],
handle: 'pds.test',
pds: `http://localhost:${port}`,
signer: plcRotationKey,
})
const config = new pds.ServerConfig({
debugMode: true,
version: '0.0.0',
scheme: 'http',
hostname: 'localhost',
port,
serverDid,
recoveryKey: recoveryKey.did(),
adminPassword: 'admin-pass',
inviteRequired: false,
userInviteInterval: null,
didPlcUrl: cfg.plcUrl,
jwtSecret: 'jwt-secret',
availableUserDomains: ['.test'],
appUrlPasswordReset: 'app://forgot-password',
emailNoReplyAddress: 'noreply@blueskyweb.xyz',
publicUrl: 'https://pds.public.url',
imgUriSalt: '9dd04221f5755bce5f55f47464c27e1e',
imgUriKey:
'f23ecd142835025f42c3db2cf25dd813956c178392760256211f9d315f8ab4d8',
dbPostgresUrl: cfg.dbPostgresUrl,
maxSubscriptionBuffer: 200,
repoBackfillLimitMs: 1000 * 60 * 60, // 1hr
labelerDid: 'did:example:labeler',
labelerKeywords: { label_me: 'test-label', label_me_2: 'test-label-2' },
...cfg,
})
const blobstore = new pds.MemoryBlobStore()
const db = config.dbPostgresUrl
? pds.Database.postgres({
url: config.dbPostgresUrl,
schema: config.dbPostgresSchema,
})
: pds.Database.memory()
await db.migrateToLatestOrThrow()
// Disable communication to app view within pds
MessageDispatcher.prototype.send = async () => {}
const server = pds.PDS.create({
db,
blobstore,
repoSigningKey,
plcRotationKey,
config,
})
await server.start()
const url = `http://localhost:${port}`
return {
port,
url,
ctx: server.ctx,
close: async () => {
await server.destroy()
},
}
}
export const runBsky = async (cfg: BskyConfig): Promise<BskyServerInfo> => {
const serviceKeypair = await crypto.Secp256k1Keypair.create()
const plcClient = new PlcClient(cfg.plcUrl)
const port = cfg.port || (await getPort())
const serverDid = await plcClient.createDid({
signingKey: serviceKeypair.did(),
rotationKeys: [serviceKeypair.did()],
handle: 'bsky.test',
pds: `http://localhost:${port}`,
signer: serviceKeypair,
})
const config = new bsky.ServerConfig({
version: '0.0.0',
port,
didPlcUrl: cfg.plcUrl,
publicUrl: 'https://bsky.public.url',
serverDid,
imgUriSalt: '9dd04221f5755bce5f55f47464c27e1e',
imgUriKey:
'f23ecd142835025f42c3db2cf25dd813956c178392760256211f9d315f8ab4d8',
didCacheStaleTTL: HOUR,
didCacheMaxTTL: DAY,
...cfg,
// Each test suite gets its own lock id for the repo subscription
repoSubLockId: uniqueLockId(),
adminPassword: 'admin-pass',
labelerDid: 'did:example:labeler',
labelerKeywords: { label_me: 'test-label', label_me_2: 'test-label-2' },
})
const db = bsky.Database.postgres({
url: cfg.dbPostgresUrl,
schema: cfg.dbPostgresSchema,
})
// Separate migration db in case migration changes some connection state that we need in the tests, e.g. "alter database ... set ..."
const migrationDb = bsky.Database.postgres({
url: cfg.dbPostgresUrl,
schema: cfg.dbPostgresSchema,
})
if (cfg.migration) {
await migrationDb.migrateToOrThrow(cfg.migration)
} else {
await migrationDb.migrateToLatestOrThrow()
}
await migrationDb.close()
const server = bsky.BskyAppView.create({ db, config })
await server.start()
const url = `http://localhost:${port}`
const sub = server.sub
if (!sub) {
throw new Error('No appview sub setup')
}
return {
port,
url,
ctx: server.ctx,
sub,
close: async () => {
await server.destroy()
},
}
}
const usedLockIds = new Set()
const uniqueLockId = () => {
let lockId: number
do {
lockId = 1000 + Math.ceil(1000 * Math.random())
} while (usedLockIds.has(lockId))
usedLockIds.add(lockId)
return lockId
}
export const mockNetworkUtilities = (pds: PdsServerInfo) => {
// Map pds public url to its local url when resolving from plc
const origResolveDid = DidResolver.prototype.resolveDidNoCache
DidResolver.prototype.resolveDidNoCache = async function (did) {
const result = await (origResolveDid.call(this, did) as ReturnType<
typeof origResolveDid
>)
const service = result?.service?.find((svc) => svc.id === '#atproto_pds')
if (typeof service?.serviceEndpoint === 'string') {
service.serviceEndpoint = service.serviceEndpoint.replace(
pds.ctx.cfg.publicUrl,
`http://localhost:${pds.port}`,
)
}
return result
}
// Map pds public url and handles to pds local url
AtpAgent.configure({
fetch: (httpUri, ...args) => {
const url = new URL(httpUri)
const pdsUrl = pds.ctx.cfg.publicUrl
const pdsHandleDomains = pds.ctx.cfg.availableUserDomains
if (
url.origin === pdsUrl ||
pdsHandleDomains.some((handleDomain) => url.host.endsWith(handleDomain))
) {
url.protocol = 'http:'
url.host = `localhost:${pds.port}`
return defaultFetchHandler(url.href, ...args)
}
return defaultFetchHandler(httpUri, ...args)
},
})
}
export const processAll = async (server: TestEnvInfo, timeout = 5000) => {
const { bsky, pds } = server
const sub = bsky.sub
if (!sub) return
const { db } = pds.ctx.db
const start = Date.now()
while (Date.now() - start < timeout) {
await wait(50)
if (!sub) return
const state = await sub.getState()
const { lastSeq } = await db
.selectFrom('repo_seq')
.select(db.fn.max('repo_seq.seq').as('lastSeq'))
.executeTakeFirstOrThrow()
if (state.cursor === lastSeq) return
}
throw new Error(`Sequence was not processed within ${timeout}ms`)
}

@ -1,24 +1,27 @@
export enum ServerType {
PersonalDataServer = 'pds',
DidPlaceholder = 'plc',
BskyAppView = 'bsky',
import * as pds from '@atproto/pds'
import * as bsky from '@atproto/bsky'
export type PlcConfig = {
port?: number
version?: string
}
export interface ServerConfig {
type: ServerType
port: number
export type PdsConfig = Partial<pds.ServerConfig> & {
plcUrl: string
migration?: string
}
export interface StartParams {
servers?: ServerConfig[]
export type BskyConfig = Partial<bsky.ServerConfig> & {
plcUrl: string
repoProvider: string
dbPostgresUrl: string
migration?: string
}
export const PORTS = {
[ServerType.BskyAppView]: 2584,
[ServerType.PersonalDataServer]: 2583,
[ServerType.DidPlaceholder]: 2582,
}
export const SERVER_TYPE_LABELS = {
[ServerType.PersonalDataServer]: 'personal data server',
export type TestServerParams = {
dbPostgresUrl: string
dbPostgresSchema: string
pds: Partial<pds.ServerConfig>
plc: Partial<pds.ServerConfig>
bsky: Partial<bsky.ServerConfig>
}

@ -1,13 +1,40 @@
import getPort, { portNumbers } from 'get-port'
import { PORTS, ServerType, ServerConfig } from './types.js'
import { AtpAgent } from '@atproto/api'
import { DidResolver } from '@atproto/did-resolver'
import { defaultFetchHandler } from '@atproto/xrpc'
import { TestPds } from './pds'
export async function genServerCfg(
type: ServerType,
port?: number,
): Promise<ServerConfig> {
const basePort = PORTS[type]
return {
type,
port: port || (await getPort({ port: portNumbers(basePort, 65535) })),
export const mockNetworkUtilities = (pds: TestPds) => {
// Map pds public url to its local url when resolving from plc
const origResolveDid = DidResolver.prototype.resolveDidNoCache
DidResolver.prototype.resolveDidNoCache = async function (did) {
const result = await (origResolveDid.call(this, did) as ReturnType<
typeof origResolveDid
>)
const service = result?.service?.find((svc) => svc.id === '#atproto_pds')
if (typeof service?.serviceEndpoint === 'string') {
service.serviceEndpoint = service.serviceEndpoint.replace(
pds.ctx.cfg.publicUrl,
`http://localhost:${pds.port}`,
)
}
return result
}
// Map pds public url and handles to pds local url
AtpAgent.configure({
fetch: (httpUri, ...args) => {
const url = new URL(httpUri)
const pdsUrl = pds.ctx.cfg.publicUrl
const pdsHandleDomains = pds.ctx.cfg.availableUserDomains
if (
url.origin === pdsUrl ||
pdsHandleDomains.some((handleDomain) => url.host.endsWith(handleDomain))
) {
url.protocol = 'http:'
url.host = `localhost:${pds.port}`
return defaultFetchHandler(url.href, ...args)
}
return defaultFetchHandler(httpUri, ...args)
},
})
}

@ -1,29 +1,28 @@
import AtpAgent from '@atproto/api'
import { runTestEnv, CloseFn, processAll } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { forSnapshot, paginateAll } from '../_util'
import { SeedClient } from '../seeds/client'
import usersBulkSeed from '../seeds/users-bulk'
import { wait } from '@atproto/common'
describe('pds user search proxy views', () => {
let network: TestNetwork
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
let headers: { [s: string]: string }
beforeAll(async () => {
const testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'proxy_user_search',
})
close = testEnv.close
agent = new AtpAgent({ service: testEnv.pds.url })
agent = new AtpAgent({ service: network.pds.url })
sc = new SeedClient(agent)
await wait(50) // allow pending sub to be established
await testEnv.bsky.sub.destroy()
await network.bsky.sub.destroy()
await usersBulkSeed(sc)
// Skip did/handle resolution for expediency
const { db } = testEnv.bsky.ctx
const { db } = network.bsky.ctx
const now = new Date().toISOString()
await db.db
.insertInto('actor')
@ -38,13 +37,13 @@ describe('pds user search proxy views', () => {
.execute()
// Process remaining profiles
testEnv.bsky.sub.resume()
await processAll(testEnv, 50000)
network.bsky.sub.resume()
await network.processAll(50000)
headers = sc.getHeaders(Object.values(sc.dids)[0])
})
afterAll(async () => {
await close()
await network.close()
})
it('typeahead gives relevant results', async () => {

@ -1,12 +1,12 @@
import AtpAgent from '@atproto/api'
import { runTestEnv, CloseFn, processAll } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { forSnapshot, paginateAll } from '../_util'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
describe('pds author feed proxy views', () => {
let network: TestNetwork
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
// account dids, for convenience
@ -16,23 +16,22 @@ describe('pds author feed proxy views', () => {
let dan: string
beforeAll(async () => {
const testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'proxy_author_feed',
})
close = testEnv.close
agent = new AtpAgent({ service: testEnv.pds.url })
agent = network.pds.getClient()
sc = new SeedClient(agent)
await basicSeed(sc)
alice = sc.dids.alice
bob = sc.dids.bob
carol = sc.dids.carol
dan = sc.dids.dan
await testEnv.pds.ctx.labeler.processAll()
await processAll(testEnv)
await network.pds.ctx.labeler.processAll()
await network.processAll()
})
afterAll(async () => {
await close()
await network.close()
})
it('fetches full author feeds for self (sorted, minimal viewer state).', async () => {

@ -1,31 +1,30 @@
import AtpAgent from '@atproto/api'
import { runTestEnv, CloseFn, processAll } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { forSnapshot, paginateAll } from '../_util'
import { SeedClient } from '../seeds/client'
import followsSeed from '../seeds/follows'
describe('pds follow proxy views', () => {
let network: TestNetwork
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
// account dids, for convenience
let alice: string
beforeAll(async () => {
const testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'proxy_follows',
})
close = testEnv.close
agent = new AtpAgent({ service: testEnv.pds.url })
agent = network.pds.getClient()
sc = new SeedClient(agent)
await followsSeed(sc)
await processAll(testEnv)
await network.processAll()
alice = sc.dids.alice
})
afterAll(async () => {
await close()
await network.close()
})
it('fetches followers', async () => {

@ -1,12 +1,12 @@
import AtpAgent from '@atproto/api'
import { runTestEnv, CloseFn, processAll } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { SeedClient } from '../seeds/client'
import likesSeed from '../seeds/likes'
import { constantDate, forSnapshot, paginateAll } from '../_util'
describe('pds like proxy views', () => {
let network: TestNetwork
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
// account dids, for convenience
@ -14,20 +14,19 @@ describe('pds like proxy views', () => {
let bob: string
beforeAll(async () => {
const testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'proxy_likes',
})
close = testEnv.close
agent = new AtpAgent({ service: testEnv.pds.url })
agent = network.pds.getClient()
sc = new SeedClient(agent)
await likesSeed(sc)
await processAll(testEnv)
await network.processAll()
alice = sc.dids.alice
bob = sc.dids.bob
})
afterAll(async () => {
await close()
await network.close()
})
const getCursors = (items: { createdAt?: string }[]) =>

@ -1,33 +1,31 @@
import AtpAgent from '@atproto/api'
import { runTestEnv, CloseFn, processAll, TestEnvInfo } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { forSnapshot, paginateAll } from '../_util'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
import { Notification } from '../../src/lexicon/types/app/bsky/notification/listNotifications'
describe('pds notification proxy views', () => {
let network: TestNetwork
let agent: AtpAgent
let testEnv: TestEnvInfo
let close: CloseFn
let sc: SeedClient
// account dids, for convenience
let alice: string
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'proxy_notifications',
})
close = testEnv.close
agent = new AtpAgent({ service: testEnv.pds.url })
agent = new AtpAgent({ service: network.pds.url })
sc = new SeedClient(agent)
await basicSeed(sc)
await processAll(testEnv)
await network.processAll()
alice = sc.dids.alice
})
afterAll(async () => {
await close()
await network.close()
})
const sort = (notifs: Notification[]) => {
@ -66,7 +64,7 @@ describe('pds notification proxy views', () => {
'indeed',
)
await processAll(testEnv)
await network.processAll()
const notifCountAlice =
await agent.api.app.bsky.notification.getUnreadCount(

@ -1,12 +1,11 @@
import AtpAgent from '@atproto/api'
import { runTestEnv, CloseFn, processAll, TestEnvInfo } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
describe('popular proxy views', () => {
let network: TestNetwork
let agent: AtpAgent
let testEnv: TestEnvInfo
let close: CloseFn
let sc: SeedClient
// account dids, for convenience
@ -23,11 +22,10 @@ describe('popular proxy views', () => {
}
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'proxy_popular',
})
close = testEnv.close
agent = new AtpAgent({ service: testEnv.pds.url })
agent = network.pds.getClient()
sc = new SeedClient(agent)
await basicSeed(sc)
await sc.createAccount('eve', {
@ -42,7 +40,7 @@ describe('popular proxy views', () => {
handle: 'frank.test',
password: 'frank-pass',
})
await processAll(testEnv)
await network.processAll()
alice = sc.dids.alice
bob = sc.dids.bob
carol = sc.dids.carol
@ -52,7 +50,7 @@ describe('popular proxy views', () => {
})
afterAll(async () => {
await close()
await network.close()
})
it('returns well liked posts', async () => {
@ -80,7 +78,7 @@ describe('popular proxy views', () => {
await sc.like(eve, three.ref)
await sc.like(frank, three.ref)
await processAll(testEnv)
await network.processAll()
const res = await agent.api.app.bsky.unspecced.getPopular(
{},

@ -1,26 +1,26 @@
import AtpAgent from '@atproto/api'
import { TestEnvInfo, processAll, runTestEnv } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { forSnapshot } from '../_util'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
describe('pds posts views', () => {
let testEnv: TestEnvInfo
let network: TestNetwork
let agent: AtpAgent
let sc: SeedClient
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'proxy_posts',
})
agent = new AtpAgent({ service: testEnv.pds.url })
agent = network.pds.getClient()
sc = new SeedClient(agent)
await basicSeed(sc)
await processAll(testEnv)
await network.processAll()
})
afterAll(async () => {
await testEnv.close()
await network.close()
})
it('fetches posts', async () => {

@ -1,12 +1,12 @@
import AtpAgent from '@atproto/api'
import { runTestEnv, CloseFn, processAll } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { forSnapshot } from '../_util'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
describe('pds profile proxy views', () => {
let network: TestNetwork
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
// account dids, for convenience
@ -15,21 +15,20 @@ describe('pds profile proxy views', () => {
let dan: string
beforeAll(async () => {
const testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'proxy_profile',
})
close = testEnv.close
agent = new AtpAgent({ service: testEnv.pds.url })
agent = network.pds.getClient()
sc = new SeedClient(agent)
await basicSeed(sc)
await processAll(testEnv)
await network.processAll()
alice = sc.dids.alice
bob = sc.dids.bob
dan = sc.dids.dan
})
afterAll(async () => {
await close()
await network.close()
})
it('fetches own profile', async () => {

@ -1,12 +1,12 @@
import AtpAgent from '@atproto/api'
import { runTestEnv, CloseFn, processAll } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { forSnapshot, paginateAll } from '../_util'
import { SeedClient } from '../seeds/client'
import repostsSeed from '../seeds/reposts'
describe('pds repost proxy views', () => {
let network: TestNetwork
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
// account dids, for convenience
@ -14,20 +14,19 @@ describe('pds repost proxy views', () => {
let bob: string
beforeAll(async () => {
const testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'proxy_reposts',
})
close = testEnv.close
agent = new AtpAgent({ service: testEnv.pds.url })
agent = network.pds.getClient()
sc = new SeedClient(agent)
await repostsSeed(sc)
await processAll(testEnv)
await network.processAll()
alice = sc.dids.alice
bob = sc.dids.bob
})
afterAll(async () => {
await close()
await network.close()
})
it('fetches reposted-by for a post', async () => {

@ -1,26 +1,25 @@
import AtpAgent from '@atproto/api'
import { runTestEnv, CloseFn, processAll } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
describe('pds user search proxy views', () => {
let network: TestNetwork
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
beforeAll(async () => {
const testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'proxy_suggestions',
})
close = testEnv.close
agent = new AtpAgent({ service: testEnv.pds.url })
agent = network.pds.getClient()
sc = new SeedClient(agent)
await basicSeed(sc)
await processAll(testEnv)
await network.processAll()
})
afterAll(async () => {
await close()
await network.close()
})
it('actor suggestion gives users', async () => {

@ -1,13 +1,12 @@
import AtpAgent, { AppBskyFeedGetPostThread } from '@atproto/api'
import { runTestEnv, CloseFn, processAll, TestEnvInfo } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { forSnapshot } from '../_util'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
describe('pds thread proxy views', () => {
let network: TestNetwork
let agent: AtpAgent
let testEnv: TestEnvInfo
let close: CloseFn
let sc: SeedClient
// account dids, for convenience
@ -15,11 +14,10 @@ describe('pds thread proxy views', () => {
let bob: string
beforeAll(async () => {
testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'proxy_thread',
})
close = testEnv.close
agent = new AtpAgent({ service: testEnv.pds.url })
agent = network.pds.getClient()
sc = new SeedClient(agent)
await basicSeed(sc)
alice = sc.dids.alice
@ -29,11 +27,11 @@ describe('pds thread proxy views', () => {
beforeAll(async () => {
// Add a repost of a reply so that we can confirm viewer state in the thread
await sc.repost(bob, sc.replies[alice][0].ref)
await processAll(testEnv)
await network.processAll()
})
afterAll(async () => {
await close()
await network.close()
})
it('fetches deep post thread', async () => {
@ -120,7 +118,7 @@ describe('pds thread proxy views', () => {
)
indexes.aliceReplyReply = sc.replies[alice].length - 1
await processAll(testEnv)
await network.processAll()
const thread1 = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.posts[alice][indexes.aliceRoot].ref.uriStr },
@ -129,7 +127,7 @@ describe('pds thread proxy views', () => {
expect(forSnapshot(thread1.data.thread)).toMatchSnapshot()
await sc.deletePost(bob, sc.replies[bob][indexes.bobReply].ref.uri)
await processAll(testEnv)
await network.processAll()
const thread2 = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.posts[alice][indexes.aliceRoot].ref.uriStr },

@ -1,5 +1,5 @@
import AtpAgent from '@atproto/api'
import { runTestEnv, CloseFn, processAll } from '@atproto/dev-env'
import { TestNetwork } from '@atproto/dev-env'
import { FeedViewPost } from '../../src/lexicon/types/app/bsky/feed/defs'
import { forSnapshot, getOriginator, paginateAll } from '../_util'
import { SeedClient } from '../seeds/client'
@ -7,8 +7,8 @@ import basicSeed from '../seeds/basic'
import { FeedAlgorithm } from '../../src/app-view/api/app/bsky/util/feed'
describe('timeline proxy views', () => {
let network: TestNetwork
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
// account dids, for convenience
@ -18,23 +18,22 @@ describe('timeline proxy views', () => {
let dan: string
beforeAll(async () => {
const testEnv = await runTestEnv({
network = await TestNetwork.create({
dbPostgresSchema: 'proxy_timeline',
})
close = testEnv.close
agent = new AtpAgent({ service: testEnv.pds.url })
agent = network.pds.getClient()
sc = new SeedClient(agent)
await basicSeed(sc)
alice = sc.dids.alice
bob = sc.dids.bob
carol = sc.dids.carol
dan = sc.dids.dan
await processAll(testEnv)
await testEnv.pds.ctx.labeler.processAll()
await network.processAll()
await network.pds.ctx.labeler.processAll()
})
afterAll(async () => {
await close()
await network.close()
})
it("fetches authenticated user's home feed w/ reverse-chronological algorithm", async () => {