// SPDX-License-Identifier: AGPL-3.0-or-later import {createTestAccount, type TestAccount} from '@app/api/auth/tests/AuthTestUtils'; import {createWebhookID} from '@app/api/BrandedTypes'; import {resetApiServicesForTesting} from '@app/api/CreateApiContext'; import { acceptInvite, addMemberRole, createChannelInvite, createGuild, createPermissionOverwrite, createRole, } from '@app/api/channel/tests/ChannelTestUtils'; import {createTestGuildStack} from '@app/api/channel/tests/CrosspostTestUtils'; import {getGatewayService, getSnowflakeService, setInjectedWorkerService} from '@app/api/middleware/ServiceRegistry'; import { getAvatarService, getCacheService, getChannelRepository, getGuildRepository, getLimitConfigService, getPurgeQueue, getStorageService, getUserRepository, getWebhookRepository, } from '@app/api/middleware/ServiceSingletons'; import type {ApiTestHarness} from '@app/api/test/ApiTestHarness'; import {NoopWorkerService} from '@app/api/test/NoopWorkerService'; import {SyncTaskWorkerService} from '@app/api/test/SyncTaskWorkerService'; import {createBuilder} from '@app/api/test/TestRequestBuilder'; import {WebhookRepository} from '@app/api/webhook/WebhookRepository'; import crosspostMessage from '@app/api/worker/tasks/CrosspostMessage'; import crosspostMessageChunk from '@app/api/worker/tasks/CrosspostMessageChunk'; import guildProcessDeletion from '@app/api/worker/tasks/GuildProcessDeletion'; import removeChannelFollowers from '@app/api/worker/tasks/RemoveChannelFollowers'; import syncCrosspostCopies from '@app/api/worker/tasks/SyncCrosspostCopies'; import syncCrosspostedMessage from '@app/api/worker/tasks/SyncCrosspostedMessage'; import {clearWorkerDependencies, setWorkerDependenciesForTest} from '@app/api/worker/WorkerContext'; import type {WorkerDependencies} from '@app/api/worker/WorkerDependencies'; import {ChannelTypes, Permissions} from '@fluxer/constants/src/ChannelConstants'; import {ContentWarningLevel} from '@fluxer/constants/src/GuildConstants'; import type {FollowedChannelResponse} from '@fluxer/schema/src/domains/channel/ChannelFollowSchemas'; import type {ChannelResponse} from '@fluxer/schema/src/domains/channel/ChannelSchemas'; import type {GuildResponse} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas'; import type {WorkerTaskHandler} from '@pkgs/worker/src/contracts/WorkerTask'; export interface AnnouncementSourceGuild { owner: TestAccount; member: TestAccount; moderator: TestAccount; guild: GuildResponse; ann: ChannelResponse; } export interface AnnouncementTargetGuild { owner: TestAccount; webhookManager: TestAccount; guild: GuildResponse; t1: ChannelResponse; t2: ChannelResponse; voice: ChannelResponse; ageRestricted: ChannelResponse; contentWarning: ChannelResponse; } export interface AnnouncementWorld { a: AnnouncementSourceGuild; b: AnnouncementTargetGuild; } export async function createGuildChannel( harness: ApiTestHarness, token: string, guildId: string, body: Record, ): Promise { return createBuilder(harness, token).post(`/guilds/${guildId}/channels`).body(body).execute(); } export async function addGuildMember( harness: ApiTestHarness, owner: TestAccount, guild: GuildResponse, member: TestAccount, ): Promise { const invite = await createChannelInvite(harness, owner.token, guild.system_channel_id!); await acceptInvite(harness, member.token, invite.code); } export async function grantGuildRole( harness: ApiTestHarness, owner: TestAccount, guildId: string, member: TestAccount, permissions: bigint, name = 'Announcement role', ): Promise { const role = await createRole(harness, owner.token, guildId, { name, permissions: (Permissions.VIEW_CHANNEL | Permissions.SEND_MESSAGES | permissions).toString(), }); await addMemberRole(harness, owner.token, guildId, member.userId, role.id); return role.id; } export async function createAnnouncementSourceGuild( harness: ApiTestHarness, name = 'Announcement Source', ): Promise { const owner = await createTestAccount(harness); const member = await createTestAccount(harness); const moderator = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, name); const ann = await createGuildChannel(harness, owner.token, guild.id, { name: 'news', type: ChannelTypes.GUILD_ANNOUNCEMENT, }); await addGuildMember(harness, owner, guild, member); await addGuildMember(harness, owner, guild, moderator); await grantGuildRole(harness, owner, guild.id, moderator, Permissions.MANAGE_MESSAGES, 'Announcement moderator'); return {owner, member, moderator, guild, ann}; } export async function createAnnouncementTargetGuild( harness: ApiTestHarness, name = 'Announcement Target', ): Promise { const owner = await createTestAccount(harness); const webhookManager = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, name); const t1 = await createGuildChannel(harness, owner.token, guild.id, {name: 't1', type: ChannelTypes.GUILD_TEXT}); const t2 = await createGuildChannel(harness, owner.token, guild.id, {name: 't2', type: ChannelTypes.GUILD_TEXT}); const voice = await createGuildChannel(harness, owner.token, guild.id, { name: 'voice', type: ChannelTypes.GUILD_VOICE, }); const ageRestricted = await createGuildChannel(harness, owner.token, guild.id, { name: 'restricted', type: ChannelTypes.GUILD_TEXT, nsfw_override: true, }); const contentWarning = await createGuildChannel(harness, owner.token, guild.id, { name: 'warned', type: ChannelTypes.GUILD_TEXT, content_warning_level: ContentWarningLevel.CONTENT_WARNING, }); await addGuildMember(harness, owner, guild, webhookManager); await createPermissionOverwrite(harness, owner.token, t1.id, webhookManager.userId, { type: 1, allow: Permissions.MANAGE_WEBHOOKS.toString(), deny: '0', }); return {owner, webhookManager, guild, t1, t2, voice, ageRestricted, contentWarning}; } export async function announcementWorld(harness: ApiTestHarness): Promise { const a = await createAnnouncementSourceGuild(harness); const b = await createAnnouncementTargetGuild(harness); await addGuildMember(harness, a.owner, a.guild, b.owner); await addGuildMember(harness, a.owner, a.guild, b.webhookManager); return {a, b}; } export function followRequest( harness: ApiTestHarness, token: string, sourceChannelId: string, targetChannelId: string, ) { return createBuilder(harness, token) .post(`/channels/${sourceChannelId}/followers`) .body({webhook_channel_id: targetChannelId}); } export async function follow( harness: ApiTestHarness, token: string, sourceChannelId: string, targetChannelId: string, ): Promise { return followRequest(harness, token, sourceChannelId, targetChannelId).execute(); } export async function findWebhookRow(webhookId: string) { return new WebhookRepository().findUnique(createWebhookID(BigInt(webhookId))); } export async function setGuildFeatures( harness: ApiTestHarness, guildId: string, features: {add?: Array; remove?: Array}, ): Promise { await createBuilder(harness, '') .post(`/test/guilds/${guildId}/features`) .body({add_features: features.add ?? [], remove_features: features.remove ?? []}) .execute(); } export const crosspostTaskHandlers: Record = { crosspostMessage, crosspostMessageChunk, syncCrosspostedMessage, syncCrosspostCopies, removeChannelFollowers, guildProcessDeletion, }; export function enableCrosspostWorker( extraHandlers: Record = {}, extraDependencies: Partial = {}, ): SyncTaskWorkerService { const worker = new SyncTaskWorkerService({...crosspostTaskHandlers, ...extraHandlers}, {deferred: true}); setInjectedWorkerService(worker); resetApiServicesForTesting(); const guildStack = createTestGuildStack(); setWorkerDependenciesForTest({ channelRepository: getChannelRepository(), webhookRepository: getWebhookRepository(), userRepository: getUserRepository(), guildRepository: getGuildRepository(), gatewayService: getGatewayService(), storageService: getStorageService(), avatarService: getAvatarService(), purgeQueue: getPurgeQueue(), snowflakeService: getSnowflakeService(), cacheService: getCacheService(), limitConfigService: getLimitConfigService(), channelService: guildStack.channelService, guildService: guildStack.guildService, workerService: worker, ...extraDependencies, }); return worker; } export function disableCrosspostWorker(): void { setInjectedWorkerService(new NoopWorkerService()); resetApiServicesForTesting(); clearWorkerDependencies(); } export {crosspostRequest as publishRequest, publish} from '@app/api/channel/tests/CrosspostTestUtils';