1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-20 01:15:29 +02:00

Merge branch 'develop' into renovate/major-backend-update-dependencies

This commit is contained in:
kakkokari-gtyih
2025-11-25 09:34:21 +09:00
248 changed files with 9569 additions and 3157 deletions

View File

@@ -69,6 +69,9 @@ describe('アンテナ', () => {
let userMutingAlice: User;
let userMutedByAlice: User;
let testChannel: misskey.entities.Channel;
let testMutedChannel: misskey.entities.Channel;
beforeAll(async () => {
root = await signup({ username: 'root' });
alice = await signup({ username: 'alice' });
@@ -120,6 +123,10 @@ describe('アンテナ', () => {
userMutedByAlice = await signup({ username: 'userMutedByAlice' });
await post(userMutedByAlice, { text: 'test' });
await api('mute/create', { userId: userMutedByAlice.id }, alice);
testChannel = (await api('channels/create', { name: 'test' }, root)).body;
testMutedChannel = (await api('channels/create', { name: 'test-muted' }, root)).body;
await api('channels/mute/create', { channelId: testMutedChannel.id }, alice);
}, 1000 * 60 * 10);
beforeEach(async () => {
@@ -605,6 +612,20 @@ describe('アンテナ', () => {
{ note: (): Promise<Note> => post(bob, { text: `${keyword}` }), included: true },
],
},
{
label: 'チャンネルノートも含む',
parameters: () => ({ src: 'all' }),
posts: [
{ note: (): Promise<Note> => post(bob, { text: `test ${keyword}`, channelId: testChannel.id }), included: true },
],
},
{
label: 'ミュートしてるチャンネルは含まない',
parameters: () => ({ src: 'all' }),
posts: [
{ note: (): Promise<Note> => post(bob, { text: `test ${keyword}`, channelId: testMutedChannel.id }) },
],
},
])('が取得できること($label', async ({ parameters, posts }) => {
const antenna = await successfulApiCall({
endpoint: 'antennas/create',

View File

@@ -506,10 +506,10 @@ describe('クリップ', () => {
});
};
const myFavorites = async (request: Partial<ApiRequest<'clips/my-favorites'>> = {}): Promise<Misskey.entities.Clip[]> => {
const myFavorites = async (parameters: Misskey.entities.ClipsMyFavoritesRequest, request: Partial<ApiRequest<'clips/my-favorites'>> = {}): Promise<Misskey.entities.Clip[]> => {
return successfulApiCall({
endpoint: 'clips/my-favorites',
parameters: {},
parameters,
user: alice,
...request,
});
@@ -562,8 +562,9 @@ describe('クリップ', () => {
await favorite({ clipId: clip.id });
}
// pagenationはない。全部一気にとれる。
const favorited = await myFavorites();
const favorited = await myFavorites({
limit: 30,
});
assert.strictEqual(favorited.length, clips.length);
for (const clip of favorited) {
assert.strictEqual(clip.favoritedCount, 1);
@@ -617,7 +618,7 @@ describe('クリップ', () => {
const clip = await show({ clipId: aliceClip.id });
assert.strictEqual(clip.favoritedCount, 0);
assert.strictEqual(clip.isFavorited, false);
assert.deepStrictEqual(await myFavorites(), []);
assert.deepStrictEqual(await myFavorites({}), []);
});
test.each([
@@ -651,13 +652,13 @@ describe('クリップ', () => {
test('を取得できる。', async () => {
await favorite({ clipId: aliceClip.id });
const favorited = await myFavorites();
const favorited = await myFavorites({});
assert.deepStrictEqual(favorited, [await show({ clipId: aliceClip.id })]);
});
test('を取得したとき他人のお気に入りは含まない。', async () => {
await favorite({ clipId: aliceClip.id });
const favorited = await myFavorites({ user: bob });
const favorited = await myFavorites({}, { user: bob });
assert.deepStrictEqual(favorited, []);
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
module.exports = async () => {
// DBはUTCっぽいので、テスト側も合わせておく
process.env.TZ = 'UTC';
process.env.NODE_ENV = 'test';
};

View File

@@ -0,0 +1,235 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
/* eslint-disable */
import { afterEach, beforeEach, describe, expect } from '@jest/globals';
import { Test, TestingModule } from '@nestjs/testing';
import { GlobalModule } from '@/GlobalModule.js';
import { CoreModule } from '@/core/CoreModule.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { IdService } from '@/core/IdService.js';
import {
type ChannelFollowingsRepository,
ChannelsRepository,
DriveFilesRepository,
MiChannel,
MiChannelFollowing,
MiDriveFile,
MiUser,
UserProfilesRepository,
UsersRepository,
} from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { ChannelFollowingService } from "@/core/ChannelFollowingService.js";
import { MiLocalUser } from "@/models/User.js";
describe('ChannelFollowingService', () => {
let app: TestingModule;
let service: ChannelFollowingService;
let channelsRepository: ChannelsRepository;
let channelFollowingsRepository: ChannelFollowingsRepository;
let usersRepository: UsersRepository;
let userProfilesRepository: UserProfilesRepository;
let driveFilesRepository: DriveFilesRepository;
let idService: IdService;
let alice: MiLocalUser;
let bob: MiLocalUser;
let channel1: MiChannel;
let channel2: MiChannel;
let channel3: MiChannel;
let driveFile1: MiDriveFile;
let driveFile2: MiDriveFile;
async function createUser(data: Partial<MiUser> = {}) {
const user = await usersRepository
.insert({
id: idService.gen(),
username: 'username',
usernameLower: 'username',
...data,
})
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
await userProfilesRepository.insert({
userId: user.id,
});
return user;
}
async function createChannel(data: Partial<MiChannel> = {}) {
return await channelsRepository
.insert({
id: idService.gen(),
...data,
})
.then(x => channelsRepository.findOneByOrFail(x.identifiers[0]));
}
async function createChannelFollowing(data: Partial<MiChannelFollowing> = {}) {
return await channelFollowingsRepository
.insert({
id: idService.gen(),
...data,
})
.then(x => channelFollowingsRepository.findOneByOrFail(x.identifiers[0]));
}
async function fetchChannelFollowing() {
return await channelFollowingsRepository.findBy({});
}
async function createDriveFile(data: Partial<MiDriveFile> = {}) {
return await driveFilesRepository
.insert({
id: idService.gen(),
md5: 'md5',
name: 'name',
size: 0,
type: 'type',
storedInternal: false,
url: 'url',
...data,
})
.then(x => driveFilesRepository.findOneByOrFail(x.identifiers[0]));
}
beforeAll(async () => {
app = await Test.createTestingModule({
imports: [
GlobalModule,
CoreModule,
],
providers: [
GlobalEventService,
IdService,
ChannelFollowingService,
],
}).compile();
app.enableShutdownHooks();
service = app.get<ChannelFollowingService>(ChannelFollowingService);
idService = app.get<IdService>(IdService);
channelsRepository = app.get<ChannelsRepository>(DI.channelsRepository);
channelFollowingsRepository = app.get<ChannelFollowingsRepository>(DI.channelFollowingsRepository);
usersRepository = app.get<UsersRepository>(DI.usersRepository);
userProfilesRepository = app.get<UserProfilesRepository>(DI.userProfilesRepository);
driveFilesRepository = app.get<DriveFilesRepository>(DI.driveFilesRepository);
});
afterAll(async () => {
await app.close();
});
beforeEach(async () => {
alice = { ...await createUser({ username: 'alice' }), host: null, uri: null };
bob = { ...await createUser({ username: 'bob' }), host: null, uri: null };
driveFile1 = await createDriveFile();
driveFile2 = await createDriveFile();
channel1 = await createChannel({ name: 'channel1', userId: alice.id, bannerId: driveFile1.id });
channel2 = await createChannel({ name: 'channel2', userId: alice.id, bannerId: driveFile2.id });
channel3 = await createChannel({ name: 'channel3', userId: alice.id, bannerId: driveFile2.id });
});
afterEach(async () => {
await channelFollowingsRepository.deleteAll();
await channelsRepository.deleteAll();
await userProfilesRepository.deleteAll();
await usersRepository.deleteAll();
});
describe('list', () => {
test('default', async () => {
await createChannelFollowing({ followerId: alice.id, followeeId: channel1.id });
await createChannelFollowing({ followerId: alice.id, followeeId: channel2.id });
await createChannelFollowing({ followerId: bob.id, followeeId: channel3.id });
const followings = await service.list({ requestUserId: alice.id });
expect(followings).toHaveLength(2);
expect(followings[0].id).toBe(channel1.id);
expect(followings[0].userId).toBe(alice.id);
expect(followings[0].user).toBeFalsy();
expect(followings[0].bannerId).toBe(driveFile1.id);
expect(followings[0].banner).toBeFalsy();
expect(followings[1].id).toBe(channel2.id);
expect(followings[1].userId).toBe(alice.id);
expect(followings[1].user).toBeFalsy();
expect(followings[1].bannerId).toBe(driveFile2.id);
expect(followings[1].banner).toBeFalsy();
});
test('idOnly', async () => {
await createChannelFollowing({ followerId: alice.id, followeeId: channel1.id });
await createChannelFollowing({ followerId: alice.id, followeeId: channel2.id });
await createChannelFollowing({ followerId: bob.id, followeeId: channel3.id });
const followings = await service.list({ requestUserId: alice.id }, { idOnly: true });
expect(followings).toHaveLength(2);
expect(followings[0].id).toBe(channel1.id);
expect(followings[1].id).toBe(channel2.id);
});
test('joinUser', async () => {
await createChannelFollowing({ followerId: alice.id, followeeId: channel1.id });
await createChannelFollowing({ followerId: alice.id, followeeId: channel2.id });
await createChannelFollowing({ followerId: bob.id, followeeId: channel3.id });
const followings = await service.list({ requestUserId: alice.id }, { joinUser: true });
expect(followings).toHaveLength(2);
expect(followings[0].id).toBe(channel1.id);
expect(followings[0].user).toEqual(alice);
expect(followings[0].banner).toBeFalsy();
expect(followings[1].id).toBe(channel2.id);
expect(followings[1].user).toEqual(alice);
expect(followings[1].banner).toBeFalsy();
});
test('joinBannerFile', async () => {
await createChannelFollowing({ followerId: alice.id, followeeId: channel1.id });
await createChannelFollowing({ followerId: alice.id, followeeId: channel2.id });
await createChannelFollowing({ followerId: bob.id, followeeId: channel3.id });
const followings = await service.list({ requestUserId: alice.id }, { joinBannerFile: true });
expect(followings).toHaveLength(2);
expect(followings[0].id).toBe(channel1.id);
expect(followings[0].user).toBeFalsy();
expect(followings[0].banner).toEqual(driveFile1);
expect(followings[1].id).toBe(channel2.id);
expect(followings[1].user).toBeFalsy();
expect(followings[1].banner).toEqual(driveFile2);
});
});
describe('follow', () => {
test('default', async () => {
await service.follow(alice, channel1);
const followings = await fetchChannelFollowing();
expect(followings).toHaveLength(1);
expect(followings[0].followeeId).toBe(channel1.id);
expect(followings[0].followerId).toBe(alice.id);
});
});
describe('unfollow', () => {
test('default', async () => {
await createChannelFollowing({ followerId: alice.id, followeeId: channel1.id });
await service.unfollow(alice, channel1);
const followings = await fetchChannelFollowing();
expect(followings).toHaveLength(0);
});
});
});

View File

@@ -0,0 +1,336 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
/* eslint-disable */
import { afterEach, beforeEach, describe, expect } from '@jest/globals';
import { Test, TestingModule } from '@nestjs/testing';
import { GlobalModule } from '@/GlobalModule.js';
import { CoreModule } from '@/core/CoreModule.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { IdService } from '@/core/IdService.js';
import { ChannelMutingService } from '@/core/ChannelMutingService.js';
import {
ChannelMutingRepository,
ChannelsRepository,
DriveFilesRepository,
MiChannel,
MiChannelMuting,
MiDriveFile,
MiUser,
UserProfilesRepository,
UsersRepository,
} from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { setTimeout } from 'node:timers/promises';
describe('ChannelMutingService', () => {
let app: TestingModule;
let service: ChannelMutingService;
let channelsRepository: ChannelsRepository;
let channelMutingRepository: ChannelMutingRepository;
let usersRepository: UsersRepository;
let userProfilesRepository: UserProfilesRepository;
let driveFilesRepository: DriveFilesRepository;
let idService: IdService;
let alice: MiUser;
let bob: MiUser;
let channel1: MiChannel;
let channel2: MiChannel;
let channel3: MiChannel;
let driveFile1: MiDriveFile;
let driveFile2: MiDriveFile;
async function createUser(data: Partial<MiUser> = {}) {
const user = await usersRepository
.insert({
id: idService.gen(),
username: 'username',
usernameLower: 'username',
...data,
})
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
await userProfilesRepository.insert({
userId: user.id,
});
return user;
}
async function createChannel(data: Partial<MiChannel> = {}) {
return await channelsRepository
.insert({
id: idService.gen(),
...data,
})
.then(x => channelsRepository.findOneByOrFail(x.identifiers[0]));
}
async function createChannelMuting(data: Partial<MiChannelMuting> = {}) {
return await channelMutingRepository
.insert({
id: idService.gen(),
...data,
})
.then(x => channelMutingRepository.findOneByOrFail(x.identifiers[0]));
}
async function fetchChannelMuting() {
return await channelMutingRepository.findBy({});
}
async function createDriveFile(data: Partial<MiDriveFile> = {}) {
return await driveFilesRepository
.insert({
id: idService.gen(),
md5: 'md5',
name: 'name',
size: 0,
type: 'type',
storedInternal: false,
url: 'url',
...data,
})
.then(x => driveFilesRepository.findOneByOrFail(x.identifiers[0]));
}
beforeAll(async () => {
app = await Test.createTestingModule({
imports: [
GlobalModule,
CoreModule,
],
providers: [
GlobalEventService,
IdService,
ChannelMutingService,
],
}).compile();
app.enableShutdownHooks();
service = app.get<ChannelMutingService>(ChannelMutingService);
idService = app.get<IdService>(IdService);
channelsRepository = app.get<ChannelsRepository>(DI.channelsRepository);
channelMutingRepository = app.get<ChannelMutingRepository>(DI.channelMutingRepository);
usersRepository = app.get<UsersRepository>(DI.usersRepository);
userProfilesRepository = app.get<UserProfilesRepository>(DI.userProfilesRepository);
driveFilesRepository = app.get<DriveFilesRepository>(DI.driveFilesRepository);
});
afterAll(async () => {
await app.close();
});
beforeEach(async () => {
alice = await createUser({ username: 'alice' });
bob = await createUser({ username: 'bob' });
driveFile1 = await createDriveFile();
driveFile2 = await createDriveFile();
channel1 = await createChannel({ name: 'channel1', userId: alice.id, bannerId: driveFile1.id });
channel2 = await createChannel({ name: 'channel2', userId: alice.id, bannerId: driveFile2.id });
channel3 = await createChannel({ name: 'channel3', userId: alice.id, bannerId: driveFile2.id });
});
afterEach(async () => {
await channelMutingRepository.deleteAll();
await channelsRepository.deleteAll();
await userProfilesRepository.deleteAll();
await usersRepository.deleteAll();
});
describe('list', () => {
test('default', async () => {
await createChannelMuting({ userId: alice.id, channelId: channel1.id });
await createChannelMuting({ userId: alice.id, channelId: channel2.id });
await createChannelMuting({ userId: bob.id, channelId: channel3.id });
const mutings = await service.list({ requestUserId: alice.id });
expect(mutings).toHaveLength(2);
expect(mutings[0].id).toBe(channel1.id);
expect(mutings[0].userId).toBe(alice.id);
expect(mutings[0].user).toBeFalsy();
expect(mutings[0].bannerId).toBe(driveFile1.id);
expect(mutings[0].banner).toBeFalsy();
expect(mutings[1].id).toBe(channel2.id);
expect(mutings[1].userId).toBe(alice.id);
expect(mutings[1].user).toBeFalsy();
expect(mutings[1].bannerId).toBe(driveFile2.id);
expect(mutings[1].banner).toBeFalsy();
});
test('withoutExpires', async () => {
const now = new Date();
const past = new Date(now);
const future = new Date(now);
past.setMinutes(past.getMinutes() - 1);
future.setMinutes(future.getMinutes() + 1);
await createChannelMuting({ userId: alice.id, channelId: channel1.id, expiresAt: past });
await createChannelMuting({ userId: alice.id, channelId: channel2.id, expiresAt: null });
await createChannelMuting({ userId: alice.id, channelId: channel3.id, expiresAt: future });
const mutings = await service.list({ requestUserId: alice.id });
expect(mutings).toHaveLength(2);
expect(mutings[0].id).toBe(channel2.id);
expect(mutings[1].id).toBe(channel3.id);
});
test('idOnly', async () => {
await createChannelMuting({ userId: alice.id, channelId: channel1.id });
await createChannelMuting({ userId: alice.id, channelId: channel2.id });
await createChannelMuting({ userId: bob.id, channelId: channel3.id });
const mutings = await service.list({ requestUserId: alice.id }, { idOnly: true });
expect(mutings).toHaveLength(2);
expect(mutings[0].id).toBe(channel1.id);
expect(mutings[1].id).toBe(channel2.id);
});
test('withoutExpires-idOnly', async () => {
const now = new Date();
const past = new Date(now);
const future = new Date(now);
past.setMinutes(past.getMinutes() - 1);
future.setMinutes(future.getMinutes() + 1);
await createChannelMuting({ userId: alice.id, channelId: channel1.id, expiresAt: past });
await createChannelMuting({ userId: alice.id, channelId: channel2.id, expiresAt: null });
await createChannelMuting({ userId: alice.id, channelId: channel3.id, expiresAt: future });
const mutings = await service.list({ requestUserId: alice.id }, { idOnly: true });
expect(mutings).toHaveLength(2);
expect(mutings[0].id).toBe(channel2.id);
expect(mutings[1].id).toBe(channel3.id);
});
test('joinUser', async () => {
await createChannelMuting({ userId: alice.id, channelId: channel1.id });
await createChannelMuting({ userId: alice.id, channelId: channel2.id });
await createChannelMuting({ userId: bob.id, channelId: channel3.id });
const mutings = await service.list({ requestUserId: alice.id }, { joinUser: true });
expect(mutings).toHaveLength(2);
expect(mutings[0].id).toBe(channel1.id);
expect(mutings[0].user).toEqual(alice);
expect(mutings[0].banner).toBeFalsy();
expect(mutings[1].id).toBe(channel2.id);
expect(mutings[1].user).toEqual(alice);
expect(mutings[1].banner).toBeFalsy();
});
test('joinBannerFile', async () => {
await createChannelMuting({ userId: alice.id, channelId: channel1.id });
await createChannelMuting({ userId: alice.id, channelId: channel2.id });
await createChannelMuting({ userId: bob.id, channelId: channel3.id });
const mutings = await service.list({ requestUserId: alice.id }, { joinBannerFile: true });
expect(mutings).toHaveLength(2);
expect(mutings[0].id).toBe(channel1.id);
expect(mutings[0].user).toBeFalsy();
expect(mutings[0].banner).toEqual(driveFile1);
expect(mutings[1].id).toBe(channel2.id);
expect(mutings[1].user).toBeFalsy();
expect(mutings[1].banner).toEqual(driveFile2);
});
});
describe('findExpiredMutings', () => {
test('default', async () => {
const now = new Date();
const future = new Date(now);
const past = new Date(now);
future.setMinutes(now.getMinutes() + 1);
past.setMinutes(now.getMinutes() - 1);
await createChannelMuting({ userId: alice.id, channelId: channel1.id, expiresAt: past });
await createChannelMuting({ userId: alice.id, channelId: channel2.id, expiresAt: future });
await createChannelMuting({ userId: bob.id, channelId: channel3.id, expiresAt: past });
const mutings = await service.findExpiredMutings();
expect(mutings).toHaveLength(2);
expect(mutings[0].channelId).toBe(channel1.id);
expect(mutings[1].channelId).toBe(channel3.id);
});
});
describe('isMuted', () => {
test('isMuted: true', async () => {
// キャッシュを読むのでServiceの機能を使って登録し、キャッシュを作成する
await service.mute({ requestUserId: alice.id, targetChannelId: channel1.id });
await service.mute({ requestUserId: alice.id, targetChannelId: channel2.id });
await setTimeout(500);
const result = await service.isMuted({ requestUserId: alice.id, targetChannelId: channel1.id });
expect(result).toBe(true);
});
test('isMuted: false', async () => {
await service.mute({ requestUserId: alice.id, targetChannelId: channel2.id });
await setTimeout(500);
const result = await service.isMuted({ requestUserId: alice.id, targetChannelId: channel1.id });
expect(result).toBe(false);
});
});
describe('mute', () => {
test('default', async () => {
await service.mute({ requestUserId: alice.id, targetChannelId: channel1.id });
const muting = await fetchChannelMuting();
expect(muting).toHaveLength(1);
expect(muting[0].channelId).toBe(channel1.id);
});
});
describe('unmute', () => {
test('default', async () => {
await createChannelMuting({ userId: alice.id, channelId: channel1.id });
let muting = await fetchChannelMuting();
expect(muting).toHaveLength(1);
expect(muting[0].channelId).toBe(channel1.id);
await service.unmute({ requestUserId: alice.id, targetChannelId: channel1.id });
muting = await fetchChannelMuting();
expect(muting).toHaveLength(0);
});
});
describe('eraseExpiredMutings', () => {
test('default', async () => {
const now = new Date();
const future = new Date(now);
const past = new Date(now);
future.setMinutes(now.getMinutes() + 1);
past.setMinutes(now.getMinutes() - 1);
await createChannelMuting({ userId: alice.id, channelId: channel1.id, expiresAt: past });
await createChannelMuting({ userId: alice.id, channelId: channel2.id, expiresAt: future });
await createChannelMuting({ userId: bob.id, channelId: channel3.id, expiresAt: past });
await service.eraseExpiredMutings();
const mutings = await fetchChannelMuting();
expect(mutings).toHaveLength(1);
expect(mutings[0].channelId).toBe(channel2.id);
});
});
});

View File

@@ -61,6 +61,7 @@ describe('NoteCreateService', () => {
replyUserHost: null,
renoteUserId: null,
renoteUserHost: null,
renoteChannelId: null,
};
const poll: IPoll = {

View File

@@ -6,7 +6,7 @@
process.env.NODE_ENV = 'test';
import { setTimeout } from 'node:timers/promises';
import { jest } from '@jest/globals';
import { describe, jest } from '@jest/globals';
import { ModuleMocker } from 'jest-mock';
import { Test } from '@nestjs/testing';
import * as lolex from '@sinonjs/fake-timers';
@@ -160,16 +160,75 @@ describe('RoleService', () => {
afterEach(async () => {
clock.uninstall();
/**
* Delete meta and roleAssignment first to avoid deadlock due to schema dependencies
* https://github.com/misskey-dev/misskey/issues/16783
*/
await app.get(DI.metasRepository).createQueryBuilder().delete().execute();
await roleAssignmentsRepository.createQueryBuilder().delete().execute();
await Promise.all([
app.get(DI.metasRepository).createQueryBuilder().delete().execute(),
usersRepository.createQueryBuilder().delete().execute(),
rolesRepository.createQueryBuilder().delete().execute(),
roleAssignmentsRepository.createQueryBuilder().delete().execute(),
]);
await app.close();
});
describe('getUserAssigns', () => {
test('アサインされたロールを取得できる', async () => {
const user = await createUser();
const role1 = await createRole({ name: 'a' });
const role2 = await createRole({ name: 'b' });
await roleService.assign(user.id, role1.id);
await roleService.assign(user.id, role2.id);
const assigns = await roleService.getUserAssigns(user.id);
expect(assigns).toHaveLength(2);
expect(assigns.some(a => a.roleId === role1.id)).toBe(true);
expect(assigns.some(a => a.roleId === role2.id)).toBe(true);
});
test('アサインされたロールの有効/期限切れパターンを取得できる', async () => {
const user = await createUser();
const roleNoExpiry = await createRole({ name: 'no-expires' });
const roleNotExpired = await createRole({ name: 'not-expired' });
const roleExpired = await createRole({ name: 'expired' });
// expiresAtなし
await roleService.assign(user.id, roleNoExpiry.id);
// expiresAtあり期限切れでない
const future = new Date(Date.now() + 1000 * 60 * 60); // +1 hour
await roleService.assign(user.id, roleNotExpired.id, future);
// expiresAtあり期限切れ
await assignRole({ userId: user.id, roleId: roleExpired.id, expiresAt: new Date(Date.now() - 1000) });
const assigns = await roleService.getUserAssigns(user.id);
expect(assigns.some(a => a.roleId === roleNoExpiry.id)).toBe(true);
expect(assigns.some(a => a.roleId === roleNotExpired.id)).toBe(true);
expect(assigns.some(a => a.roleId === roleExpired.id)).toBe(false);
});
});
describe('getUserRoles', () => {
test('アサインされたロールとコンディショナルロールの両方が取得できる', async () => {
const user = await createUser();
const manualRole = await createRole({ name: 'manual role' });
const conditionalRole = await createConditionalRole({
id: aidx(),
type: 'isBot',
});
await roleService.assign(user.id, manualRole.id);
await roleService.assign(user.id, conditionalRole.id);
const roles = await roleService.getUserRoles(user.id);
expect(roles.some(r => r.id === manualRole.id)).toBe(true);
expect(roles.some(r => r.id === conditionalRole.id)).toBe(true);
});
});
describe('getUserPolicies', () => {
test('instance default policies', async () => {
const user = await createUser();
@@ -282,6 +341,112 @@ describe('RoleService', () => {
const resultAfter25hAgain = await roleService.getUserPolicies(user.id);
expect(resultAfter25hAgain.canManageCustomEmojis).toBe(true);
});
test('role with no policy set', async () => {
const user = await createUser();
const roleWithPolicy = await createRole({
name: 'roleWithPolicy',
policies: {
pinLimit: {
useDefault: false,
priority: 0,
value: 10,
},
},
});
const roleWithoutPolicy = await createRole({
name: 'roleWithoutPolicy',
policies: {}, // ポリシーが空
});
await roleService.assign(user.id, roleWithPolicy.id);
await roleService.assign(user.id, roleWithoutPolicy.id);
meta.policies = {
pinLimit: 5,
};
const result = await roleService.getUserPolicies(user.id);
// roleWithoutPolicy は default 値 (5) を使い、roleWithPolicy の 10 と比較して大きい方が採用される
expect(result.pinLimit).toBe(10);
});
});
describe('getUserBadgeRoles', () => {
test('手動アサイン済みのバッジロールのみが返る', async () => {
const user = await createUser();
const badgeRole = await createRole({ name: 'badge', asBadge: true });
const normalRole = await createRole({ name: 'normal', asBadge: false });
await roleService.assign(user.id, badgeRole.id);
await roleService.assign(user.id, normalRole.id);
const roles = await roleService.getUserBadgeRoles(user.id);
expect(roles.some(r => r.id === badgeRole.id)).toBe(true);
expect(roles.some(r => r.id === normalRole.id)).toBe(false);
});
test('コンディショナルなバッジロールが条件一致で返る', async () => {
const user = await createUser({ isBot: true });
const condBadgeRole = await createConditionalRole({
id: aidx(),
type: 'isBot',
}, { asBadge: true, name: 'cond-badge' });
const condNonBadgeRole = await createConditionalRole({
id: aidx(),
type: 'isBot',
}, { asBadge: false, name: 'cond-non-badge' });
const roles = await roleService.getUserBadgeRoles(user.id);
expect(roles.some(r => r.id === condBadgeRole.id)).toBe(true);
expect(roles.some(r => r.id === condNonBadgeRole.id)).toBe(false);
});
test('roleAssignedTo 条件のバッジロール: アサイン有無で変化する', async () => {
const [user1, user2] = await Promise.all([createUser(), createUser()]);
const manualRole = await createRole({ name: 'manual' });
const condBadgeRole = await createConditionalRole({
id: aidx(),
type: 'roleAssignedTo',
roleId: manualRole.id,
}, { asBadge: true, name: 'assigned-badge' });
await roleService.assign(user2.id, manualRole.id);
const [roles1, roles2] = await Promise.all([
roleService.getUserBadgeRoles(user1.id),
roleService.getUserBadgeRoles(user2.id),
]);
expect(roles1.some(r => r.id === condBadgeRole.id)).toBe(false);
expect(roles2.some(r => r.id === condBadgeRole.id)).toBe(true);
});
test('期限切れのバッジロールは除外される', async () => {
const user = await createUser();
const roleNoExpiry = await createRole({ name: 'no-exp', asBadge: true });
const roleNotExpired = await createRole({ name: 'not-expired', asBadge: true });
const roleExpired = await createRole({ name: 'expired', asBadge: true });
// expiresAt なし
await roleService.assign(user.id, roleNoExpiry.id);
// expiresAt あり(期限切れでない)
const future = new Date(Date.now() + 1000 * 60 * 60); // +1 hour
await roleService.assign(user.id, roleNotExpired.id, future);
// expiresAt あり(期限切れ)
await assignRole({ userId: user.id, roleId: roleExpired.id, expiresAt: new Date(Date.now() - 1000) });
const rolesBefore = await roleService.getUserBadgeRoles(user.id);
expect(rolesBefore.some(r => r.id === roleNoExpiry.id)).toBe(true);
expect(rolesBefore.some(r => r.id === roleNotExpired.id)).toBe(true);
expect(rolesBefore.some(r => r.id === roleExpired.id)).toBe(false);
// 時間経過で roleNotExpired を失効させる
clock.tick('02:00:00');
const rolesAfter = await roleService.getUserBadgeRoles(user.id);
expect(rolesAfter.some(r => r.id === roleNoExpiry.id)).toBe(true);
expect(rolesAfter.some(r => r.id === roleNotExpired.id)).toBe(false);
});
});
describe('getModeratorIds', () => {
@@ -415,9 +580,9 @@ describe('RoleService', () => {
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(), createRoot(),
test('includeAdmins = false, includeRoot = true, excludeExpire = true', async () => {
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -426,9 +591,11 @@ describe('RoleService', () => {
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: rootUser.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({
@@ -436,12 +603,12 @@ describe('RoleService', () => {
includeRoot: true,
excludeExpire: false,
});
expect(result).toEqual([modeUser1.id, rootUser.id]);
expect(result).toEqual([modeUser1.id, modeUser2.id, rootUser.id]);
});
test('root has administrator role', async () => {
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
createUser(), createUser(), createUser(), createRoot(),
test('includeAdmins = true, includeRoot = true, excludeExpire = false', async () => {
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -450,9 +617,11 @@ describe('RoleService', () => {
await Promise.all([
assignRole({ userId: adminUser1.id, roleId: role1.id }),
assignRole({ userId: rootUser.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({
@@ -460,12 +629,12 @@ describe('RoleService', () => {
includeRoot: true,
excludeExpire: false,
});
expect(result).toEqual([adminUser1.id, modeUser1.id, rootUser.id]);
expect(result).toEqual([adminUser1.id, adminUser2.id, modeUser1.id, modeUser2.id, rootUser.id]);
});
test('root has moderator role(expire)', async () => {
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
createUser(), createUser(), createUser(), createRoot(),
test('includeAdmins = true, includeRoot = true, excludeExpire = true', async () => {
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -474,17 +643,71 @@ describe('RoleService', () => {
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: 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,
includeAdmins: true,
includeRoot: true,
excludeExpire: true,
});
expect(result).toEqual([rootUser.id]);
expect(result).toEqual([adminUser1.id, modeUser1.id, rootUser.id]);
});
});
describe('getAdministratorIds', () => {
test('should return only user IDs with administrator roles', async () => {
const adminUser1 = await createUser();
const adminUser2 = await createUser();
const normalUser = await createUser();
const moderatorUser = await createUser();
const adminRole = await createRole({ name: 'admin', isAdministrator: true, isModerator: false });
const moderatorRole = await createRole({ name: 'moderator', isModerator: true, isAdministrator: false });
const normalRole = await createRole({ name: 'normal', isAdministrator: false, isModerator: false });
await roleService.assign(adminUser1.id, adminRole.id);
await roleService.assign(adminUser2.id, adminRole.id);
await roleService.assign(moderatorUser.id, moderatorRole.id);
await roleService.assign(normalUser.id, normalRole.id);
const adminIds = await roleService.getAdministratorIds();
// sort for deterministic order
adminIds.sort();
const expectedIds = [adminUser1.id, adminUser2.id].sort();
expect(adminIds).toEqual(expectedIds);
});
test('should return an empty array if no users have administrator roles', async () => {
const normalUser = await createUser();
const normalRole = await createRole({ name: 'normal', isAdministrator: false });
await roleService.assign(normalUser.id, normalRole.id);
const adminIds = await roleService.getAdministratorIds();
expect(adminIds).toHaveLength(0);
});
test('should return an empty array if there are no administrator roles defined', async () => {
await createUser(); // create user to ensure not empty db
const adminIds = await roleService.getAdministratorIds();
expect(adminIds).toHaveLength(0);
});
// TODO: rootユーザーは現在実装に含まれていないため、テストもそれに倣う
test('should not include the root user', async () => {
const rootUser = await createUser();
meta.rootUserId = rootUser.id;
const adminIds = await roleService.getAdministratorIds();
expect(adminIds).not.toContain(rootUser.id);
});
});

View File

@@ -44,6 +44,7 @@ const base: MiNote = {
replyUserHost: null,
renoteUserId: null,
renoteUserHost: null,
renoteChannelId: null,
};
describe('misc:is-renote', () => {