diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts index f9ad66c119..757d86dbec 100644 --- a/packages/backend/test/e2e/timelines.ts +++ b/packages/backend/test/e2e/timelines.ts @@ -11,7 +11,7 @@ import { setTimeout } from 'node:timers/promises'; import { Redis } from 'ioredis'; import { api, post, randomString, sendEnvUpdateRequest, signup, uploadUrl, role } from '../utils.js'; import { loadConfig } from '@/config.js'; -import { SignupResponse, Role } from 'misskey-js/entities.js'; +import { SignupResponse, Role, Note } from 'misskey-js/entities.js'; function genHost() { return randomString() + '.example.com'; @@ -654,26 +654,45 @@ describe('Timelines', () => { assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 0); }); - test('凍結: 凍結後に凍結されたユーザーのノートは見えなくなる', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + describe('凍結', async () => { + let alice: SignupResponse, bob: SignupResponse, carol: SignupResponse; + let aliceNote: Note, bobNote: Note, carolNote: Note; - await api('following/create', { userId: bob.id }, alice); - await api('following/create', { userId: carol.id }, alice); - const aliceNote = await post(alice, { text: 'hi' }); - const bobNote = await post(bob, { text: 'yo' }); - const carolNote = await post(carol, { text: 'kon\'nichiwa' }); + beforeAll(async () => { + [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await waitForPushToTl(); + await api('following/create', { userId: bob.id }, alice); + await api('following/create', { userId: carol.id }, alice); + aliceNote = await post(alice, { text: 'hi' }); + bobNote = await post(bob, { text: 'yo' }); + carolNote = await post(carol, { text: 'kon\'nichiwa' }); - await api('admin/suspend-user', { userId: carol.id }, root); + await waitForPushToTl(); + }); - await setTimeout(250); + test('凍結後に凍結されたユーザーのノートは見えなくなる', async () => { + await api('admin/suspend-user', { userId: carol.id }, root); + await setTimeout(100); - const res = await api('notes/timeline', { limit: 100 }, alice); - assert.strictEqual(res.body.length, 2); - assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + const res = await api('notes/timeline', { limit: 100 }, alice); + assert.strictEqual(res.body.length, 2); + assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('凍結解除後に凍結されていたユーザーのノートは見えるようになる', async () => { + await api('admin/unsuspend-user', { userId: carol.id }, root); + await setTimeout(100); + + const res = await api('notes/timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.length, 3); + assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), true); + assert.strictEqual(res.body.find(note => note.id === carolNote.id)?.text, 'kon\'nichiwa'); + }); }); }); @@ -912,25 +931,43 @@ describe('Timelines', () => { assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); }, 1000 * 10); - test('凍結: 凍結後に凍結されたユーザーのノートは見えなくなる', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + describe('凍結', async () => { + let alice: SignupResponse, bob: SignupResponse, carol: SignupResponse; + let aliceNote: Note, bobNote: Note, carolNote: Note; - await api('following/create', { userId: bob.id }, alice); - await api('following/create', { userId: carol.id }, alice); - const aliceNote = await post(alice, { text: 'hi' }); - const bobNote = await post(bob, { text: 'yo' }); - const carolNote = await post(carol, { text: 'kon\'nichiwa' }); + beforeAll(async () => { + [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await waitForPushToTl(); + aliceNote = await post(alice, { text: 'hi' }); + bobNote = await post(bob, { text: 'yo' }); + carolNote = await post(carol, { text: 'kon\'nichiwa' }); - await api('admin/suspend-user', { userId: carol.id }, root); + await waitForPushToTl(); + }); - await setTimeout(250); + test('凍結後に凍結されたユーザーのノートは見えなくなる', async () => { + await api('admin/suspend-user', { userId: carol.id }, root); + await setTimeout(100); - const res = await api('notes/local-timeline', { limit: 100 }, alice); - assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + const res = await api('notes/local-timeline', { limit: 100 }, alice); + assert.strictEqual(res.body.length, 2); + assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + }); + + test('凍結解除後に凍結されていたユーザーのノートは見えるようになる', async () => { + await api('admin/unsuspend-user', { userId: carol.id }, root); + await setTimeout(100); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.length, 3); + assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), true); + assert.strictEqual(res.body.find(note => note.id === carolNote.id)?.text, 'kon\'nichiwa'); + }); }); }); @@ -1152,25 +1189,61 @@ describe('Timelines', () => { assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true); }, 1000 * 10); - test('凍結: 凍結後に凍結されたユーザーのノートは見えなくなる', async () => { - const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + describe('凍結', async () => { + /* + * bob = 未フォローのローカルユーザー (凍結対象でない) + * carol = 未フォローのローカルユーザー (凍結対象) + * dave = フォローしているローカルユーザー (凍結対象) + * elle = フォローしているリモートユーザー (凍結対象) + */ + let alice: SignupResponse, bob: SignupResponse, carol: SignupResponse, dave: SignupResponse, elle: SignupResponse; + let aliceNote: Note, bobNote: Note, carolNote: Note, daveNote: Note, elleNote: Note; - await api('following/create', { userId: bob.id }, alice); - await api('following/create', { userId: carol.id }, alice); - const aliceNote = await post(alice, { text: 'hi' }); - const bobNote = await post(bob, { text: 'yo' }); - const carolNote = await post(carol, { text: 'kon\'nichiwa' }); + beforeAll(async () => { + [alice, bob, carol, dave, elle] = await Promise.all([signup(), signup(), signup(), signup(), signup({ host: genHost() })]); - await waitForPushToTl(); + aliceNote = await post(alice, { text: 'hi' }); + bobNote = await post(bob, { text: 'yo' }); + carolNote = await post(carol, { text: 'kon\'nichiwa' }); + daveNote = await post(dave, { text: 'hello' }); + elleNote = await post(elle, { text: 'hi there' }); - await api('admin/suspend-user', { userId: carol.id }, root); + await api('following/create', { userId: dave.id }, alice); + await api('following/create', { userId: elle.id }, alice); - await setTimeout(250); + await waitForPushToTl(); + }); - const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); - assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); - assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); - assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + test('凍結後に凍結されたユーザーのノートは見えなくなる', async () => { + await api('admin/suspend-user', { userId: carol.id }, root); + await api('admin/suspend-user', { userId: dave.id }, root); + await api('admin/suspend-user', { userId: elle.id }, root); + await setTimeout(250); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + assert.strictEqual(res.body.length, 2); + assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), false); + assert.strictEqual(res.body.some(note => note.id === daveNote.id), false); + assert.strictEqual(res.body.some(note => note.id === elleNote.id), false); + }); + + test('凍結解除後に凍結されていたユーザーのノートは見えるようになる', async () => { + await api('admin/unsuspend-user', { userId: carol.id }, root); + await api('admin/unsuspend-user', { userId: dave.id }, root); + await api('admin/unsuspend-user', { userId: elle.id }, root); + await setTimeout(250); + + const res = await api('notes/local-timeline', { limit: 100 }, alice); + + assert.strictEqual(res.body.length, 5); + assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true); + assert.strictEqual(res.body.some(note => note.id === bobNote.id), true); + assert.strictEqual(res.body.some(note => note.id === carolNote.id), true); + assert.strictEqual(res.body.some(note => note.id === daveNote.id), true); + assert.strictEqual(res.body.some(note => note.id === elleNote.id), true); + }); }); }); diff --git a/packages/backend/test/unit/UserSuspendService.ts b/packages/backend/test/unit/UserSuspendService.ts index 89edc6d116..ce8d35f408 100644 --- a/packages/backend/test/unit/UserSuspendService.ts +++ b/packages/backend/test/unit/UserSuspendService.ts @@ -7,7 +7,7 @@ process.env.NODE_ENV = 'test'; import { jest } from '@jest/globals'; import { Test } from '@nestjs/testing'; -import { DataSource } from 'typeorm'; +import { setTimeout } from 'node:timers/promises'; import type { TestingModule } from '@nestjs/testing'; import { GlobalModule } from '@/GlobalModule.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; @@ -26,10 +26,6 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; -export async function sleep(ms = 250): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} - describe('UserSuspendService', () => { let app: TestingModule; let userSuspendService: UserSuspendService; @@ -169,7 +165,7 @@ describe('UserSuspendService', () => { await createFollowing(user, followee2); await userSuspendService.suspend(user, moderator); - await sleep(); + await setTimeout(250); // フォロー関係が論理削除されているかチェック const followings = await followingsRepository.find({ @@ -187,10 +183,10 @@ describe('UserSuspendService', () => { const moderator = await createUser(); await userSuspendService.suspend(user, moderator); - await sleep(); + await setTimeout(250); // 内部イベントが発行されているかチェック(非同期処理のため少し待つ) - await new Promise(resolve => setTimeout(resolve, 100)); + await setTimeout(100); expect(globalEventService.publishInternalEvent).toHaveBeenCalledWith( 'userChangeSuspendedState', @@ -205,7 +201,7 @@ describe('UserSuspendService', () => { const moderator = await createUser(); await userSuspendService.unsuspend(user, moderator); - await sleep(); + await setTimeout(250); // ユーザーの凍結が解除されているかチェック const unsuspendedUser = await usersRepository.findOneBy({ id: user.id }); @@ -230,7 +226,7 @@ describe('UserSuspendService', () => { await createFollowing(user, followee2, { isFollowerSuspended: true }); await userSuspendService.unsuspend(user, moderator); - await sleep(); + await setTimeout(250); // フォロー関係が復元されているかチェック const followings = await followingsRepository.find({ @@ -248,10 +244,10 @@ describe('UserSuspendService', () => { const moderator = await createUser(); await userSuspendService.unsuspend(user, moderator); - await sleep(); + await setTimeout(250); // 内部イベントが発行されているかチェック(非同期処理のため少し待つ) - await new Promise(resolve => setTimeout(resolve, 100)); + await setTimeout(100); expect(globalEventService.publishInternalEvent).toHaveBeenCalledWith( 'userChangeSuspendedState', @@ -282,7 +278,7 @@ describe('UserSuspendService', () => { // 凍結 await userSuspendService.suspend(user, moderator); - await sleep(); + await setTimeout(250); // 凍結後の状態確認 followings = await followingsRepository.find({ @@ -296,7 +292,7 @@ describe('UserSuspendService', () => { // 凍結解除 const suspendedUser = await usersRepository.findOneByOrFail({ id: user.id }); await userSuspendService.unsuspend(suspendedUser, moderator); - await sleep(); + await setTimeout(250); // 凍結解除後の状態確認 followings = await followingsRepository.find({ @@ -320,7 +316,7 @@ describe('UserSuspendService', () => { apRendererService.addContext.mockReturnValue({ '@context': '...', type: 'Delete' } as any); await userSuspendService.suspend(localUser, moderator); - await sleep(); + await setTimeout(250); // ActivityPub配信が呼ばれているかチェック expect(userEntityService.isLocalUser).toHaveBeenCalledWith(localUser); @@ -339,7 +335,7 @@ describe('UserSuspendService', () => { apRendererService.addContext.mockReturnValue({ '@context': '...', type: 'Undo' } as any); await userSuspendService.unsuspend(localUser, moderator); - await sleep(); + await setTimeout(250); // ActivityPub配信が呼ばれているかチェック expect(userEntityService.isLocalUser).toHaveBeenCalledWith(localUser); @@ -355,7 +351,7 @@ describe('UserSuspendService', () => { userEntityService.isLocalUser.mockReturnValue(false); await userSuspendService.suspend(remoteUser, moderator); - await sleep(); + await setTimeout(250); // ActivityPub配信が呼ばれていないことをチェック expect(userEntityService.isLocalUser).toHaveBeenCalledWith(remoteUser);