From cd7f7271ca5595cae95f6fb0280fac9dee77d751 Mon Sep 17 00:00:00 2001 From: おさむのひと <46447427+samunohito@users.noreply.github.com> Date: Fri, 19 Apr 2024 15:22:23 +0900 Subject: enhance: 新しいコンディショナルロール条件の実装 (#13732) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance: 新しいコンディショナルロールの実装 * fix: CHANGELOG.md --- packages/backend/test/unit/RoleService.ts | 498 ++++++++++++++++++++++++++---- 1 file changed, 432 insertions(+), 66 deletions(-) (limited to 'packages/backend/test/unit/RoleService.ts') diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts index 19d03570e0..ec441735d7 100644 --- a/packages/backend/test/unit/RoleService.ts +++ b/packages/backend/test/unit/RoleService.ts @@ -3,6 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { UserEntityService } from '@/core/entities/UserEntityService.js'; + process.env.NODE_ENV = 'test'; import { jest } from '@jest/globals'; @@ -20,6 +22,7 @@ import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { RoleCondFormulaValue } from '@/models/Role.js'; import { sleep } from '../utils.js'; import type { TestingModule } from '@nestjs/testing'; import type { MockFunctionMetadata } from 'jest-mock'; @@ -52,12 +55,26 @@ describe('RoleService', () => { id: genAidx(Date.now()), updatedAt: new Date(), lastUsedAt: new Date(), + name: '', description: '', ...data, }) .then(x => rolesRepository.findOneByOrFail(x.identifiers[0])); } + function createConditionalRole(condFormula: RoleCondFormulaValue, data: Partial = {}) { + return createRole({ + name: `[conditional] ${condFormula.type}`, + target: 'conditional', + condFormula: condFormula, + ...data, + }); + } + + function aidx() { + return genAidx(Date.now()); + } + beforeEach(async () => { clock = lolex.install({ now: new Date(), @@ -73,6 +90,7 @@ describe('RoleService', () => { CacheService, IdService, GlobalEventService, + UserEntityService, { provide: NotificationService, useFactory: () => ({ @@ -209,15 +227,9 @@ describe('RoleService', () => { expect(result.driveCapacityMb).toBe(100); }); - test('conditional role', async () => { - const user1 = await createUser({ - id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)), - }); - const user2 = await createUser({ - id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)), - followersCount: 10, - }); - await createRole({ + test('expired role', async () => { + const user = await createUser(); + const role = await createRole({ name: 'a', policies: { canManageCustomEmojis: { @@ -226,35 +238,133 @@ describe('RoleService', () => { value: true, }, }, - target: 'conditional', - condFormula: { - id: '232a4221-9816-49a6-a967-ae0fac52ec5e', - type: 'and', - values: [{ - id: '2a37ef43-2d93-4c4d-87f6-f2fdb7d9b530', - type: 'followersMoreThanOrEq', - value: 10, - }, { - id: '1bd67839-b126-4f92-bad0-4e285dab453b', - type: 'createdMoreThan', - sec: 60 * 60 * 24 * 7, - }], - }, }); - + await roleService.assign(user.id, role.id, new Date(Date.now() + (1000 * 60 * 60 * 24))); metaService.fetch.mockResolvedValue({ policies: { canManageCustomEmojis: false, }, } as any); - const user1Policies = await roleService.getUserPolicies(user1.id); - const user2Policies = await roleService.getUserPolicies(user2.id); - expect(user1Policies.canManageCustomEmojis).toBe(false); - expect(user2Policies.canManageCustomEmojis).toBe(true); + const result = await roleService.getUserPolicies(user.id); + expect(result.canManageCustomEmojis).toBe(true); + + clock.tick('25:00:00'); + + const resultAfter25h = await roleService.getUserPolicies(user.id); + expect(resultAfter25h.canManageCustomEmojis).toBe(false); + + await roleService.assign(user.id, role.id); + + // ストリーミング経由で反映されるまでちょっと待つ + clock.uninstall(); + await sleep(100); + + const resultAfter25hAgain = await roleService.getUserPolicies(user.id); + expect(resultAfter25hAgain.canManageCustomEmojis).toBe(true); + }); + }); + + describe('conditional role', () => { + test('~かつ~', async () => { + const [user1, user2, user3, user4] = await Promise.all([ + createUser({ isBot: true, isCat: false, isSuspended: false }), + createUser({ isBot: false, isCat: true, isSuspended: false }), + createUser({ isBot: true, isCat: true, isSuspended: false }), + createUser({ isBot: false, isCat: false, isSuspended: true }), + ]); + const role1 = await createConditionalRole({ + id: aidx(), + type: 'isBot', + }); + const role2 = await createConditionalRole({ + id: aidx(), + type: 'isCat', + }); + const role3 = await createConditionalRole({ + id: aidx(), + type: 'isSuspended', + }); + const role4 = await createConditionalRole({ + id: aidx(), + type: 'and', + values: [role1.condFormula, role2.condFormula], + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + const actual4 = await roleService.getUserRoles(user4.id); + expect(actual1.some(r => r.id === role4.id)).toBe(false); + expect(actual2.some(r => r.id === role4.id)).toBe(false); + expect(actual3.some(r => r.id === role4.id)).toBe(true); + expect(actual4.some(r => r.id === role4.id)).toBe(false); + }); + + test('~または~', async () => { + const [user1, user2, user3, user4] = await Promise.all([ + createUser({ isBot: true, isCat: false, isSuspended: false }), + createUser({ isBot: false, isCat: true, isSuspended: false }), + createUser({ isBot: true, isCat: true, isSuspended: false }), + createUser({ isBot: false, isCat: false, isSuspended: true }), + ]); + const role1 = await createConditionalRole({ + id: aidx(), + type: 'isBot', + }); + const role2 = await createConditionalRole({ + id: aidx(), + type: 'isCat', + }); + const role3 = await createConditionalRole({ + id: aidx(), + type: 'isSuspended', + }); + const role4 = await createConditionalRole({ + id: aidx(), + type: 'or', + values: [role1.condFormula, role2.condFormula], + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + const actual4 = await roleService.getUserRoles(user4.id); + expect(actual1.some(r => r.id === role4.id)).toBe(true); + expect(actual2.some(r => r.id === role4.id)).toBe(true); + expect(actual3.some(r => r.id === role4.id)).toBe(true); + expect(actual4.some(r => r.id === role4.id)).toBe(false); }); - test('コンディショナルロール: マニュアルロールにアサイン済み', async () => { + test('~ではない', async () => { + const [user1, user2, user3] = await Promise.all([ + createUser({ isBot: true, isCat: false, isSuspended: false }), + createUser({ isBot: false, isCat: true, isSuspended: false }), + createUser({ isBot: true, isCat: true, isSuspended: false }), + ]); + const role1 = await createConditionalRole({ + id: aidx(), + type: 'isBot', + }); + const role2 = await createConditionalRole({ + id: aidx(), + type: 'isCat', + }); + const role4 = await createConditionalRole({ + id: aidx(), + type: 'not', + value: role1.condFormula, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role4.id)).toBe(false); + expect(actual2.some(r => r.id === role4.id)).toBe(true); + expect(actual3.some(r => r.id === role4.id)).toBe(false); + }); + + test('マニュアルロールにアサイン済み', async () => { const [user1, user2, role1] = await Promise.all([ createUser(), createUser(), @@ -262,15 +372,10 @@ describe('RoleService', () => { name: 'manual role', }), ]); - const role2 = await createRole({ - name: 'conditional role', - target: 'conditional', - condFormula: { - // idはバックエンドのロジックに必要ない? - id: 'bdc612bd-9d54-4675-ae83-0499c82ea670', - type: 'roleAssignedTo', - roleId: role1.id, - }, + const role2 = await createConditionalRole({ + id: aidx(), + type: 'roleAssignedTo', + roleId: role1.id, }); await roleService.assign(user2.id, role1.id); @@ -282,41 +387,302 @@ describe('RoleService', () => { expect(u2role.some(r => r.id === role2.id)).toBe(true); }); - test('expired role', async () => { - const user = await createUser(); - const role = await createRole({ - name: 'a', - policies: { - canManageCustomEmojis: { - useDefault: false, - priority: 0, - value: true, - }, - }, + test('ローカルユーザのみ', async () => { + const [user1, user2] = await Promise.all([ + createUser({ host: null }), + createUser({ host: 'example.com' }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'isLocal', }); - await roleService.assign(user.id, role.id, new Date(Date.now() + (1000 * 60 * 60 * 24))); - metaService.fetch.mockResolvedValue({ - policies: { - canManageCustomEmojis: false, - }, - } as any); - const result = await roleService.getUserPolicies(user.id); - expect(result.canManageCustomEmojis).toBe(true); + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + expect(actual1.some(r => r.id === role.id)).toBe(true); + expect(actual2.some(r => r.id === role.id)).toBe(false); + }); - clock.tick('25:00:00'); + test('リモートユーザのみ', async () => { + const [user1, user2] = await Promise.all([ + createUser({ host: null }), + createUser({ host: 'example.com' }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'isRemote', + }); - const resultAfter25h = await roleService.getUserPolicies(user.id); - expect(resultAfter25h.canManageCustomEmojis).toBe(false); + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + }); - await roleService.assign(user.id, role.id); + test('サスペンド済みユーザである', async () => { + const [user1, user2] = await Promise.all([ + createUser({ isSuspended: false }), + createUser({ isSuspended: true }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'isSuspended', + }); - // ストリーミング経由で反映されるまでちょっと待つ - clock.uninstall(); - await sleep(100); + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + }); - const resultAfter25hAgain = await roleService.getUserPolicies(user.id); - expect(resultAfter25hAgain.canManageCustomEmojis).toBe(true); + test('鍵アカウントユーザである', async () => { + const [user1, user2] = await Promise.all([ + createUser({ isLocked: false }), + createUser({ isLocked: true }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'isLocked', + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + }); + + test('botユーザである', async () => { + const [user1, user2] = await Promise.all([ + createUser({ isBot: false }), + createUser({ isBot: true }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'isBot', + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + }); + + test('猫である', async () => { + const [user1, user2] = await Promise.all([ + createUser({ isCat: false }), + createUser({ isCat: true }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'isCat', + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + }); + + test('「ユーザを見つけやすくする」が有効なアカウント', async () => { + const [user1, user2] = await Promise.all([ + createUser({ isExplorable: false }), + createUser({ isExplorable: true }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'isExplorable', + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + }); + + test('ユーザが作成されてから指定期間経過した', async () => { + const base = new Date(); + base.setMinutes(base.getMinutes() - 5); + + const d1 = new Date(base); + const d2 = new Date(base); + const d3 = new Date(base); + d1.setSeconds(d1.getSeconds() - 1); + d3.setSeconds(d3.getSeconds() + 1); + + const [user1, user2, user3] = await Promise.all([ + // 4:59 + createUser({ id: genAidx(d1.getTime()) }), + // 5:00 + createUser({ id: genAidx(d2.getTime()) }), + // 5:01 + createUser({ id: genAidx(d3.getTime()) }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'createdLessThan', + // 5 minutes + sec: 300, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(false); + expect(actual3.some(r => r.id === role.id)).toBe(true); + }); + + test('ユーザが作成されてから指定期間経っていない', async () => { + const base = new Date(); + base.setMinutes(base.getMinutes() - 5); + + const d1 = new Date(base); + const d2 = new Date(base); + const d3 = new Date(base); + d1.setSeconds(d1.getSeconds() - 1); + d3.setSeconds(d3.getSeconds() + 1); + + const [user1, user2, user3] = await Promise.all([ + // 4:59 + createUser({ id: genAidx(d1.getTime()) }), + // 5:00 + createUser({ id: genAidx(d2.getTime()) }), + // 5:01 + createUser({ id: genAidx(d3.getTime()) }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'createdMoreThan', + // 5 minutes + sec: 300, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role.id)).toBe(true); + expect(actual2.some(r => r.id === role.id)).toBe(false); + expect(actual3.some(r => r.id === role.id)).toBe(false); + }); + + test('フォロワー数が指定値以下', async () => { + const [user1, user2, user3] = await Promise.all([ + createUser({ followersCount: 99 }), + createUser({ followersCount: 100 }), + createUser({ followersCount: 101 }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'followersLessThanOrEq', + value: 100, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role.id)).toBe(true); + expect(actual2.some(r => r.id === role.id)).toBe(true); + expect(actual3.some(r => r.id === role.id)).toBe(false); + }); + + test('フォロワー数が指定値以下', async () => { + const [user1, user2, user3] = await Promise.all([ + createUser({ followersCount: 99 }), + createUser({ followersCount: 100 }), + createUser({ followersCount: 101 }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'followersMoreThanOrEq', + value: 100, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + expect(actual3.some(r => r.id === role.id)).toBe(true); + }); + + test('フォロー数が指定値以下', async () => { + const [user1, user2, user3] = await Promise.all([ + createUser({ followingCount: 99 }), + createUser({ followingCount: 100 }), + createUser({ followingCount: 101 }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'followingLessThanOrEq', + value: 100, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role.id)).toBe(true); + expect(actual2.some(r => r.id === role.id)).toBe(true); + expect(actual3.some(r => r.id === role.id)).toBe(false); + }); + + test('フォロー数が指定値以上', async () => { + const [user1, user2, user3] = await Promise.all([ + createUser({ followingCount: 99 }), + createUser({ followingCount: 100 }), + createUser({ followingCount: 101 }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'followingMoreThanOrEq', + value: 100, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + expect(actual3.some(r => r.id === role.id)).toBe(true); + }); + + test('ノート数が指定値以下', async () => { + const [user1, user2, user3] = await Promise.all([ + createUser({ notesCount: 9 }), + createUser({ notesCount: 10 }), + createUser({ notesCount: 11 }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'notesLessThanOrEq', + value: 10, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role.id)).toBe(true); + expect(actual2.some(r => r.id === role.id)).toBe(true); + expect(actual3.some(r => r.id === role.id)).toBe(false); + }); + + test('ノート数が指定値以上', async () => { + const [user1, user2, user3] = await Promise.all([ + createUser({ notesCount: 9 }), + createUser({ notesCount: 10 }), + createUser({ notesCount: 11 }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'notesMoreThanOrEq', + value: 10, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + expect(actual3.some(r => r.id === role.id)).toBe(true); }); }); -- cgit v1.2.3-freya