2023-07-27 05:31:52 +00:00
|
|
|
/*
|
|
|
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2023-02-02 01:26:59 +00:00
|
|
|
process.env.NODE_ENV = 'test';
|
|
|
|
|
|
|
|
import { jest } from '@jest/globals';
|
|
|
|
import { ModuleMocker } from 'jest-mock';
|
|
|
|
import { Test } from '@nestjs/testing';
|
2023-03-01 01:20:03 +00:00
|
|
|
import * as lolex from '@sinonjs/fake-timers';
|
2023-02-02 01:26:59 +00:00
|
|
|
import { GlobalModule } from '@/GlobalModule.js';
|
|
|
|
import { RoleService } from '@/core/RoleService.js';
|
2023-09-15 05:28:29 +00:00
|
|
|
import type { MiRole, RolesRepository, RoleAssignmentsRepository, UsersRepository, MiUser } from '@/models/_.js';
|
2023-02-02 01:26:59 +00:00
|
|
|
import { DI } from '@/di-symbols.js';
|
|
|
|
import { MetaService } from '@/core/MetaService.js';
|
2023-09-06 09:33:51 +00:00
|
|
|
import { genAidx } from '@/misc/id/aidx.js';
|
2023-04-04 08:32:09 +00:00
|
|
|
import { CacheService } from '@/core/CacheService.js';
|
2023-03-01 01:20:03 +00:00
|
|
|
import { IdService } from '@/core/IdService.js';
|
|
|
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
2023-06-25 02:04:33 +00:00
|
|
|
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
2023-12-21 01:39:11 +00:00
|
|
|
import { NotificationService } from '@/core/NotificationService.js';
|
2023-03-01 01:20:03 +00:00
|
|
|
import { sleep } from '../utils.js';
|
2023-02-02 01:26:59 +00: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>;
|
2023-12-21 01:39:11 +00:00
|
|
|
let notificationService: jest.Mocked<NotificationService>;
|
2023-03-01 01:20:03 +00:00
|
|
|
let clock: lolex.InstalledClock;
|
2023-02-02 01:26:59 +00:00
|
|
|
|
2023-09-04 04:33:38 +00:00
|
|
|
function createUser(data: Partial<MiUser> = {}) {
|
2023-06-25 02:04:33 +00:00
|
|
|
const un = secureRndstr(16);
|
2023-02-02 01:26:59 +00:00
|
|
|
return usersRepository.insert({
|
2023-10-16 03:58:17 +00:00
|
|
|
id: genAidx(Date.now()),
|
2023-02-02 05:28:29 +00:00
|
|
|
username: un,
|
|
|
|
usernameLower: un,
|
|
|
|
...data,
|
2023-02-02 01:26:59 +00:00
|
|
|
})
|
|
|
|
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
|
|
|
|
}
|
|
|
|
|
2023-09-04 04:33:38 +00:00
|
|
|
function createRole(data: Partial<MiRole> = {}) {
|
2023-02-02 01:26:59 +00:00
|
|
|
return rolesRepository.insert({
|
2023-10-16 03:58:17 +00:00
|
|
|
id: genAidx(Date.now()),
|
2023-02-02 01:26:59 +00:00
|
|
|
updatedAt: new Date(),
|
|
|
|
lastUsedAt: new Date(),
|
|
|
|
description: '',
|
|
|
|
...data,
|
|
|
|
})
|
|
|
|
.then(x => rolesRepository.findOneByOrFail(x.identifiers[0]));
|
|
|
|
}
|
|
|
|
|
2023-03-01 01:20:03 +00:00
|
|
|
beforeEach(async () => {
|
|
|
|
clock = lolex.install({
|
|
|
|
now: new Date(),
|
|
|
|
shouldClearNativeTimers: true,
|
2023-02-02 09:08:34 +00:00
|
|
|
});
|
|
|
|
|
2023-02-02 01:26:59 +00:00
|
|
|
app = await Test.createTestingModule({
|
|
|
|
imports: [
|
|
|
|
GlobalModule,
|
|
|
|
],
|
|
|
|
providers: [
|
|
|
|
RoleService,
|
2023-04-04 08:32:09 +00:00
|
|
|
CacheService,
|
2023-03-01 01:20:03 +00:00
|
|
|
IdService,
|
|
|
|
GlobalEventService,
|
2023-12-22 00:06:13 +00:00
|
|
|
{
|
|
|
|
provide: NotificationService,
|
|
|
|
useFactory: () => ({
|
|
|
|
createNotification: jest.fn(),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
provide: NotificationService.name,
|
|
|
|
useExisting: NotificationService,
|
|
|
|
},
|
2023-02-02 01:26:59 +00: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>;
|
2023-12-21 01:39:11 +00:00
|
|
|
notificationService = app.get<NotificationService>(NotificationService) as jest.Mocked<NotificationService>;
|
2023-12-22 00:06:13 +00:00
|
|
|
|
|
|
|
await roleService.onModuleInit();
|
2023-02-02 01:26:59 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(async () => {
|
2023-03-01 01:20:03 +00:00
|
|
|
clock.uninstall();
|
|
|
|
|
2023-02-02 01:26:59 +00:00
|
|
|
await Promise.all([
|
|
|
|
app.get(DI.metasRepository).delete({}),
|
|
|
|
usersRepository.delete({}),
|
|
|
|
rolesRepository.delete({}),
|
|
|
|
roleAssignmentsRepository.delete({}),
|
|
|
|
]);
|
2023-03-01 01:20:03 +00:00
|
|
|
|
2023-02-02 01:26:59 +00:00
|
|
|
await app.close();
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('getUserPolicies', () => {
|
2023-06-25 02:04:33 +00:00
|
|
|
test('instance default policies', async () => {
|
2023-02-02 01:26:59 +00:00
|
|
|
const user = await createUser();
|
|
|
|
metaService.fetch.mockResolvedValue({
|
|
|
|
policies: {
|
|
|
|
canManageCustomEmojis: false,
|
|
|
|
},
|
2023-02-02 05:28:29 +00:00
|
|
|
} as any);
|
2023-06-25 02:04:33 +00:00
|
|
|
|
2023-02-02 01:26:59 +00:00
|
|
|
const result = await roleService.getUserPolicies(user.id);
|
2023-06-25 02:04:33 +00:00
|
|
|
|
2023-02-02 01:26:59 +00:00
|
|
|
expect(result.canManageCustomEmojis).toBe(false);
|
|
|
|
});
|
2023-06-25 02:04:33 +00:00
|
|
|
|
2023-03-01 01:20:03 +00:00
|
|
|
test('instance default policies 2', async () => {
|
2023-02-02 01:26:59 +00:00
|
|
|
const user = await createUser();
|
|
|
|
metaService.fetch.mockResolvedValue({
|
|
|
|
policies: {
|
|
|
|
canManageCustomEmojis: true,
|
|
|
|
},
|
2023-02-02 05:28:29 +00:00
|
|
|
} as any);
|
2023-06-25 02:04:33 +00:00
|
|
|
|
2023-02-02 01:26:59 +00:00
|
|
|
const result = await roleService.getUserPolicies(user.id);
|
2023-06-25 02:04:33 +00:00
|
|
|
|
2023-02-02 01:26:59 +00:00
|
|
|
expect(result.canManageCustomEmojis).toBe(true);
|
|
|
|
});
|
2023-06-25 02:04:33 +00:00
|
|
|
|
2023-03-01 01:20:03 +00:00
|
|
|
test('with role', async () => {
|
2023-02-02 01:26:59 +00:00
|
|
|
const user = await createUser();
|
|
|
|
const role = await createRole({
|
|
|
|
name: 'a',
|
|
|
|
policies: {
|
|
|
|
canManageCustomEmojis: {
|
|
|
|
useDefault: false,
|
|
|
|
priority: 0,
|
|
|
|
value: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
2023-03-01 01:20:03 +00:00
|
|
|
await roleService.assign(user.id, role.id);
|
2023-02-02 01:26:59 +00:00
|
|
|
metaService.fetch.mockResolvedValue({
|
|
|
|
policies: {
|
|
|
|
canManageCustomEmojis: false,
|
|
|
|
},
|
2023-02-02 05:28:29 +00:00
|
|
|
} as any);
|
2023-06-25 02:04:33 +00:00
|
|
|
|
2023-02-02 01:26:59 +00:00
|
|
|
const result = await roleService.getUserPolicies(user.id);
|
2023-06-25 02:04:33 +00:00
|
|
|
|
2023-02-02 01:26:59 +00:00
|
|
|
expect(result.canManageCustomEmojis).toBe(true);
|
|
|
|
});
|
2023-02-02 05:28:29 +00:00
|
|
|
|
2023-03-01 01:20:03 +00:00
|
|
|
test('priority', async () => {
|
2023-02-02 09:06:23 +00: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,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
2023-03-01 01:20:03 +00:00
|
|
|
await roleService.assign(user.id, role1.id);
|
|
|
|
await roleService.assign(user.id, role2.id);
|
2023-02-02 09:06:23 +00:00
|
|
|
metaService.fetch.mockResolvedValue({
|
|
|
|
policies: {
|
|
|
|
driveCapacityMb: 50,
|
|
|
|
},
|
|
|
|
} as any);
|
2023-06-25 02:04:33 +00:00
|
|
|
|
2023-02-02 09:06:23 +00:00
|
|
|
const result = await roleService.getUserPolicies(user.id);
|
2023-06-25 02:04:33 +00:00
|
|
|
|
2023-02-02 09:06:23 +00:00
|
|
|
expect(result.driveCapacityMb).toBe(100);
|
|
|
|
});
|
|
|
|
|
2023-03-01 01:20:03 +00:00
|
|
|
test('conditional role', async () => {
|
2023-02-02 05:28:29 +00:00
|
|
|
const user1 = await createUser({
|
2023-10-16 05:35:44 +00:00
|
|
|
id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)),
|
2023-02-02 05:28:29 +00:00
|
|
|
});
|
|
|
|
const user2 = await createUser({
|
2023-10-16 05:35:44 +00:00
|
|
|
id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)),
|
2023-02-02 05:28:29 +00:00
|
|
|
followersCount: 10,
|
|
|
|
});
|
2023-09-04 04:33:38 +00:00
|
|
|
await createRole({
|
2023-02-02 05:28:29 +00:00
|
|
|
name: 'a',
|
|
|
|
policies: {
|
|
|
|
canManageCustomEmojis: {
|
|
|
|
useDefault: false,
|
|
|
|
priority: 0,
|
|
|
|
value: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
target: 'conditional',
|
|
|
|
condFormula: {
|
|
|
|
type: 'and',
|
|
|
|
values: [{
|
|
|
|
type: 'followersMoreThanOrEq',
|
|
|
|
value: 10,
|
|
|
|
}, {
|
|
|
|
type: 'createdMoreThan',
|
|
|
|
sec: 60 * 60 * 24 * 7,
|
|
|
|
}],
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
metaService.fetch.mockResolvedValue({
|
|
|
|
policies: {
|
|
|
|
canManageCustomEmojis: false,
|
|
|
|
},
|
|
|
|
} as any);
|
2023-06-25 02:04:33 +00:00
|
|
|
|
2023-02-02 05:28:29 +00: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);
|
|
|
|
});
|
2023-03-01 01:20:03 +00:00
|
|
|
|
|
|
|
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);
|
2023-06-25 02:04:33 +00:00
|
|
|
|
2023-03-01 01:20:03 +00:00
|
|
|
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 01:26:59 +00:00
|
|
|
});
|
2023-12-21 01:39:11 +00:00
|
|
|
|
|
|
|
describe('assign', () => {
|
|
|
|
test('公開ロールの場合は通知される', async () => {
|
|
|
|
const user = await createUser();
|
|
|
|
const role = await createRole({
|
|
|
|
isPublic: true,
|
2023-12-22 00:06:13 +00:00
|
|
|
name: 'a',
|
2023-12-21 01:39:11 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
await roleService.assign(user.id, role.id);
|
|
|
|
|
2023-12-22 00:06:13 +00:00
|
|
|
clock.uninstall();
|
2023-12-21 01:39:11 +00:00
|
|
|
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');
|
2023-12-22 00:06:13 +00:00
|
|
|
expect(notificationService.createNotification.mock.lastCall![2]).toEqual({
|
2023-12-21 01:39:11 +00:00
|
|
|
roleId: role.id,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
test('非公開ロールの場合は通知されない', async () => {
|
|
|
|
const user = await createUser();
|
|
|
|
const role = await createRole({
|
|
|
|
isPublic: false,
|
2023-12-22 00:06:13 +00:00
|
|
|
name: 'a',
|
2023-12-21 01:39:11 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
await roleService.assign(user.id, role.id);
|
|
|
|
|
2023-12-22 00:06:13 +00:00
|
|
|
clock.uninstall();
|
2023-12-21 01:39:11 +00:00
|
|
|
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 01:26:59 +00:00
|
|
|
});
|