summaryrefslogtreecommitdiff
path: root/packages/backend/test/unit
diff options
context:
space:
mode:
authorおさむのひと <46447427+samunohito@users.noreply.github.com>2024-10-11 20:59:36 +0900
committerGitHub <noreply@github.com>2024-10-11 20:59:36 +0900
commita2cd6a7709ffacfabb738deac22cb0fd1eb7d493 (patch)
treedb473d5f5c9339db9798073c54409b3acb0cb848 /packages/backend/test/unit
parentfix: admin/emoji/update で不正なエラーが発生する (#14750) (diff)
downloadsharkey-a2cd6a7709ffacfabb738deac22cb0fd1eb7d493.tar.gz
sharkey-a2cd6a7709ffacfabb738deac22cb0fd1eb7d493.tar.bz2
sharkey-a2cd6a7709ffacfabb738deac22cb0fd1eb7d493.zip
feat(backend): 7日間運営のアクティビティがないサーバを自動的に招待制にする (#14746)
* feat(backend): 7日間運営のアクティビティがないサーバを自動的に招待制にする * fix RoleService. * fix * fix * fix * add test and fix * fix * fix CHANGELOG.md * fix test
Diffstat (limited to 'packages/backend/test/unit')
-rw-r--r--packages/backend/test/unit/RoleService.ts150
-rw-r--r--packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts235
2 files changed, 367 insertions, 18 deletions
diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts
index ef80d25f81..9c1b1008d6 100644
--- a/packages/backend/test/unit/RoleService.ts
+++ b/packages/backend/test/unit/RoleService.ts
@@ -10,6 +10,8 @@ import { jest } from '@jest/globals';
import { ModuleMocker } from 'jest-mock';
import { Test } from '@nestjs/testing';
import * as lolex from '@sinonjs/fake-timers';
+import type { TestingModule } from '@nestjs/testing';
+import type { MockFunctionMetadata } from 'jest-mock';
import { GlobalModule } from '@/GlobalModule.js';
import { RoleService } from '@/core/RoleService.js';
import {
@@ -31,8 +33,6 @@ import { secureRndstr } from '@/misc/secure-rndstr.js';
import { NotificationService } from '@/core/NotificationService.js';
import { RoleCondFormulaValue } from '@/models/Role.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import type { TestingModule } from '@nestjs/testing';
-import type { MockFunctionMetadata } from 'jest-mock';
const moduleMocker = new ModuleMocker(global);
@@ -277,9 +277,9 @@ describe('RoleService', () => {
});
describe('getModeratorIds', () => {
- test('includeAdmins = false, excludeExpire = false', async () => {
- const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([
- createUser(), createUser(), createUser(), createUser(), createUser(), createUser(),
+ test('includeAdmins = false, includeRoot = false, excludeExpire = false', async () => {
+ const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
+ createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -295,13 +295,17 @@ describe('RoleService', () => {
assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
]);
- const result = await roleService.getModeratorIds(false, false);
+ const result = await roleService.getModeratorIds({
+ includeAdmins: false,
+ includeRoot: false,
+ excludeExpire: false,
+ });
expect(result).toEqual([modeUser1.id, modeUser2.id]);
});
- test('includeAdmins = false, excludeExpire = true', async () => {
- const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([
- createUser(), createUser(), createUser(), createUser(), createUser(), createUser(),
+ test('includeAdmins = false, includeRoot = false, excludeExpire = true', async () => {
+ const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
+ createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -317,13 +321,17 @@ describe('RoleService', () => {
assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
]);
- const result = await roleService.getModeratorIds(false, true);
+ const result = await roleService.getModeratorIds({
+ includeAdmins: false,
+ includeRoot: false,
+ excludeExpire: true,
+ });
expect(result).toEqual([modeUser1.id]);
});
- test('includeAdmins = true, excludeExpire = false', async () => {
- const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([
- createUser(), createUser(), createUser(), createUser(), createUser(), createUser(),
+ test('includeAdmins = true, includeRoot = false, excludeExpire = false', async () => {
+ const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
+ createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -339,13 +347,17 @@ describe('RoleService', () => {
assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
]);
- const result = await roleService.getModeratorIds(true, false);
+ const result = await roleService.getModeratorIds({
+ includeAdmins: true,
+ includeRoot: false,
+ excludeExpire: false,
+ });
expect(result).toEqual([adminUser1.id, adminUser2.id, modeUser1.id, modeUser2.id]);
});
- test('includeAdmins = true, excludeExpire = true', async () => {
- const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([
- createUser(), createUser(), createUser(), createUser(), createUser(), createUser(),
+ test('includeAdmins = true, includeRoot = false, excludeExpire = true', async () => {
+ const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
+ createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -361,9 +373,111 @@ describe('RoleService', () => {
assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
]);
- const result = await roleService.getModeratorIds(true, true);
+ const result = await roleService.getModeratorIds({
+ includeAdmins: true,
+ includeRoot: false,
+ excludeExpire: true,
+ });
expect(result).toEqual([adminUser1.id, modeUser1.id]);
});
+
+ test('includeAdmins = false, includeRoot = true, excludeExpire = false', async () => {
+ const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
+ createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
+ ]);
+
+ const role1 = await createRole({ name: 'admin', isAdministrator: true });
+ const role2 = await createRole({ name: 'moderator', isModerator: true });
+ const role3 = await createRole({ name: 'normal' });
+
+ await Promise.all([
+ assignRole({ userId: adminUser1.id, roleId: role1.id }),
+ assignRole({ userId: adminUser2.id, roleId: role1.id, expiresAt: new Date(Date.now() - 1000) }),
+ assignRole({ userId: modeUser1.id, roleId: role2.id }),
+ assignRole({ userId: modeUser2.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }),
+ assignRole({ userId: normalUser1.id, roleId: role3.id }),
+ assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
+ ]);
+
+ const result = await roleService.getModeratorIds({
+ includeAdmins: false,
+ includeRoot: true,
+ excludeExpire: false,
+ });
+ expect(result).toEqual([modeUser1.id, modeUser2.id, rootUser.id]);
+ });
+
+ test('root has moderator role', async () => {
+ const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
+ createUser(), createUser(), createUser(), createUser({ isRoot: true }),
+ ]);
+
+ const role1 = await createRole({ name: 'admin', isAdministrator: true });
+ const role2 = await createRole({ name: 'moderator', isModerator: true });
+ const role3 = await createRole({ name: 'normal' });
+
+ await Promise.all([
+ assignRole({ userId: adminUser1.id, roleId: role1.id }),
+ assignRole({ userId: modeUser1.id, roleId: role2.id }),
+ assignRole({ userId: rootUser.id, roleId: role2.id }),
+ assignRole({ userId: normalUser1.id, roleId: role3.id }),
+ ]);
+
+ const result = await roleService.getModeratorIds({
+ includeAdmins: false,
+ includeRoot: true,
+ excludeExpire: false,
+ });
+ expect(result).toEqual([modeUser1.id, rootUser.id]);
+ });
+
+ test('root has administrator role', async () => {
+ const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
+ createUser(), createUser(), createUser(), createUser({ isRoot: true }),
+ ]);
+
+ const role1 = await createRole({ name: 'admin', isAdministrator: true });
+ const role2 = await createRole({ name: 'moderator', isModerator: true });
+ const role3 = await createRole({ name: 'normal' });
+
+ await Promise.all([
+ assignRole({ userId: adminUser1.id, roleId: role1.id }),
+ assignRole({ userId: rootUser.id, roleId: role1.id }),
+ assignRole({ userId: modeUser1.id, roleId: role2.id }),
+ assignRole({ userId: normalUser1.id, roleId: role3.id }),
+ ]);
+
+ const result = await roleService.getModeratorIds({
+ includeAdmins: true,
+ includeRoot: true,
+ excludeExpire: false,
+ });
+ expect(result).toEqual([adminUser1.id, modeUser1.id, rootUser.id]);
+ });
+
+ test('root has moderator role(expire)', async () => {
+ const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
+ createUser(), createUser(), createUser(), createUser({ isRoot: true }),
+ ]);
+
+ const role1 = await createRole({ name: 'admin', isAdministrator: true });
+ const role2 = await createRole({ name: 'moderator', isModerator: true });
+ const role3 = await createRole({ name: 'normal' });
+
+ await Promise.all([
+ assignRole({ userId: adminUser1.id, roleId: role1.id }),
+ assignRole({ userId: modeUser1.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }),
+ assignRole({ userId: rootUser.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }),
+ assignRole({ userId: normalUser1.id, roleId: role3.id }),
+ ]);
+
+ const result = await roleService.getModeratorIds({
+ includeAdmins: false,
+ includeRoot: true,
+ excludeExpire: true,
+ });
+ expect(result).toEqual([rootUser.id]);
+ });
});
describe('conditional role', () => {
diff --git a/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts
new file mode 100644
index 0000000000..b783320aa0
--- /dev/null
+++ b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts
@@ -0,0 +1,235 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { jest } from '@jest/globals';
+import { Test, TestingModule } from '@nestjs/testing';
+import * as lolex from '@sinonjs/fake-timers';
+import { addHours, addSeconds, subDays, subHours, subSeconds } from 'date-fns';
+import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
+import { MiUser, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import { IdService } from '@/core/IdService.js';
+import { RoleService } from '@/core/RoleService.js';
+import { GlobalModule } from '@/GlobalModule.js';
+import { MetaService } from '@/core/MetaService.js';
+import { DI } from '@/di-symbols.js';
+import { QueueLoggerService } from '@/queue/QueueLoggerService.js';
+
+const baseDate = new Date(Date.UTC(2000, 11, 15, 12, 0, 0));
+
+describe('CheckModeratorsActivityProcessorService', () => {
+ let app: TestingModule;
+ let clock: lolex.InstalledClock;
+ let service: CheckModeratorsActivityProcessorService;
+
+ // --------------------------------------------------------------------------------------
+
+ let usersRepository: UsersRepository;
+ let userProfilesRepository: UserProfilesRepository;
+ let idService: IdService;
+ let roleService: jest.Mocked<RoleService>;
+
+ // --------------------------------------------------------------------------------------
+
+ async function createUser(data: Partial<MiUser> = {}) {
+ const id = idService.gen();
+ const user = await usersRepository
+ .insert({
+ id: id,
+ username: `user_${id}`,
+ usernameLower: `user_${id}`.toLowerCase(),
+ ...data,
+ })
+ .then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
+
+ await userProfilesRepository.insert({
+ userId: user.id,
+ });
+
+ return user;
+ }
+
+ function mockModeratorRole(users: MiUser[]) {
+ roleService.getModerators.mockReset();
+ roleService.getModerators.mockResolvedValue(users);
+ }
+
+ // --------------------------------------------------------------------------------------
+
+ beforeAll(async () => {
+ app = await Test
+ .createTestingModule({
+ imports: [
+ GlobalModule,
+ ],
+ providers: [
+ CheckModeratorsActivityProcessorService,
+ IdService,
+ {
+ provide: RoleService, useFactory: () => ({ getModerators: jest.fn() }),
+ },
+ {
+ provide: MetaService, useFactory: () => ({ fetch: jest.fn() }),
+ },
+ {
+ provide: QueueLoggerService, useFactory: () => ({
+ logger: ({
+ createSubLogger: () => ({
+ info: jest.fn(),
+ warn: jest.fn(),
+ succ: jest.fn(),
+ }),
+ }),
+ }),
+ },
+ ],
+ })
+ .compile();
+
+ usersRepository = app.get(DI.usersRepository);
+ userProfilesRepository = app.get(DI.userProfilesRepository);
+
+ service = app.get(CheckModeratorsActivityProcessorService);
+ idService = app.get(IdService);
+ roleService = app.get(RoleService) as jest.Mocked<RoleService>;
+
+ app.enableShutdownHooks();
+ });
+
+ beforeEach(async () => {
+ clock = lolex.install({
+ now: new Date(baseDate),
+ shouldClearNativeTimers: true,
+ });
+ });
+
+ afterEach(async () => {
+ clock.uninstall();
+ await usersRepository.delete({});
+ await userProfilesRepository.delete({});
+ roleService.getModerators.mockReset();
+ });
+
+ afterAll(async () => {
+ await app.close();
+ });
+
+ // --------------------------------------------------------------------------------------
+
+ describe('evaluateModeratorsInactiveDays', () => {
+ test('[isModeratorsInactive] inactiveなモデレーターがいても他のモデレーターがアクティブなら"運営が非アクティブ"としてみなされない', async () => {
+ const [user1, user2, user3, user4] = await Promise.all([
+ // 期限よりも1秒新しいタイミングでアクティブ化(セーフ)
+ createUser({ lastActiveDate: subDays(addSeconds(baseDate, 1), 7) }),
+ // 期限ちょうどにアクティブ化(セーフ)
+ createUser({ lastActiveDate: subDays(baseDate, 7) }),
+ // 期限よりも1秒古いタイミングでアクティブ化(アウト)
+ createUser({ lastActiveDate: subDays(subSeconds(baseDate, 1), 7) }),
+ // 対象外
+ createUser({ lastActiveDate: null }),
+ ]);
+
+ mockModeratorRole([user1, user2, user3, user4]);
+
+ const result = await service.evaluateModeratorsInactiveDays();
+ expect(result.isModeratorsInactive).toBe(false);
+ expect(result.inactiveModerators).toEqual([user3]);
+ });
+
+ test('[isModeratorsInactive] 全員非アクティブなら"運営が非アクティブ"としてみなされる', async () => {
+ const [user1, user2] = await Promise.all([
+ // 期限よりも1秒古いタイミングでアクティブ化(アウト)
+ createUser({ lastActiveDate: subDays(subSeconds(baseDate, 1), 7) }),
+ // 対象外
+ createUser({ lastActiveDate: null }),
+ ]);
+
+ mockModeratorRole([user1, user2]);
+
+ const result = await service.evaluateModeratorsInactiveDays();
+ expect(result.isModeratorsInactive).toBe(true);
+ expect(result.inactiveModerators).toEqual([user1]);
+ });
+
+ test('[countdown] 猶予まで24時間ある場合、猶予1日として計算される', async () => {
+ const [user1, user2] = await Promise.all([
+ createUser({ lastActiveDate: subDays(baseDate, 8) }),
+ // 猶予はこのユーザ基準で計算される想定。
+ // 期限まで残り24時間->猶予1日として計算されるはずである
+ createUser({ lastActiveDate: subDays(baseDate, 6) }),
+ ]);
+
+ mockModeratorRole([user1, user2]);
+
+ const result = await service.evaluateModeratorsInactiveDays();
+ expect(result.isModeratorsInactive).toBe(false);
+ expect(result.inactiveModerators).toEqual([user1]);
+ expect(result.inactivityLimitCountdown).toBe(1);
+ });
+
+ test('[countdown] 猶予まで25時間ある場合、猶予1日として計算される', async () => {
+ const [user1, user2] = await Promise.all([
+ createUser({ lastActiveDate: subDays(baseDate, 8) }),
+ // 猶予はこのユーザ基準で計算される想定。
+ // 期限まで残り25時間->猶予1日として計算されるはずである
+ createUser({ lastActiveDate: subDays(addHours(baseDate, 1), 6) }),
+ ]);
+
+ mockModeratorRole([user1, user2]);
+
+ const result = await service.evaluateModeratorsInactiveDays();
+ expect(result.isModeratorsInactive).toBe(false);
+ expect(result.inactiveModerators).toEqual([user1]);
+ expect(result.inactivityLimitCountdown).toBe(1);
+ });
+
+ test('[countdown] 猶予まで23時間ある場合、猶予0日として計算される', async () => {
+ const [user1, user2] = await Promise.all([
+ createUser({ lastActiveDate: subDays(baseDate, 8) }),
+ // 猶予はこのユーザ基準で計算される想定。
+ // 期限まで残り23時間->猶予0日として計算されるはずである
+ createUser({ lastActiveDate: subDays(subHours(baseDate, 1), 6) }),
+ ]);
+
+ mockModeratorRole([user1, user2]);
+
+ const result = await service.evaluateModeratorsInactiveDays();
+ expect(result.isModeratorsInactive).toBe(false);
+ expect(result.inactiveModerators).toEqual([user1]);
+ expect(result.inactivityLimitCountdown).toBe(0);
+ });
+
+ test('[countdown] 期限ちょうどの場合、猶予0日として計算される', async () => {
+ const [user1, user2] = await Promise.all([
+ createUser({ lastActiveDate: subDays(baseDate, 8) }),
+ // 猶予はこのユーザ基準で計算される想定。
+ // 期限ちょうど->猶予0日として計算されるはずである
+ createUser({ lastActiveDate: subDays(baseDate, 7) }),
+ ]);
+
+ mockModeratorRole([user1, user2]);
+
+ const result = await service.evaluateModeratorsInactiveDays();
+ expect(result.isModeratorsInactive).toBe(false);
+ expect(result.inactiveModerators).toEqual([user1]);
+ expect(result.inactivityLimitCountdown).toBe(0);
+ });
+
+ test('[countdown] 期限より1時間超過している場合、猶予-1日として計算される', async () => {
+ const [user1, user2] = await Promise.all([
+ createUser({ lastActiveDate: subDays(baseDate, 8) }),
+ // 猶予はこのユーザ基準で計算される想定。
+ // 期限より1時間超過->猶予-1日として計算されるはずである
+ createUser({ lastActiveDate: subDays(subHours(baseDate, 1), 7) }),
+ ]);
+
+ mockModeratorRole([user1, user2]);
+
+ const result = await service.evaluateModeratorsInactiveDays();
+ expect(result.isModeratorsInactive).toBe(true);
+ expect(result.inactiveModerators).toEqual([user1, user2]);
+ expect(result.inactivityLimitCountdown).toBe(-1);
+ });
+ });
+});