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

376 lines
9.9 KiB
TypeScript
Raw Permalink Normal View History

/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
2023-02-02 03:26:59 +02:00
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';
2023-02-02 03:26:59 +02:00
import { GlobalModule } from '@/GlobalModule.js';
import { RoleService } from '@/core/RoleService.js';
import type { MiRole, MiUser, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/_.js';
2023-02-02 03:26:59 +02:00
import { DI } from '@/di-symbols.js';
import { MetaService } from '@/core/MetaService.js';
import { genAidx } from '@/misc/id/aidx.js';
2023-04-04 11:32:09 +03:00
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 { sleep } from '../utils.js';
2023-02-02 03:26:59 +02:00
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;
2023-02-02 03:26:59 +02:00
function createUser(data: Partial<MiUser> = {}) {
const un = secureRndstr(16);
2023-02-02 03:26:59 +02:00
return usersRepository.insert({
2023-10-16 06:58:17 +03:00
id: genAidx(Date.now()),
2023-02-02 07:28:29 +02:00
username: un,
usernameLower: un,
...data,
2023-02-02 03:26:59 +02:00
})
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
}
function createRole(data: Partial<MiRole> = {}) {
2023-02-02 03:26:59 +02:00
return rolesRepository.insert({
2023-10-16 06:58:17 +03:00
id: genAidx(Date.now()),
2023-02-02 03:26:59 +02:00
updatedAt: new Date(),
lastUsedAt: new Date(),
description: '',
...data,
})
.then(x => rolesRepository.findOneByOrFail(x.identifiers[0]));
}
beforeEach(async () => {
clock = lolex.install({
now: new Date(),
shouldClearNativeTimers: true,
2023-02-02 11:08:34 +02:00
});
2023-02-02 03:26:59 +02:00
app = await Test.createTestingModule({
imports: [
GlobalModule,
],
providers: [
RoleService,
2023-04-04 11:32:09 +03:00
CacheService,
IdService,
GlobalEventService,
{
provide: NotificationService,
useFactory: () => ({
createNotification: jest.fn(),
}),
},
{
provide: NotificationService.name,
useExisting: NotificationService,
},
2023-02-02 03:26:59 +02:00
],
})
.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();
2023-02-02 03:26:59 +02:00
});
afterEach(async () => {
clock.uninstall();
2023-02-02 03:26:59 +02:00
await Promise.all([
app.get(DI.metasRepository).delete({}),
usersRepository.delete({}),
rolesRepository.delete({}),
roleAssignmentsRepository.delete({}),
]);
2023-02-02 03:26:59 +02:00
await app.close();
});
describe('getUserPolicies', () => {
test('instance default policies', async () => {
2023-02-02 03:26:59 +02:00
const user = await createUser();
metaService.fetch.mockResolvedValue({
policies: {
canManageCustomEmojis: false,
},
2023-02-02 07:28:29 +02:00
} as any);
2023-02-02 03:26:59 +02:00
const result = await roleService.getUserPolicies(user.id);
2023-02-02 03:26:59 +02:00
expect(result.canManageCustomEmojis).toBe(false);
});
test('instance default policies 2', async () => {
2023-02-02 03:26:59 +02:00
const user = await createUser();
metaService.fetch.mockResolvedValue({
policies: {
canManageCustomEmojis: true,
},
2023-02-02 07:28:29 +02:00
} as any);
2023-02-02 03:26:59 +02:00
const result = await roleService.getUserPolicies(user.id);
2023-02-02 03:26:59 +02:00
expect(result.canManageCustomEmojis).toBe(true);
});
test('with role', async () => {
2023-02-02 03:26:59 +02:00
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);
2023-02-02 03:26:59 +02:00
metaService.fetch.mockResolvedValue({
policies: {
canManageCustomEmojis: false,
},
2023-02-02 07:28:29 +02:00
} as any);
2023-02-02 03:26:59 +02:00
const result = await roleService.getUserPolicies(user.id);
2023-02-02 03:26:59 +02:00
expect(result.canManageCustomEmojis).toBe(true);
});
2023-02-02 07:28:29 +02:00
test('priority', async () => {
2023-02-02 11:06:23 +02:00
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);
2023-02-02 11:06:23 +02:00
metaService.fetch.mockResolvedValue({
policies: {
driveCapacityMb: 50,
},
} as any);
2023-02-02 11:06:23 +02:00
const result = await roleService.getUserPolicies(user.id);
2023-02-02 11:06:23 +02:00
expect(result.driveCapacityMb).toBe(100);
});
test('conditional role', async () => {
2023-02-02 07:28:29 +02:00
const user1 = await createUser({
2023-10-16 08:35:44 +03:00
id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)),
2023-02-02 07:28:29 +02:00
});
const user2 = await createUser({
2023-10-16 08:35:44 +03:00
id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)),
2023-02-02 07:28:29 +02:00
followersCount: 10,
});
await createRole({
2023-02-02 07:28:29 +02:00
name: 'a',
policies: {
canManageCustomEmojis: {
useDefault: false,
priority: 0,
value: true,
},
},
target: 'conditional',
condFormula: {
id: '232a4221-9816-49a6-a967-ae0fac52ec5e',
2023-02-02 07:28:29 +02:00
type: 'and',
values: [{
id: '2a37ef43-2d93-4c4d-87f6-f2fdb7d9b530',
2023-02-02 07:28:29 +02:00
type: 'followersMoreThanOrEq',
value: 10,
}, {
id: '1bd67839-b126-4f92-bad0-4e285dab453b',
2023-02-02 07:28:29 +02:00
type: 'createdMoreThan',
sec: 60 * 60 * 24 * 7,
}],
},
});
metaService.fetch.mockResolvedValue({
policies: {
canManageCustomEmojis: false,
},
} as any);
2023-02-02 07:28:29 +02:00
const user1Policies = await roleService.getUserPolicies(user1.id);
const user2Policies = await roleService.getUserPolicies(user2.id);
expect(user1Policies.canManageCustomEmojis).toBe(false);
expect(user2Policies.canManageCustomEmojis).toBe(true);
});
test('コンディショナルロール: マニュアルロールにアサイン済み', async () => {
const [user1, user2, role1] = await Promise.all([
createUser(),
createUser(),
createRole({
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,
},
});
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('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 any);
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);
});
2023-02-02 03:26:59 +02:00
});
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();
});
});
2023-02-02 03:26:59 +02:00
});