process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import rndstr from 'rndstr'; import { loadConfig } from '@/config.js'; import { User, UsersRepository } from '@/models/index.js'; import { jobQueue } from '@/boot/common.js'; import { uploadFile, signup, startServer, initTestDb, api, sleep } from '../utils.js'; import type { INestApplicationContext } from '@nestjs/common'; describe('Account Move', () => { let app: INestApplicationContext; let url: URL; let root: any; let alice: any; let bob: any; let carol: any; let dave: any; let eve: any; let frank: any; let Users: UsersRepository; beforeAll(async () => { app = await startServer(); await jobQueue(); const config = loadConfig(); url = new URL(config.url); const connection = await initTestDb(false); root = await signup({ username: 'root' }); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); carol = await signup({ username: 'carol' }); dave = await signup({ username: 'dave' }); eve = await signup({ username: 'eve' }); frank = await signup({ username: 'frank' }); Users = connection.getRepository(User); }, 1000 * 60 * 2); afterAll(async () => { await app.close(); }); describe('Create Alias', () => { afterEach(async () => { await Users.update(bob.id, { alsoKnownAs: null }); }, 1000 * 10); test('Able to create an alias', async () => { await api('/i/update', { alsoKnownAs: [`@alice@${url.hostname}`], }, bob); const newBob = await Users.findOneByOrFail({ id: bob.id }); assert.strictEqual(newBob.alsoKnownAs?.length, 1); assert.strictEqual(newBob.alsoKnownAs[0], `${url.origin}/users/${alice.id}`); }); test('Able to set remote user (but may fail)', async () => { const res = await api('/i/update', { alsoKnownAs: ['@syuilo@example.com'], }, bob); assert.strictEqual(res.status, 400); assert.strictEqual(res.body.error.code, 'NO_SUCH_USER'); assert.strictEqual(res.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5'); }); test('Unable to add duplicated aliases to alsoKnownAs', async () => { const res = await api('/i/update', { alsoKnownAs: [`@alice@${url.hostname}`, `@alice@${url.hostname}`], }, bob); assert.strictEqual(res.status, 400); assert.strictEqual(res.body.error.code, 'INVALID_PARAM'); assert.strictEqual(res.body.error.id, '3d81ceae-475f-4600-b2a8-2bc116157532'); }); test('Unable to add itself', async () => { const res = await api('/i/update', { alsoKnownAs: [`@bob@${url.hostname}`], }, bob); assert.strictEqual(res.status, 400); assert.strictEqual(res.body.error.code, 'FORBIDDEN_TO_SET_YOURSELF'); assert.strictEqual(res.body.error.id, '25c90186-4ab0-49c8-9bba-a1fa6c202ba4'); }); test('Unable to add a nonexisting local account to alsoKnownAs', async () => { const res = await api('/i/update', { alsoKnownAs: [`@nonexist@${url.hostname}`], }, bob); assert.strictEqual(res.status, 400); assert.strictEqual(res.body.error.code, 'NO_SUCH_USER'); assert.strictEqual(res.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5'); }); test('Able to add two existing local account to alsoKnownAs', async () => { await api('/i/update', { alsoKnownAs: [`@alice@${url.hostname}`, `@carol@${url.hostname}`], }, bob); const newBob = await Users.findOneByOrFail({ id: bob.id }); assert.strictEqual(newBob.alsoKnownAs?.length, 2); assert.strictEqual(newBob.alsoKnownAs[0], `${url.origin}/users/${alice.id}`); assert.strictEqual(newBob.alsoKnownAs[1], `${url.origin}/users/${carol.id}`); }); test('Able to properly overwrite alsoKnownAs', async () => { await api('/i/update', { alsoKnownAs: [`@alice@${url.hostname}`], }, bob); await api('/i/update', { alsoKnownAs: [`@carol@${url.hostname}`, `@dave@${url.hostname}`], }, bob); const newBob = await Users.findOneByOrFail({ id: bob.id }); assert.strictEqual(newBob.alsoKnownAs?.length, 2); assert.strictEqual(newBob.alsoKnownAs[0], `${url.origin}/users/${carol.id}`); assert.strictEqual(newBob.alsoKnownAs[1], `${url.origin}/users/${dave.id}`); }); test('Unable to create an alias without the second @', async () => { const res1 = await api('/i/update', { alsoKnownAs: ['@alice'], }, bob); assert.strictEqual(res1.status, 400); assert.strictEqual(res1.body.error.code, 'NO_SUCH_USER'); assert.strictEqual(res1.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5'); const res2 = await api('/i/update', { alsoKnownAs: ['alice'], }, bob); assert.strictEqual(res2.status, 400); assert.strictEqual(res2.body.error.code, 'NO_SUCH_USER'); assert.strictEqual(res2.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5'); }); }); describe('Local to Local', () => { let antennaId = ''; beforeAll(async () => { await api('/i/update', { alsoKnownAs: [`@alice@${url.hostname}`], }, root); const list = await api('/users/lists/create', { name: rndstr('0-9a-z', 8), }, root); await api('/users/lists/push', { listId: list.body.id, userId: alice.id, }, root); await api('/following/create', { userId: root.id, }, alice); await api('/following/create', { userId: eve.id, }, alice); const antenna = await api('/antennas/create', { name: rndstr('0-9a-z', 8), src: 'home', keywords: [rndstr('0-9a-z', 8)], excludeKeywords: [], users: [], caseSensitive: false, withReplies: false, withFile: false, notify: false, }, alice); antennaId = antenna.body.id; await api('/i/update', { alsoKnownAs: [`@alice@${url.hostname}`], }, bob); await api('/following/create', { userId: alice.id, }, carol); await api('/mute/create', { userId: alice.id, }, dave); await api('/blocking/create', { userId: alice.id, }, dave); await api('/following/create', { userId: eve.id, }, dave); await api('/following/create', { userId: dave.id, }, eve); await api('/i/update', { isLocked: true, }, frank); await api('/following/create', { userId: frank.id, }, alice); await api('/following/requests/accept', { userId: alice.id, }, frank); }, 1000 * 10); test('Prohibit the root account from moving', async () => { const res = await api('/i/move', { moveToAccount: `@bob@${url.hostname}`, }, root); assert.strictEqual(res.status, 400); assert.strictEqual(res.body.error.code, 'NOT_ROOT_FORBIDDEN'); assert.strictEqual(res.body.error.id, '4362e8dc-731f-4ad8-a694-be2a88922a24'); }); test('Unable to move to a nonexisting local account', async () => { const res = await api('/i/move', { moveToAccount: `@nonexist@${url.hostname}`, }, alice); assert.strictEqual(res.status, 400); assert.strictEqual(res.body.error.code, 'NO_SUCH_USER'); assert.strictEqual(res.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5'); }); test('Unable to move if alsoKnownAs is invalid', async () => { const res = await api('/i/move', { moveToAccount: `@carol@${url.hostname}`, }, alice); assert.strictEqual(res.status, 400); assert.strictEqual(res.body.error.code, 'REMOTE_ACCOUNT_FORBIDS'); assert.strictEqual(res.body.error.id, 'b5c90186-4ab0-49c8-9bba-a1f766282ba4'); }); test('Relationships have been properly migrated', async () => { const move = await api('/i/move', { moveToAccount: `@bob@${url.hostname}`, }, alice); assert.strictEqual(move.status, 200); await sleep(1000 * 1); // wait for jobs to finish const followings = await api('/users/following', { userId: carol.id, }, carol); assert.strictEqual(followings.status, 200); assert.strictEqual(followings.body.length, 2); assert.strictEqual(followings.body[0].followeeId, bob.id); assert.strictEqual(followings.body[1].followeeId, alice.id); const blockings = await api('/blocking/list', {}, dave); assert.strictEqual(blockings.status, 200); assert.strictEqual(blockings.body.length, 2); assert.strictEqual(blockings.body[0].blockeeId, bob.id); assert.strictEqual(blockings.body[1].blockeeId, alice.id); const mutings = await api('/mute/list', {}, dave); assert.strictEqual(mutings.status, 200); assert.strictEqual(mutings.body.length, 1); assert.strictEqual(mutings.body[0].muteeId, bob.id); const lists = await api('/users/lists/list', {}, root); assert.strictEqual(lists.status, 200); assert.strictEqual(lists.body[0].userIds.length, 2); assert.ok(lists.body[0].userIds.find((id: string) => id === bob.id)); assert.ok(lists.body[0].userIds.find((id: string) => id === alice.id)); }); test('Unable to move if the destination account has already moved.', async () => { await api('/i/move', { moveToAccount: `@bob@${url.hostname}`, }, alice); const newAlice = await Users.findOneByOrFail({ id: alice.id }); assert.strictEqual(newAlice.movedToUri, `${url.origin}/users/${bob.id}`); assert.strictEqual(newAlice.alsoKnownAs?.length, 1); assert.strictEqual(newAlice.alsoKnownAs[0], `${url.origin}/users/${bob.id}`); const res = await api('/i/move', { moveToAccount: `@alice@${url.hostname}`, }, bob); assert.strictEqual(res.status, 400); assert.strictEqual(res.body.error.code, 'REMOTE_ACCOUNT_FORBIDS'); assert.strictEqual(res.body.error.id, 'b5c90186-4ab0-49c8-9bba-a1f766282ba4'); }); test('Follow and follower counts are properly adjusted', async () => { await api('/following/create', { userId: alice.id, }, eve); const newAlice = await Users.findOneByOrFail({ id: alice.id }); const newCarol = await Users.findOneByOrFail({ id: carol.id }); let newEve = await Users.findOneByOrFail({ id: eve.id }); assert.strictEqual(newAlice.movedToUri, `${url.origin}/users/${bob.id}`); assert.strictEqual(newAlice.followingCount, 0); assert.strictEqual(newAlice.followersCount, 0); assert.strictEqual(newCarol.followingCount, 1); assert.strictEqual(newEve.followingCount, 1); assert.strictEqual(newEve.followersCount, 1); await api('/following/delete', { userId: alice.id, }, eve); newEve = await Users.findOneByOrFail({ id: eve.id }); assert.strictEqual(newEve.followingCount, 1); assert.strictEqual(newEve.followersCount, 1); }); test('A locked account automatically accept the follow request if it had already accepted the old account.', async () => { await api('/following/create', { userId: frank.id, }, bob); const followers = await api('/users/followers', { userId: frank.id, }, frank); assert.strictEqual(followers.status, 200); assert.strictEqual(followers.body.length, 2); assert.strictEqual(followers.body[0].followerId, bob.id); }); test.each([ '/antennas/create', '/channels/create', '/channels/favorite', '/channels/follow', '/channels/unfavorite', '/channels/unfollow', '/clips/add-note', '/clips/create', '/clips/favorite', '/clips/remove-note', '/clips/unfavorite', '/clips/update', '/drive/files/upload-from-url', '/flash/create', '/flash/like', '/flash/unlike', '/flash/update', '/following/create', '/gallery/posts/create', '/gallery/posts/like', '/gallery/posts/unlike', '/gallery/posts/update', '/i/update', '/i/move', '/notes/create', '/notes/polls/vote', '/notes/reactions/create', '/pages/create', '/pages/like', '/pages/unlike', '/pages/update', '/users/lists/create', '/users/lists/pull', '/users/lists/push', '/users/lists/update', ])('Prohibit access after moving: %s', async (endpoint) => { const res = await api(endpoint, {}, alice); assert.strictEqual(res.status, 403); assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED'); assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31'); }); test('Prohibit access after moving: /antennas/update', async () => { const res = await api('/antennas/update', { antennaId, name: rndstr('0-9a-z', 8), src: 'users', keywords: [rndstr('0-9a-z', 8)], excludeKeywords: [], users: [eve.id], caseSensitive: false, withReplies: false, withFile: false, notify: false, }, alice); assert.strictEqual(res.status, 403); assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED'); assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31'); }); test('Prohibit access after moving: /drive/files/create', async () => { const res = await uploadFile(alice); assert.strictEqual(res.status, 403); assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED'); assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31'); }); }); });