mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
126 lines
4.2 KiB
TypeScript
126 lines
4.2 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
import {type ChannelID, createChannelID, createGuildID} from '@app/api/BrandedTypes';
|
|
import {MessageContentService} from '@app/api/channel/services/message/MessageContentService';
|
|
import {maskThreadArtifacts} from '@app/api/channel/services/message/ThreadMessageResponses';
|
|
import {resolveNsfwScopeChannel} from '@app/api/channel/utils/ThreadNsfwScope';
|
|
import type {ChannelRow} from '@app/api/database/types/ChannelTypes';
|
|
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
|
import type {LimitConfigService} from '@app/api/limits/LimitConfigService';
|
|
import {Channel} from '@app/api/models/Channel';
|
|
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
|
import {ChannelTypes, MessageTypes} from '@fluxer/constants/src/ChannelConstants';
|
|
import {GuildExplicitContentFilterTypes} from '@fluxer/constants/src/GuildConstants';
|
|
import {ServerMessageFlags} from '@fluxer/constants/src/ThreadConstants';
|
|
import type {GuildResponse} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas';
|
|
import type {MessageResponse} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
|
|
import {describe, expect, it} from 'vitest';
|
|
|
|
const GUILD_ID = createGuildID(1_920_000_000_000_000_000n);
|
|
|
|
function channel(id: bigint, type: number, overrides: Partial<ChannelRow> = {}): Channel {
|
|
return new Channel({
|
|
channel_id: createChannelID(id),
|
|
guild_id: GUILD_ID,
|
|
type,
|
|
name: `c${id}`,
|
|
topic: null,
|
|
icon_hash: null,
|
|
url: null,
|
|
parent_id: null,
|
|
position: 0,
|
|
owner_id: null,
|
|
recipient_ids: null,
|
|
nsfw: null,
|
|
rate_limit_per_user: 0,
|
|
bitrate: null,
|
|
user_limit: null,
|
|
voice_connection_limit: null,
|
|
rtc_region: null,
|
|
rtc_p2p: null,
|
|
last_message_id: null,
|
|
last_pin_timestamp: null,
|
|
permission_overwrites: null,
|
|
nicks: null,
|
|
soft_deleted: false,
|
|
indexed_at: null,
|
|
version: 1,
|
|
...overrides,
|
|
});
|
|
}
|
|
|
|
function message(overrides: Partial<MessageResponse>): MessageResponse {
|
|
return {
|
|
id: '10',
|
|
channel_id: '1',
|
|
author: {
|
|
id: '2',
|
|
username: 'a',
|
|
discriminator: '0001',
|
|
global_name: null,
|
|
avatar: null,
|
|
avatar_color: null,
|
|
flags: 0,
|
|
},
|
|
type: MessageTypes.DEFAULT,
|
|
flags: 0,
|
|
content: '',
|
|
timestamp: new Date(0).toISOString(),
|
|
pinned: false,
|
|
mention_everyone: false,
|
|
tts: false,
|
|
mentions: [],
|
|
mention_roles: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('thread NSFW scope', () => {
|
|
const contentService = new MessageContentService(
|
|
{} as UserRepository,
|
|
{} as GuildRepository,
|
|
{} as LimitConfigService,
|
|
);
|
|
const guild = {
|
|
nsfw_level: 0,
|
|
features: [],
|
|
explicit_content_filter: GuildExplicitContentFilterTypes.ALL_MEMBERS,
|
|
} as unknown as GuildResponse;
|
|
|
|
it('resolves a thread to its parent and leaves other channels alone', async () => {
|
|
const parent = channel(1n, ChannelTypes.GUILD_TEXT, {nsfw: true});
|
|
const thread = channel(2n, ChannelTypes.PUBLIC_THREAD, {parent_id: parent.id});
|
|
const find = async (id: ChannelID) => (id === parent.id ? parent : null);
|
|
expect(await resolveNsfwScopeChannel(thread, find)).toBe(parent);
|
|
expect(await resolveNsfwScopeChannel(parent, find)).toBe(parent);
|
|
expect(contentService.isNSFWContentAllowed({channel: thread, guild})).toBe(false);
|
|
expect(contentService.isNSFWContentAllowed({channel: await resolveNsfwScopeChannel(thread, find), guild})).toBe(
|
|
true,
|
|
);
|
|
});
|
|
|
|
it('treats forum and media parents like text for NSFW', () => {
|
|
expect(
|
|
contentService.isNSFWContentAllowed({channel: channel(3n, ChannelTypes.GUILD_FORUM, {nsfw: true}), guild}),
|
|
).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('thread artifact masking', () => {
|
|
it('drops thread created messages and clears thread bits including nested ones', () => {
|
|
const masked = maskThreadArtifacts([
|
|
message({id: '1', type: MessageTypes.THREAD_CREATED}),
|
|
message({
|
|
id: '2',
|
|
flags: ServerMessageFlags.HAS_THREAD,
|
|
referenced_message: message({id: '3', flags: ServerMessageFlags.FAILED_TO_MENTION_SOME_ROLES_IN_THREAD}),
|
|
}),
|
|
message({id: '4', referenced_message: message({id: '5', type: MessageTypes.THREAD_CREATED})}),
|
|
]);
|
|
expect(masked.map((m) => m.id)).toEqual(['2', '4']);
|
|
expect(masked[0]?.flags).toBe(0);
|
|
expect(masked[0]?.referenced_message?.flags).toBe(0);
|
|
expect(masked[1]?.referenced_message).toBeNull();
|
|
});
|
|
});
|