misskey/packages/backend/test/unit/RoleService.ts

742 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { UserEntityService } from '@/core/entities/UserEntityService.js';
process.env.NODE_ENV = 'test';
import { jest } from '@jest/globals';
import { ModuleMocker } from 'jest-mock';
import { Test } from '@nestjs/testing';
import * as lolex from '@sinonjs/fake-timers';
import { GlobalModule } from '@/GlobalModule.js';
import { RoleService } from '@/core/RoleService.js';
import type { MiRole, MiUser, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { MetaService } from '@/core/MetaService.js';
import { genAidx } from '@/misc/id/aidx.js';
import { CacheService } from '@/core/CacheService.js';
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';
const moduleMocker = new ModuleMocker(global);
describe('RoleService', () => {
let app: TestingModule;
let roleService: RoleService;
let usersRepository: UsersRepository;
let rolesRepository: RolesRepository;
let roleAssignmentsRepository: RoleAssignmentsRepository;
let metaService: jest.Mocked<MetaService>;
let notificationService: jest.Mocked<NotificationService>;
let clock: lolex.InstalledClock;
function createUser(data: Partial<MiUser> = {}) {
const un = secureRndstr(16);
return usersRepository.insert({
id: genAidx(Date.now()),
username: un,
usernameLower: un,
...data,
})
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
}
function createRole(data: Partial<MiRole> = {}) {
return rolesRepository.insert({
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<MiRole> = {}) {
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(),
shouldClearNativeTimers: true,
});
app = await Test.createTestingModule({
imports: [
GlobalModule,
],
providers: [
RoleService,
CacheService,
IdService,
GlobalEventService,
UserEntityService,
{
provide: NotificationService,
useFactory: () => ({
createNotification: jest.fn(),
}),
},
{
provide: NotificationService.name,
useExisting: NotificationService,
},
],
})
.useMocker((token) => {
if (token === MetaService) {
return { fetch: jest.fn() };
}
if (typeof token === 'function') {
const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata<any, any>;
const Mock = moduleMocker.generateFromMetadata(mockMetadata);
return new Mock();
}
})
.compile();
app.enableShutdownHooks();
roleService = app.get<RoleService>(RoleService);
usersRepository = app.get<UsersRepository>(DI.usersRepository);
rolesRepository = app.get<RolesRepository>(DI.rolesRepository);
roleAssignmentsRepository = app.get<RoleAssignmentsRepository>(DI.roleAssignmentsRepository);
metaService = app.get<MetaService>(MetaService) as jest.Mocked<MetaService>;
notificationService = app.get<NotificationService>(NotificationService) as jest.Mocked<NotificationService>;
await roleService.onModuleInit();
});
afterEach(async () => {
clock.uninstall();
await Promise.all([
app.get(DI.metasRepository).delete({}),
usersRepository.delete({}),
rolesRepository.delete({}),
roleAssignmentsRepository.delete({}),
]);
await app.close();
});
describe('getUserPolicies', () => {
test('instance default policies', async () => {
const user = await createUser();
metaService.fetch.mockResolvedValue({
policies: {
canManageCustomEmojis: false,
},
} as FIXME);
const result = await roleService.getUserPolicies(user.id);
expect(result.canManageCustomEmojis).toBe(false);
});
test('instance default policies 2', async () => {
const user = await createUser();
metaService.fetch.mockResolvedValue({
policies: {
canManageCustomEmojis: true,
},
} as FIXME);
const result = await roleService.getUserPolicies(user.id);
expect(result.canManageCustomEmojis).toBe(true);
});
test('with role', async () => {
const user = await createUser();
const role = await createRole({
name: 'a',
policies: {
canManageCustomEmojis: {
useDefault: false,
priority: 0,
value: true,
},
},
});
await roleService.assign(user.id, role.id);
metaService.fetch.mockResolvedValue({
policies: {
canManageCustomEmojis: false,
},
} as FIXME);
const result = await roleService.getUserPolicies(user.id);
expect(result.canManageCustomEmojis).toBe(true);
});
test('priority', async () => {
const user = await createUser();
const role1 = await createRole({
name: 'role1',
policies: {
driveCapacityMb: {
useDefault: false,
priority: 0,
value: 200,
},
},
});
const role2 = await createRole({
name: 'role2',
policies: {
driveCapacityMb: {
useDefault: false,
priority: 1,
value: 100,
},
},
});
await roleService.assign(user.id, role1.id);
await roleService.assign(user.id, role2.id);
metaService.fetch.mockResolvedValue({
policies: {
driveCapacityMb: 50,
},
} as FIXME);
const result = await roleService.getUserPolicies(user.id);
expect(result.driveCapacityMb).toBe(100);
});
test('expired role', async () => {
const user = await createUser();
const role = await createRole({
name: 'a',
policies: {
canManageCustomEmojis: {
useDefault: false,
priority: 0,
value: true,
},
},
});
await roleService.assign(user.id, role.id, new Date(Date.now() + (1000 * 60 * 60 * 24)));
metaService.fetch.mockResolvedValue({
policies: {
canManageCustomEmojis: false,
},
} as FIXME);
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 () => {
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(),
createRole({
name: 'manual role',
}),
]);
const role2 = await createConditionalRole({
id: aidx(),
type: 'roleAssignedTo',
roleId: role1.id,
});
await roleService.assign(user2.id, role1.id);
const [u1role, u2role] = await Promise.all([
roleService.getUserRoles(user1.id),
roleService.getUserRoles(user2.id),
]);
expect(u1role.some(r => r.id === role2.id)).toBe(false);
expect(u2role.some(r => r.id === role2.id)).toBe(true);
});
test('', async () => {
const [user1, user2] = await Promise.all([
createUser({ host: null }),
createUser({ host: 'example.com' }),
]);
const role = await createConditionalRole({
id: aidx(),
type: 'isLocal',
});
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);
});
test('', async () => {
const [user1, user2] = await Promise.all([
createUser({ host: null }),
createUser({ host: 'example.com' }),
]);
const role = await createConditionalRole({
id: aidx(),
type: 'isRemote',
});
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({ isSuspended: false }),
createUser({ isSuspended: true }),
]);
const role = await createConditionalRole({
id: aidx(),
type: 'isSuspended',
});
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({ 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);
});
});
describe('assign', () => {
test('', async () => {
const user = await createUser();
const role = await createRole({
isPublic: true,
name: 'a',
});
await roleService.assign(user.id, role.id);
clock.uninstall();
await sleep(100);
const assignments = await roleAssignmentsRepository.find({
where: {
userId: user.id,
roleId: role.id,
},
});
expect(assignments).toHaveLength(1);
expect(notificationService.createNotification).toHaveBeenCalled();
expect(notificationService.createNotification.mock.lastCall![0]).toBe(user.id);
expect(notificationService.createNotification.mock.lastCall![1]).toBe('roleAssigned');
expect(notificationService.createNotification.mock.lastCall![2]).toEqual({
roleId: role.id,
});
});
test('', async () => {
const user = await createUser();
const role = await createRole({
isPublic: false,
name: 'a',
});
await roleService.assign(user.id, role.id);
clock.uninstall();
await sleep(100);
const assignments = await roleAssignmentsRepository.find({
where: {
userId: user.id,
roleId: role.id,
},
});
expect(assignments).toHaveLength(1);
expect(notificationService.createNotification).not.toHaveBeenCalled();
});
});
});