* draft of account migration lexicons * format * clean up schemas * codegen * second pass on schemas * small fix * move around checkImportStatus * re-codegen * getServiceAuth * getServiceAuth impl * importRepo impl * handle uploadBlob for import * allow bringing your own did on createAccount * working on test flow * fleshing out flow * fix up sendPlcOP * small fixes * activate/deactivate account * full flow working! * schema tweaks * format * update schemas * moar codegen * match impl to new schemas * email flow for signed plc operation * add email flow for plc operations * impl plc op request email * proxy to entryway * tidy activate account * integrating account deactivated state * fix up tests * friendly parse on optional did auth * admin activate/deactivate routes * proxy relevant requests to entryway * remove admin activation routes * do not proxy acitvaition to entryway * cfg for disallowing imports * buff up test * refactor listMissingBlobs a bit * add validDid & activated to accoutn status * emit event on account activation * test creating a post after migrating * account deactivation tests * test name * tests on plc operations * fix recommended did creds * codegen * turn off accepting imports on createAccount * undo prev change * increment version * build branch * pr feedback * handle errs in p-queue * handle blob upload outside of txn * Clean old temp account migration lexicons (#2187) * clean old temp lexicons * rm old test * fix agent session test * fix bsky test * dont build branch
220 lines
6.1 KiB
TypeScript
220 lines
6.1 KiB
TypeScript
import AtpAgent, { AtUri } from '@atproto/api'
|
|
import {
|
|
SeedClient,
|
|
TestNetworkNoAppView,
|
|
TestPds,
|
|
mockNetworkUtilities,
|
|
} from '@atproto/dev-env'
|
|
import { readCar } from '@atproto/repo'
|
|
import assert from 'assert'
|
|
|
|
describe('account migration', () => {
|
|
let network: TestNetworkNoAppView
|
|
let newPds: TestPds
|
|
|
|
let sc: SeedClient
|
|
let oldAgent: AtpAgent
|
|
let newAgent: AtpAgent
|
|
|
|
let alice: string
|
|
|
|
beforeAll(async () => {
|
|
network = await TestNetworkNoAppView.create({
|
|
dbPostgresSchema: 'account_migration',
|
|
})
|
|
newPds = await TestPds.create({
|
|
didPlcUrl: network.plc.url,
|
|
})
|
|
mockNetworkUtilities(newPds)
|
|
|
|
sc = network.getSeedClient()
|
|
oldAgent = network.pds.getClient()
|
|
newAgent = newPds.getClient()
|
|
|
|
await sc.createAccount('alice', {
|
|
email: 'alice@test.com',
|
|
handle: 'alice.test',
|
|
password: 'alice-pass',
|
|
})
|
|
alice = sc.dids.alice
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
await sc.post(alice, 'test post')
|
|
}
|
|
const img1 = await sc.uploadFile(
|
|
alice,
|
|
'../dev-env/src/seed/img/at.png',
|
|
'image/png',
|
|
)
|
|
const img2 = await sc.uploadFile(
|
|
alice,
|
|
'../dev-env/src/seed/img/key-alt.jpg',
|
|
'image/jpeg',
|
|
)
|
|
const img3 = await sc.uploadFile(
|
|
alice,
|
|
'../dev-env/src/seed/img/key-landscape-small.jpg',
|
|
'image/jpeg',
|
|
)
|
|
|
|
await sc.post(alice, 'test', undefined, [img1])
|
|
await sc.post(alice, 'test', undefined, [img1, img2])
|
|
await sc.post(alice, 'test', undefined, [img3])
|
|
|
|
await network.processAll()
|
|
|
|
await oldAgent.login({
|
|
identifier: sc.accounts[alice].handle,
|
|
password: sc.accounts[alice].password,
|
|
})
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await newPds.close()
|
|
await network.close()
|
|
})
|
|
|
|
it('migrates an account', async () => {
|
|
const describeRes = await newAgent.api.com.atproto.server.describeServer()
|
|
const newServerDid = describeRes.data.did
|
|
|
|
const serviceJwtRes = await oldAgent.com.atproto.server.getServiceAuth({
|
|
aud: newServerDid,
|
|
})
|
|
const serviceJwt = serviceJwtRes.data.token
|
|
|
|
await newAgent.api.com.atproto.server.createAccount(
|
|
{
|
|
handle: 'new-alice.test',
|
|
email: 'alice@test.com',
|
|
password: 'alice-pass',
|
|
did: alice,
|
|
},
|
|
{
|
|
headers: { authorization: `Bearer ${serviceJwt}` },
|
|
encoding: 'application/json',
|
|
},
|
|
)
|
|
await newAgent.login({
|
|
identifier: 'new-alice.test',
|
|
password: 'alice-pass',
|
|
})
|
|
|
|
const statusRes1 = await newAgent.com.atproto.server.checkAccountStatus()
|
|
expect(statusRes1.data).toMatchObject({
|
|
activated: false,
|
|
validDid: false,
|
|
repoBlocks: 2, // commit & empty data root
|
|
indexedRecords: 0,
|
|
privateStateValues: 0,
|
|
expectedBlobs: 0,
|
|
importedBlobs: 0,
|
|
})
|
|
|
|
const repoRes = await oldAgent.com.atproto.sync.getRepo({ did: alice })
|
|
const carBlocks = await readCar(repoRes.data)
|
|
|
|
await newAgent.com.atproto.repo.importRepo(repoRes.data, {
|
|
encoding: 'application/vnd.ipld.car',
|
|
})
|
|
|
|
const statusRes2 = await newAgent.com.atproto.server.checkAccountStatus()
|
|
expect(statusRes2.data).toMatchObject({
|
|
activated: false,
|
|
validDid: false,
|
|
indexedRecords: 103,
|
|
privateStateValues: 0,
|
|
expectedBlobs: 3,
|
|
importedBlobs: 0,
|
|
})
|
|
expect(statusRes2.data.repoBlocks).toBe(carBlocks.blocks.size)
|
|
|
|
const missingBlobs = await newAgent.com.atproto.repo.listMissingBlobs()
|
|
expect(missingBlobs.data.blobs.length).toBe(3)
|
|
|
|
let blobCursor: string | undefined = undefined
|
|
do {
|
|
const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({
|
|
did: alice,
|
|
cursor: blobCursor,
|
|
})
|
|
for (const cid of listedBlobs.data.cids) {
|
|
const blobRes = await oldAgent.com.atproto.sync.getBlob({
|
|
did: alice,
|
|
cid,
|
|
})
|
|
await newAgent.com.atproto.repo.uploadBlob(blobRes.data, {
|
|
encoding: blobRes.headers['content-type'],
|
|
})
|
|
}
|
|
blobCursor = listedBlobs.data.cursor
|
|
} while (blobCursor)
|
|
|
|
const statusRes3 = await newAgent.com.atproto.server.checkAccountStatus()
|
|
expect(statusRes3.data.expectedBlobs).toBe(3)
|
|
expect(statusRes3.data.importedBlobs).toBe(3)
|
|
|
|
const prefs = await oldAgent.api.app.bsky.actor.getPreferences()
|
|
await newAgent.api.app.bsky.actor.putPreferences(prefs.data)
|
|
|
|
const getDidCredentials =
|
|
await newAgent.com.atproto.identity.getRecommendedDidCredentials()
|
|
|
|
await oldAgent.com.atproto.identity.requestPlcOperationSignature()
|
|
const res = await network.pds.ctx.accountManager.db.db
|
|
.selectFrom('email_token')
|
|
.selectAll()
|
|
.where('did', '=', alice)
|
|
.where('purpose', '=', 'plc_operation')
|
|
.executeTakeFirst()
|
|
const token = res?.token
|
|
assert(token)
|
|
|
|
const plcOp = await oldAgent.com.atproto.identity.signPlcOperation({
|
|
token,
|
|
...getDidCredentials.data,
|
|
})
|
|
|
|
await newAgent.com.atproto.identity.submitPlcOperation({
|
|
operation: plcOp.data.operation,
|
|
})
|
|
|
|
await newAgent.com.atproto.server.activateAccount()
|
|
|
|
const statusRes4 = await newAgent.com.atproto.server.checkAccountStatus()
|
|
expect(statusRes4.data).toMatchObject({
|
|
activated: true,
|
|
validDid: true,
|
|
indexedRecords: 103,
|
|
privateStateValues: 0,
|
|
expectedBlobs: 3,
|
|
importedBlobs: 3,
|
|
})
|
|
|
|
await oldAgent.com.atproto.server.deactivateAccount({})
|
|
|
|
const statusResOldPds =
|
|
await oldAgent.com.atproto.server.checkAccountStatus()
|
|
expect(statusResOldPds.data).toMatchObject({
|
|
activated: false,
|
|
validDid: false,
|
|
})
|
|
|
|
const postRes = await newAgent.api.app.bsky.feed.post.create(
|
|
{ repo: alice },
|
|
{
|
|
text: 'new pds!',
|
|
createdAt: new Date().toISOString(),
|
|
},
|
|
)
|
|
const postUri = new AtUri(postRes.uri)
|
|
const fetchedPost = await newAgent.api.app.bsky.feed.post.get({
|
|
repo: postUri.hostname,
|
|
rkey: postUri.rkey,
|
|
})
|
|
expect(fetchedPost.value.text).toEqual('new pds!')
|
|
const statusRes5 = await newAgent.com.atproto.server.checkAccountStatus()
|
|
expect(statusRes5.data.indexedRecords).toBe(104)
|
|
})
|
|
})
|