mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
chore(repo): remove dead code and useless tests (#3375)
This commit is contained in:
1 parent
7e177a1c90
commit
1cd62eb661
1391 files changed
+783
-120415
No files matched your search
@@ -7,8 +7,6 @@ import {
|
||||
StorageChangeFeed,
|
||||
type StorageChangeSink,
|
||||
} from '@app/api/infrastructure/StorageChangeFeed';
|
||||
import {StorageService} from '@app/api/infrastructure/StorageService';
|
||||
import {createStorageService, shutdownStorageChangeFeed} from '@app/api/infrastructure/StorageServiceFactory';
|
||||
import {MockStorageService} from '@app/api/test/mocks/MockStorageService';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
@@ -201,22 +199,3 @@ describe('ChangeFeedStorageService', () => {
|
||||
expect(feed.stats()).toEqual({queued: 1, published: 0, dropped: 2, failed: 0});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createStorageService', () => {
|
||||
it('returns the plain storage service while the change feed is disabled', () => {
|
||||
expect(Config.storageChangeFeed.enabled).toBe(false);
|
||||
expect(Config.storageChangeFeed.skipBuckets).toEqual([Config.s3.buckets.uploads]);
|
||||
expect(createStorageService()).toBeInstanceOf(StorageService);
|
||||
});
|
||||
|
||||
it('wraps the storage service once the change feed is enabled', async () => {
|
||||
const original = Config.storageChangeFeed.enabled;
|
||||
Config.storageChangeFeed.enabled = true;
|
||||
try {
|
||||
expect(createStorageService()).toBeInstanceOf(ChangeFeedStorageService);
|
||||
} finally {
|
||||
Config.storageChangeFeed.enabled = original;
|
||||
await shutdownStorageChangeFeed();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {getConfig} from '@app/api/Config';
|
||||
import {ErrorI18nService} from '@app/api/infrastructure/ErrorI18nService';
|
||||
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
|
||||
import {StripeError} from '@fluxer/errors/src/domains/payment/StripeError';
|
||||
import {afterEach, describe, expect, it} from 'vitest';
|
||||
|
||||
describe('ErrorI18nService', () => {
|
||||
const originalSelfHosted = getConfig().instance.selfHosted;
|
||||
const service = new ErrorI18nService();
|
||||
|
||||
function renderStripeError(locale: string): string {
|
||||
const error = new StripeError('Stripe checkout session missing url');
|
||||
return service.getMessage(error.code, locale, error.messageVariables, error.message);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
getConfig().instance.selfHosted = originalSelfHosted;
|
||||
});
|
||||
|
||||
it('tells a hosted user to contact support when payment processing fails', () => {
|
||||
getConfig().instance.selfHosted = false;
|
||||
expect(renderStripeError('en-US')).toBe(
|
||||
'Payment processing encountered an error. Please try again or contact support.',
|
||||
);
|
||||
});
|
||||
|
||||
it('points a self-hosted user at the instance administrators when payment processing fails', () => {
|
||||
getConfig().instance.selfHosted = true;
|
||||
expect(renderStripeError('en-US')).toBe(
|
||||
'Payment processing encountered an error. Please try again or contact the administrators of this instance.',
|
||||
);
|
||||
expect(renderStripeError('de')).toBe(
|
||||
'Bei der Zahlungsabwicklung ist ein Fehler aufgetreten. Versuch es noch mal oder kontaktiere die Administratoren dieser Instanz.',
|
||||
);
|
||||
});
|
||||
|
||||
it('lets a variable passed by the error win over the instance default', () => {
|
||||
getConfig().instance.selfHosted = true;
|
||||
expect(service.getMessage(APIErrorCodes.STRIPE_ERROR, 'en-US', {supportEmail: '[email protected]'})).toBe(
|
||||
'Payment processing encountered an error. Please try again or contact support.',
|
||||
);
|
||||
});
|
||||
|
||||
it('leaves messages without the variable unchanged', () => {
|
||||
getConfig().instance.selfHosted = true;
|
||||
expect(service.getMessage(APIErrorCodes.RATE_LIMITED, 'en-US')).toBe("You're being rate limited.");
|
||||
});
|
||||
});
|
||||
@@ -152,10 +152,6 @@ export class GatewayRpcClient {
|
||||
return this.call('call.stop_ringing', {channel_id: channelId, recipients});
|
||||
}
|
||||
|
||||
async deleteCall(channelId: string): Promise<boolean> {
|
||||
return this.call('call.delete', {channel_id: channelId});
|
||||
}
|
||||
|
||||
async getNodeStats(): Promise<unknown> {
|
||||
return this.call('process.node_stats', {});
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
callCallerRpcParams,
|
||||
type GatewayChannelMention,
|
||||
type GatewayGuildMemoryStats,
|
||||
type GatewayMentionSources,
|
||||
type GatewayMentionSourcesPage,
|
||||
type GatewayNodeStats,
|
||||
type GatewayVoiceStateCounts,
|
||||
@@ -153,26 +152,6 @@ interface ChannelCountParams {
|
||||
guildId: GuildID;
|
||||
}
|
||||
|
||||
interface UsersToMentionByRolesParams {
|
||||
guildId: GuildID;
|
||||
channelId: ChannelID;
|
||||
roleIds: Array<RoleID>;
|
||||
authorId: UserID;
|
||||
}
|
||||
|
||||
interface UsersToMentionByUserIdsParams {
|
||||
guildId: GuildID;
|
||||
channelId: ChannelID;
|
||||
userIds: Array<UserID>;
|
||||
authorId: UserID;
|
||||
}
|
||||
|
||||
interface AllUsersToMentionParams {
|
||||
guildId: GuildID;
|
||||
channelId: ChannelID;
|
||||
authorId: UserID;
|
||||
}
|
||||
|
||||
interface ResolveAllMentionsParams {
|
||||
guildId: GuildID;
|
||||
channelId: ChannelID;
|
||||
@@ -219,12 +198,6 @@ interface UpdateMemberVoiceParams {
|
||||
deaf: boolean;
|
||||
}
|
||||
|
||||
interface DisconnectVoiceUserParams {
|
||||
guildId: GuildID;
|
||||
userId: UserID;
|
||||
connectionId: string | null;
|
||||
}
|
||||
|
||||
interface MoveMemberParams {
|
||||
guildId: GuildID;
|
||||
moderatorId: UserID;
|
||||
@@ -1212,103 +1185,6 @@ export class GatewayService {
|
||||
return result.channel_ids.map((id: string) => createChannelID(BigInt(id)));
|
||||
}
|
||||
|
||||
async getUsersToMentionByRoles({
|
||||
guildId,
|
||||
channelId,
|
||||
roleIds,
|
||||
authorId,
|
||||
}: UsersToMentionByRolesParams): Promise<Array<UserID>> {
|
||||
const result = await this.call<{
|
||||
user_ids: Array<string>;
|
||||
}>('guild.get_users_to_mention_by_roles', {
|
||||
guild_id: guildId.toString(),
|
||||
channel_id: channelId.toString(),
|
||||
role_ids: roleIds.map((id) => id.toString()),
|
||||
author_id: authorId.toString(),
|
||||
});
|
||||
return result.user_ids.map((id: string) => createUserID(BigInt(id)));
|
||||
}
|
||||
|
||||
async getUsersToMentionByUserIds({
|
||||
guildId,
|
||||
channelId,
|
||||
userIds,
|
||||
authorId,
|
||||
}: UsersToMentionByUserIdsParams): Promise<Array<UserID>> {
|
||||
const result = await this.call<{
|
||||
user_ids: Array<string>;
|
||||
}>('guild.get_users_to_mention_by_user_ids', {
|
||||
guild_id: guildId.toString(),
|
||||
channel_id: channelId.toString(),
|
||||
user_ids: userIds.map((id) => id.toString()),
|
||||
author_id: authorId.toString(),
|
||||
});
|
||||
return result.user_ids.map((id: string) => createUserID(BigInt(id)));
|
||||
}
|
||||
|
||||
async getAllUsersToMention({guildId, channelId, authorId}: AllUsersToMentionParams): Promise<Array<UserID>> {
|
||||
const result = await this.call<{
|
||||
user_ids: Array<string>;
|
||||
}>('guild.get_all_users_to_mention', {
|
||||
guild_id: guildId.toString(),
|
||||
channel_id: channelId.toString(),
|
||||
author_id: authorId.toString(),
|
||||
});
|
||||
return result.user_ids.map((id: string) => createUserID(BigInt(id)));
|
||||
}
|
||||
|
||||
async resolveAllMentions({
|
||||
guildId,
|
||||
channelId,
|
||||
authorId,
|
||||
mentionEveryone,
|
||||
mentionHere,
|
||||
roleIds,
|
||||
userIds,
|
||||
}: ResolveAllMentionsParams): Promise<Array<UserID>> {
|
||||
const result = await this.call<{
|
||||
user_ids: Array<string>;
|
||||
}>('guild.resolve_all_mentions', {
|
||||
guild_id: guildId.toString(),
|
||||
channel_id: channelId.toString(),
|
||||
author_id: authorId.toString(),
|
||||
mention_everyone: mentionEveryone,
|
||||
mention_here: mentionHere,
|
||||
role_ids: roleIds.map((id) => id.toString()),
|
||||
user_ids: userIds.map((id) => id.toString()),
|
||||
});
|
||||
return result.user_ids.map((id: string) => createUserID(BigInt(id)));
|
||||
}
|
||||
|
||||
async resolveMentionSources({
|
||||
guildId,
|
||||
channelId,
|
||||
authorId,
|
||||
mentionEveryone,
|
||||
mentionHere,
|
||||
roleIds,
|
||||
userIds,
|
||||
}: ResolveAllMentionsParams): Promise<GatewayMentionSources> {
|
||||
const result = await this.call<{
|
||||
direct_user_ids: Array<string>;
|
||||
role_user_ids: Array<string>;
|
||||
everyone_user_ids: Array<string>;
|
||||
}>('guild.resolve_mention_sources', {
|
||||
guild_id: guildId.toString(),
|
||||
channel_id: channelId.toString(),
|
||||
author_id: authorId.toString(),
|
||||
mention_everyone: mentionEveryone,
|
||||
mention_here: mentionHere,
|
||||
role_ids: roleIds.map((id) => id.toString()),
|
||||
user_ids: userIds.map((id) => id.toString()),
|
||||
});
|
||||
return {
|
||||
directUserIds: result.direct_user_ids.map((id: string) => createUserID(BigInt(id))),
|
||||
roleUserIds: result.role_user_ids.map((id: string) => createUserID(BigInt(id))),
|
||||
everyoneUserIds: result.everyone_user_ids.map((id: string) => createUserID(BigInt(id))),
|
||||
};
|
||||
}
|
||||
|
||||
async resolveMentionSourcesPage({
|
||||
guildId,
|
||||
channelId,
|
||||
@@ -1366,15 +1242,6 @@ export class GatewayService {
|
||||
return result.channels ?? [];
|
||||
}
|
||||
|
||||
async getVanityUrlChannel(guildId: GuildID): Promise<ChannelID | null> {
|
||||
const result = await this.call<{
|
||||
channel_id: string | null;
|
||||
}>('guild.get_vanity_url_channel', {
|
||||
guild_id: guildId.toString(),
|
||||
});
|
||||
return result.channel_id ? createChannelID(BigInt(result.channel_id)) : null;
|
||||
}
|
||||
|
||||
async getFirstViewableTextChannel(guildId: GuildID): Promise<ChannelID | null> {
|
||||
const result = await this.call<{
|
||||
channel_id: string | null;
|
||||
@@ -1425,14 +1292,6 @@ export class GatewayService {
|
||||
return {success: result.success};
|
||||
}
|
||||
|
||||
async disconnectVoiceUser({guildId, userId, connectionId}: DisconnectVoiceUserParams): Promise<void> {
|
||||
await this.call('guild.disconnect_voice_user', {
|
||||
guild_id: guildId.toString(),
|
||||
user_id: userId.toString(),
|
||||
connection_id: connectionId,
|
||||
});
|
||||
}
|
||||
|
||||
async disconnectVoiceUserIfInChannel({
|
||||
guildId,
|
||||
userId,
|
||||
@@ -1556,23 +1415,6 @@ export class GatewayService {
|
||||
});
|
||||
}
|
||||
|
||||
async disconnectAllVoiceUsersInChannel({guildId, channelId}: {guildId: GuildID; channelId: ChannelID}): Promise<{
|
||||
success: boolean;
|
||||
disconnectedCount: number;
|
||||
}> {
|
||||
const result = await this.call<{
|
||||
success: boolean;
|
||||
disconnected_count: number;
|
||||
}>('guild.disconnect_all_voice_users_in_channel', {
|
||||
guild_id: guildId.toString(),
|
||||
channel_id: channelId.toString(),
|
||||
});
|
||||
return {
|
||||
success: result.success,
|
||||
disconnectedCount: result.disconnected_count,
|
||||
};
|
||||
}
|
||||
|
||||
async confirmVoiceConnection({
|
||||
guildId,
|
||||
channelId,
|
||||
@@ -1634,36 +1476,6 @@ export class GatewayService {
|
||||
};
|
||||
}
|
||||
|
||||
async getPendingJoinsForChannel({guildId, channelId}: {guildId?: GuildID; channelId: ChannelID}): Promise<{
|
||||
pendingJoins: Array<{
|
||||
connectionId: string;
|
||||
userId: string;
|
||||
tokenNonce: string;
|
||||
expiresAt: number;
|
||||
}>;
|
||||
}> {
|
||||
const params: Record<string, string> = {channel_id: channelId.toString()};
|
||||
if (guildId !== undefined) {
|
||||
params['guild_id'] = guildId.toString();
|
||||
}
|
||||
const result = await this.call<{
|
||||
pending_joins: Array<{
|
||||
connection_id: string;
|
||||
user_id: string;
|
||||
token_nonce: string;
|
||||
expires_at: number;
|
||||
}>;
|
||||
}>('voice.get_pending_joins_for_channel', params);
|
||||
return {
|
||||
pendingJoins: (result.pending_joins ?? []).map((pj) => ({
|
||||
connectionId: pj.connection_id,
|
||||
userId: pj.user_id,
|
||||
tokenNonce: pj.token_nonce,
|
||||
expiresAt: pj.expires_at,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
async getCall(channelId: ChannelID): Promise<CallData | null> {
|
||||
return this.call<CallData | null>('call.get', {channel_id: channelId.toString()});
|
||||
}
|
||||
@@ -1702,26 +1514,6 @@ export class GatewayService {
|
||||
return this.call<boolean>('call.stop_ringing', {channel_id: channelId.toString(), recipients});
|
||||
}
|
||||
|
||||
async deleteCall(channelId: ChannelID): Promise<boolean> {
|
||||
return this.call<boolean>('call.delete', {channel_id: channelId.toString()});
|
||||
}
|
||||
|
||||
async getDiscoveryOnlineCounts(guildIds: Array<GuildID>): Promise<Map<GuildID, number>> {
|
||||
const result = await this.call<{
|
||||
online_counts: Array<{
|
||||
guild_id: string;
|
||||
online_count: number;
|
||||
}>;
|
||||
}>('guild.get_online_counts_batch', {
|
||||
guild_ids: guildIds.map(String),
|
||||
});
|
||||
const counts = new Map<GuildID, number>();
|
||||
for (const entry of result.online_counts) {
|
||||
counts.set(BigInt(entry.guild_id) as GuildID, entry.online_count);
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
async getDiscoveryGuildCounts(guildIds: Array<GuildID>): Promise<
|
||||
Map<
|
||||
GuildID,
|
||||
|
||||
@@ -126,12 +126,6 @@ export interface GatewayChannelMention {
|
||||
type: number;
|
||||
}
|
||||
|
||||
export interface GatewayMentionSources {
|
||||
directUserIds: Array<UserID>;
|
||||
roleUserIds: Array<UserID>;
|
||||
everyoneUserIds: Array<UserID>;
|
||||
}
|
||||
|
||||
export interface GatewayMentionSourceEntry {
|
||||
userId: UserID;
|
||||
direct: boolean;
|
||||
@@ -175,46 +169,6 @@ export abstract class IGatewayService {
|
||||
|
||||
abstract getVoiceStateCounts(): Promise<GatewayVoiceStateCounts>;
|
||||
|
||||
abstract getUsersToMentionByRoles(params: {
|
||||
guildId: GuildID;
|
||||
channelId: ChannelID;
|
||||
roleIds: Array<RoleID>;
|
||||
authorId: UserID;
|
||||
}): Promise<Array<UserID>>;
|
||||
|
||||
abstract getUsersToMentionByUserIds(params: {
|
||||
guildId: GuildID;
|
||||
channelId: ChannelID;
|
||||
userIds: Array<UserID>;
|
||||
authorId: UserID;
|
||||
}): Promise<Array<UserID>>;
|
||||
|
||||
abstract getAllUsersToMention(params: {
|
||||
guildId: GuildID;
|
||||
channelId: ChannelID;
|
||||
authorId: UserID;
|
||||
}): Promise<Array<UserID>>;
|
||||
|
||||
abstract resolveAllMentions(params: {
|
||||
guildId: GuildID;
|
||||
channelId: ChannelID;
|
||||
authorId: UserID;
|
||||
mentionEveryone: boolean;
|
||||
mentionHere: boolean;
|
||||
roleIds: Array<RoleID>;
|
||||
userIds: Array<UserID>;
|
||||
}): Promise<Array<UserID>>;
|
||||
|
||||
abstract resolveMentionSources(params: {
|
||||
guildId: GuildID;
|
||||
channelId: ChannelID;
|
||||
authorId: UserID;
|
||||
mentionEveryone: boolean;
|
||||
mentionHere: boolean;
|
||||
roleIds: Array<RoleID>;
|
||||
userIds: Array<UserID>;
|
||||
}): Promise<GatewayMentionSources>;
|
||||
|
||||
abstract resolveMentionSourcesPage(params: {
|
||||
guildId: GuildID;
|
||||
channelId: ChannelID;
|
||||
@@ -297,8 +251,6 @@ export abstract class IGatewayService {
|
||||
channelId?: ChannelID;
|
||||
}): Promise<boolean>;
|
||||
|
||||
abstract getVanityUrlChannel(guildId: GuildID): Promise<ChannelID | null>;
|
||||
|
||||
abstract getFirstViewableTextChannel(guildId: GuildID): Promise<ChannelID | null>;
|
||||
|
||||
abstract dispatchPresence(params: {userId: UserID; event: GatewayDispatchEvent; data: unknown}): Promise<void>;
|
||||
@@ -323,8 +275,6 @@ export abstract class IGatewayService {
|
||||
success: boolean;
|
||||
}>;
|
||||
|
||||
abstract disconnectVoiceUser(params: {guildId: GuildID; userId: UserID; connectionId: string}): Promise<void>;
|
||||
|
||||
abstract disconnectVoiceUserIfInChannel(params: {
|
||||
guildId?: GuildID;
|
||||
channelId: ChannelID;
|
||||
@@ -335,11 +285,6 @@ export abstract class IGatewayService {
|
||||
ignored?: boolean;
|
||||
}>;
|
||||
|
||||
abstract disconnectAllVoiceUsersInChannel(params: {guildId: GuildID; channelId: ChannelID}): Promise<{
|
||||
success: boolean;
|
||||
disconnectedCount: number;
|
||||
}>;
|
||||
|
||||
abstract confirmVoiceConnection(params: {
|
||||
guildId?: GuildID;
|
||||
channelId: ChannelID;
|
||||
@@ -354,15 +299,6 @@ export abstract class IGatewayService {
|
||||
voiceStates: Array<GatewayVoiceStateEntry>;
|
||||
}>;
|
||||
|
||||
abstract getPendingJoinsForChannel(params: {guildId?: GuildID; channelId: ChannelID}): Promise<{
|
||||
pendingJoins: Array<{
|
||||
connectionId: string;
|
||||
userId: string;
|
||||
tokenNonce: string;
|
||||
expiresAt: number;
|
||||
}>;
|
||||
}>;
|
||||
|
||||
abstract getVoiceState(params: {guildId: GuildID; userId: UserID}): Promise<{
|
||||
channel_id: string | null;
|
||||
} | null>;
|
||||
@@ -408,10 +344,6 @@ export abstract class IGatewayService {
|
||||
|
||||
abstract stopRingingCallRecipients(channelId: ChannelID, recipients: Array<string>): Promise<boolean>;
|
||||
|
||||
abstract deleteCall(channelId: ChannelID): Promise<boolean>;
|
||||
|
||||
abstract getDiscoveryOnlineCounts(guildIds: Array<GuildID>): Promise<Map<GuildID, number>>;
|
||||
|
||||
abstract getDiscoveryGuildCounts(guildIds: Array<GuildID>): Promise<
|
||||
Map<
|
||||
GuildID,
|
||||
|
||||
@@ -21,12 +21,4 @@ export abstract class IVoiceRoomStore {
|
||||
} | null>;
|
||||
|
||||
abstract deleteRoomServer(guildId: GuildID | undefined, channelId: ChannelID): Promise<void>;
|
||||
|
||||
abstract getRegionOccupancy(regionId: string): Promise<Array<string>>;
|
||||
|
||||
abstract getServerOccupancy(regionId: string, serverId: string): Promise<Array<string>>;
|
||||
|
||||
async listPinnedRooms(): Promise<Array<{guildId?: GuildID; channelId: ChannelID}>> {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -23,16 +23,4 @@ export class InMemoryVoiceRoomStore {
|
||||
}
|
||||
|
||||
async deleteRoomServer(_guildId: GuildID | undefined, _channelId: ChannelID): Promise<void> {}
|
||||
|
||||
async getRegionOccupancy(_regionId: string): Promise<Array<string>> {
|
||||
return [];
|
||||
}
|
||||
|
||||
async getServerOccupancy(_regionId: string, _serverId: string): Promise<Array<string>> {
|
||||
return [];
|
||||
}
|
||||
|
||||
async listPinnedRooms(): Promise<Array<{guildId?: GuildID; channelId: ChannelID}>> {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -251,29 +251,6 @@ describe('StorageService.readObject', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('provider selection', () => {
|
||||
interface ClientProbe {
|
||||
client: {config: {region: () => Promise<string>; endpoint?: () => Promise<{hostname: string}>}};
|
||||
}
|
||||
|
||||
it('defaults to the shared S3 configuration', async () => {
|
||||
const service = new StorageService() as unknown as ClientProbe;
|
||||
expect(await service.client.config.region()).toBe(Config.s3.region);
|
||||
});
|
||||
|
||||
it('uses an explicitly supplied provider instead of the shared one', async () => {
|
||||
const service = new StorageService({
|
||||
endpoint: 'https://downloads.example.net',
|
||||
forcePathStyle: false,
|
||||
region: 'eu-central-9',
|
||||
accessKeyId: 'DL_KEY',
|
||||
secretAccessKey: 'DL_SECRET',
|
||||
}) as unknown as ClientProbe;
|
||||
expect(await service.client.config.region()).toBe('eu-central-9');
|
||||
expect(await service.client.config.region()).not.toBe(Config.s3.region);
|
||||
});
|
||||
});
|
||||
|
||||
function bodyStream(chunks: Array<Buffer>): PassThrough {
|
||||
const stream = new PassThrough();
|
||||
const writeChunk = (index: number): void => {
|
||||
|
||||
@@ -107,23 +107,6 @@ describe('UserCacheService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('records only the users-service timing boundary', async () => {
|
||||
const userId = createUserID(2501n);
|
||||
const servicePartial = createPartial(userId, 'TimedUsersService');
|
||||
const usersServiceClient = new FakeUsersServiceClient(new Map([[userId, servicePartial]]));
|
||||
const service = new UserCacheService(usersServiceClient);
|
||||
const timedSteps: Array<string> = [];
|
||||
|
||||
await service.getUserPartialResponses([userId], createRequestCache(), {
|
||||
timeStep: async (name, operation) => {
|
||||
timedSteps.push(name);
|
||||
return await operation();
|
||||
},
|
||||
});
|
||||
|
||||
expect(timedSteps).toEqual(['users_service_request']);
|
||||
});
|
||||
|
||||
it('surfaces user partial source failures', async () => {
|
||||
const userId = createUserID(3001n);
|
||||
const usersServiceClient = new FakeUsersServiceClient();
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {ChannelID, GuildID} from '@app/api/BrandedTypes';
|
||||
import {createChannelID, createGuildID} from '@app/api/BrandedTypes';
|
||||
import {parseJsonRecord} from '@app/api/utils/JsonBoundaryUtils';
|
||||
import {VOICE_OCCUPANCY_REGION_KEY_PREFIX, VOICE_OCCUPANCY_SERVER_KEY_PREFIX} from '@app/api/voice/VoiceConstants';
|
||||
import type {IKVProvider} from '@pkgs/kv_client/src/IKVProvider';
|
||||
|
||||
export interface PinnedRoomServer {
|
||||
@@ -35,10 +33,6 @@ export class VoiceRoomStore {
|
||||
endpoint: string,
|
||||
): Promise<void> {
|
||||
const key = this.getRoomKey(guildId, channelId);
|
||||
const previous = await this.getPinnedRoomServer(guildId, channelId);
|
||||
if (previous) {
|
||||
await this.removeOccupancy(previous.regionId, previous.serverId, guildId, channelId);
|
||||
}
|
||||
await this.kvClient.set(
|
||||
key,
|
||||
JSON.stringify({
|
||||
@@ -48,7 +42,6 @@ export class VoiceRoomStore {
|
||||
updatedAt: new Date().toISOString(),
|
||||
}),
|
||||
);
|
||||
await this.addOccupancy(regionId, serverId, guildId, channelId);
|
||||
}
|
||||
|
||||
async getPinnedRoomServer(guildId: GuildID | undefined, channelId: ChannelID): Promise<PinnedRoomServer | null> {
|
||||
@@ -71,73 +64,6 @@ export class VoiceRoomStore {
|
||||
|
||||
async deleteRoomServer(guildId: GuildID | undefined, channelId: ChannelID): Promise<void> {
|
||||
const key = this.getRoomKey(guildId, channelId);
|
||||
const previous = await this.getPinnedRoomServer(guildId, channelId);
|
||||
await this.kvClient.del(key);
|
||||
if (previous) {
|
||||
await this.removeOccupancy(previous.regionId, previous.serverId, guildId, channelId);
|
||||
}
|
||||
}
|
||||
|
||||
async getRegionOccupancy(regionId: string): Promise<Array<string>> {
|
||||
const key = `${VOICE_OCCUPANCY_REGION_KEY_PREFIX}:${regionId}`;
|
||||
const members = await this.kvClient.smembers(key);
|
||||
return members;
|
||||
}
|
||||
|
||||
async getServerOccupancy(regionId: string, serverId: string): Promise<Array<string>> {
|
||||
const key = `${VOICE_OCCUPANCY_SERVER_KEY_PREFIX}:${regionId}:${serverId}`;
|
||||
const members = await this.kvClient.smembers(key);
|
||||
return members;
|
||||
}
|
||||
|
||||
async listPinnedRooms(): Promise<Array<{guildId?: GuildID; channelId: ChannelID}>> {
|
||||
const keys = await this.kvClient.scan(`${this.keyPrefix}:*`, 1000);
|
||||
return keys
|
||||
.map((key) => this.parseRoomKey(key))
|
||||
.filter((room): room is {guildId?: GuildID; channelId: ChannelID} => room !== null);
|
||||
}
|
||||
|
||||
private async addOccupancy(
|
||||
regionId: string,
|
||||
serverId: string,
|
||||
guildId: bigint | undefined,
|
||||
channelId: bigint,
|
||||
): Promise<void> {
|
||||
const member = this.buildOccupancyMember(guildId, channelId);
|
||||
const regionKey = `${VOICE_OCCUPANCY_REGION_KEY_PREFIX}:${regionId}`;
|
||||
const serverKey = `${VOICE_OCCUPANCY_SERVER_KEY_PREFIX}:${regionId}:${serverId}`;
|
||||
await Promise.all([this.kvClient.sadd(regionKey, member), this.kvClient.sadd(serverKey, member)]);
|
||||
}
|
||||
|
||||
private async removeOccupancy(
|
||||
regionId: string,
|
||||
serverId: string,
|
||||
guildId: bigint | undefined,
|
||||
channelId: bigint,
|
||||
): Promise<void> {
|
||||
const member = this.buildOccupancyMember(guildId, channelId);
|
||||
const regionKey = `${VOICE_OCCUPANCY_REGION_KEY_PREFIX}:${regionId}`;
|
||||
const serverKey = `${VOICE_OCCUPANCY_SERVER_KEY_PREFIX}:${regionId}:${serverId}`;
|
||||
await Promise.all([this.kvClient.srem(regionKey, member), this.kvClient.srem(serverKey, member)]);
|
||||
}
|
||||
|
||||
private buildOccupancyMember(guildId: bigint | undefined, channelId: bigint): string {
|
||||
if (!guildId) {
|
||||
return `dm:${channelId.toString()}`;
|
||||
}
|
||||
return `guild:${guildId.toString()}:channel:${channelId.toString()}`;
|
||||
}
|
||||
|
||||
private parseRoomKey(key: string): {guildId?: GuildID; channelId: ChannelID} | null {
|
||||
const suffix = key.slice(`${this.keyPrefix}:`.length);
|
||||
if (suffix.startsWith('guild:')) {
|
||||
const parts = suffix.split(':');
|
||||
if (parts.length !== 3) return null;
|
||||
return {guildId: createGuildID(BigInt(parts[1])), channelId: createChannelID(BigInt(parts[2]))};
|
||||
}
|
||||
if (suffix.startsWith('dm:')) {
|
||||
return {channelId: createChannelID(BigInt(suffix.slice(3)))};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
drainActivitySpoolNow,
|
||||
emitActivity,
|
||||
idleActivityEvents,
|
||||
jetStreamActivityPublisher,
|
||||
resetActivityEventsForTests,
|
||||
startActivityEvents,
|
||||
} from '@app/api/infrastructure/activity/ActivityEvents';
|
||||
@@ -19,11 +18,10 @@ import {
|
||||
type ActivitySpoolEntry,
|
||||
drainActivitySpool,
|
||||
} from '@app/api/infrastructure/activity/ActivitySpool';
|
||||
import {EVENTS_STREAM} from '@app/api/infrastructure/activity/Contract.generated';
|
||||
import {MockKVProvider} from '@app/api/test/mocks/MockKVProvider';
|
||||
import {JetStreamApiError, jetstream, jetstreamManager} from '@nats-io/jetstream';
|
||||
import {connect, type NatsConnection, nanos, TimeoutError} from '@nats-io/transport-node';
|
||||
import {afterAll, afterEach, beforeAll, describe, expect, it} from 'vitest';
|
||||
import {JetStreamApiError} from '@nats-io/jetstream';
|
||||
import {TimeoutError} from '@nats-io/transport-node';
|
||||
import {afterEach, describe, expect, it} from 'vitest';
|
||||
|
||||
interface PublishCall {
|
||||
subject: string;
|
||||
@@ -293,47 +291,3 @@ describe('activity events', () => {
|
||||
expect((await spooled(kv)).map((entry) => entry.id)).toEqual(['account_deleted:3']);
|
||||
});
|
||||
});
|
||||
|
||||
const NATS_URL = process.env.FLUXER_TEST_ACTIVITY_NATS_URL;
|
||||
|
||||
describe.skipIf(!NATS_URL)('activity events against JetStream', () => {
|
||||
let nc: NatsConnection;
|
||||
beforeAll(async () => {
|
||||
nc = await connect({servers: NATS_URL, token: process.env.FLUXER_TEST_ACTIVITY_NATS_TOKEN});
|
||||
const jsm = await jetstreamManager(nc);
|
||||
await jsm.streams.delete(EVENTS_STREAM).catch(() => undefined);
|
||||
await jsm.streams.add({
|
||||
name: EVENTS_STREAM,
|
||||
subjects: ['evt.in.*.*', 'evt.p.*.*'],
|
||||
subject_transform: {src: 'evt.in.*.*', dest: 'evt.p.{{partition(64,2)}}.{{wildcard(1)}}'},
|
||||
allow_msg_ttl: true,
|
||||
duplicate_window: nanos(600_000),
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
resetActivityEventsForTests();
|
||||
});
|
||||
afterAll(async () => {
|
||||
await nc?.close();
|
||||
});
|
||||
|
||||
it('lands a fact on its partition subject with the msg id and ttl, and drops the spooled copy as a duplicate', async () => {
|
||||
const kv = new MockKVProvider();
|
||||
const publisher = jetStreamActivityPublisher(jetstream(nc));
|
||||
await startActivityEvents({publisher, kv, jsm: await jetstreamManager(nc)});
|
||||
await emitActivity('account_deleted', '1174109840998400001', {user_id: '1174109840998400001'}, workerMeta(), 'x1');
|
||||
await publisher.publish('evt.in.account_deleted.1174109840998400001', '{}', {
|
||||
msgID: 'account_deleted:x1',
|
||||
ttl: 'never',
|
||||
spooled: true,
|
||||
});
|
||||
const jsm = await jetstreamManager(nc);
|
||||
const info = await jsm.streams.info(EVENTS_STREAM);
|
||||
expect(info.state.messages).toBe(1);
|
||||
const stored = await jsm.streams.getMessage(EVENTS_STREAM, {seq: info.state.last_seq});
|
||||
expect(stored?.subject).toMatch(/^evt\.p\.\d{1,2}\.account_deleted$/u);
|
||||
expect(stored?.header.get('Nats-Msg-Id')).toBe('account_deleted:x1');
|
||||
expect(stored?.header.get('Nats-TTL')).toBe('never');
|
||||
expect(await kv.llen(ACTIVITY_SPOOL_KEY)).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -4,18 +4,13 @@ import {createUserID, type UserID} from '@app/api/BrandedTypes';
|
||||
import {getConfig} from '@app/api/Config';
|
||||
import {EMPTY_USER_ROW} from '@app/api/database/types/UserTypes';
|
||||
import {
|
||||
stripAnimationPrefixIfNoEntitlement,
|
||||
stripAvatarForUser,
|
||||
stripBannerForUser,
|
||||
stripGuildBannerForFeatures,
|
||||
stripGuildIconForFeatures,
|
||||
stripGuildSplashForFeatures,
|
||||
userHasAnimatedAvatarEntitlement,
|
||||
} from '@app/api/infrastructure/AssetEntitlementUtils';
|
||||
import {InstanceConfigRepository} from '@app/api/instance/InstanceConfigRepository';
|
||||
import {LimitConfigService} from '@app/api/limits/LimitConfigService';
|
||||
import {User} from '@app/api/models/User';
|
||||
import {GuildFeatures} from '@fluxer/constants/src/GuildConstants';
|
||||
import {PremiumFlags} from '@fluxer/constants/src/UserConstants';
|
||||
import {InMemoryProvider} from '@pkgs/cache/src/providers/InMemoryProvider';
|
||||
import {afterEach, beforeEach, describe, expect, test} from 'vitest';
|
||||
@@ -58,22 +53,6 @@ describe('AssetEntitlementUtils', () => {
|
||||
afterEach(() => {
|
||||
getConfig().instance.selfHosted = originalSelfHosted;
|
||||
});
|
||||
describe('stripAnimationPrefixIfNoEntitlement', () => {
|
||||
test('returns null when input is null', () => {
|
||||
expect(stripAnimationPrefixIfNoEntitlement(null, false)).toBeNull();
|
||||
expect(stripAnimationPrefixIfNoEntitlement(null, true)).toBeNull();
|
||||
});
|
||||
test('keeps animated prefix when entitlement is present', () => {
|
||||
expect(stripAnimationPrefixIfNoEntitlement('a_abc123', true)).toBe('a_abc123');
|
||||
});
|
||||
test('strips animated prefix when entitlement is missing', () => {
|
||||
expect(stripAnimationPrefixIfNoEntitlement('a_abc123', false)).toBe('abc123');
|
||||
});
|
||||
test('leaves static hashes untouched regardless of entitlement', () => {
|
||||
expect(stripAnimationPrefixIfNoEntitlement('xyz789', false)).toBe('xyz789');
|
||||
expect(stripAnimationPrefixIfNoEntitlement('xyz789', true)).toBe('xyz789');
|
||||
});
|
||||
});
|
||||
describe('stripAvatarForUser', () => {
|
||||
test('strips a_ prefix on user without animated-avatar entitlement', () => {
|
||||
const user = createUser({userId: createUserID(1n), avatarHash: 'a_abc123'});
|
||||
@@ -126,29 +105,4 @@ describe('AssetEntitlementUtils', () => {
|
||||
expect(stripBannerForUser(user)).toBe('a_banner123');
|
||||
});
|
||||
});
|
||||
describe('guild asset feature sanitization', () => {
|
||||
test('strips animated guild icon prefix without ANIMATED_ICON feature', () => {
|
||||
expect(stripGuildIconForFeatures('a_icon123', new Set())).toBe('icon123');
|
||||
});
|
||||
test('keeps animated guild icon prefix with ANIMATED_ICON feature', () => {
|
||||
expect(stripGuildIconForFeatures('a_icon123', new Set([GuildFeatures.ANIMATED_ICON]))).toBe('a_icon123');
|
||||
});
|
||||
test('hides guild banner without BANNER feature', () => {
|
||||
expect(stripGuildBannerForFeatures('banner123', new Set())).toBeNull();
|
||||
});
|
||||
test('strips animated guild banner prefix without ANIMATED_BANNER feature', () => {
|
||||
expect(stripGuildBannerForFeatures('a_banner123', new Set([GuildFeatures.BANNER]))).toBe('banner123');
|
||||
});
|
||||
test('keeps animated guild banner prefix with ANIMATED_BANNER feature', () => {
|
||||
expect(
|
||||
stripGuildBannerForFeatures('a_banner123', new Set([GuildFeatures.BANNER, GuildFeatures.ANIMATED_BANNER])),
|
||||
).toBe('a_banner123');
|
||||
});
|
||||
test('hides guild splashes without INVITE_SPLASH feature', () => {
|
||||
expect(stripGuildSplashForFeatures('splash123', new Set())).toBeNull();
|
||||
});
|
||||
test('keeps guild splashes with INVITE_SPLASH feature', () => {
|
||||
expect(stripGuildSplashForFeatures('splash123', new Set([GuildFeatures.INVITE_SPLASH]))).toBe('splash123');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,67 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {getConfig} from '@app/api/Config';
|
||||
import {createDefaultLimitConfig} from '@app/api/constants/LimitConfig';
|
||||
import {DiscriminatorService} from '@app/api/infrastructure/DiscriminatorService';
|
||||
import {getCachedInstancePremiumMode, setCachedInstancePremiumMode} from '@app/api/limits/InstancePremiumModeCache';
|
||||
import type {LimitConfigService} from '@app/api/limits/LimitConfigService';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {ICacheService} from '@pkgs/cache/src/ICacheService';
|
||||
import {afterEach, beforeEach, describe, expect, test} from 'vitest';
|
||||
|
||||
function createService(): DiscriminatorService {
|
||||
const userRepository = {
|
||||
async findByUsernameDiscriminator() {
|
||||
return null;
|
||||
},
|
||||
async findDiscriminatorsByUsername() {
|
||||
return new Set<number>([42]);
|
||||
},
|
||||
} as unknown as IUserRepository;
|
||||
const cacheService = {
|
||||
async acquireLock() {
|
||||
return 'token';
|
||||
},
|
||||
async releaseLock() {},
|
||||
async sismember() {
|
||||
return false;
|
||||
},
|
||||
async sadd() {},
|
||||
async smembers() {
|
||||
return new Set<string>();
|
||||
},
|
||||
} as unknown as ICacheService;
|
||||
const limitConfigService = {
|
||||
getConfigSnapshot: () => createDefaultLimitConfig({selfHosted: true, premiumMode: 'mirror'}),
|
||||
} as unknown as LimitConfigService;
|
||||
return new DiscriminatorService(userRepository, cacheService, limitConfigService);
|
||||
}
|
||||
|
||||
describe('DiscriminatorService on a self-hosted instance', () => {
|
||||
let originalSelfHosted: boolean;
|
||||
let originalPremiumMode: ReturnType<typeof getCachedInstancePremiumMode>;
|
||||
|
||||
beforeEach(() => {
|
||||
originalSelfHosted = getConfig().instance.selfHosted;
|
||||
originalPremiumMode = getCachedInstancePremiumMode();
|
||||
getConfig().instance.selfHosted = true;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
getConfig().instance.selfHosted = originalSelfHosted;
|
||||
setCachedInstancePremiumMode(originalPremiumMode);
|
||||
});
|
||||
|
||||
test('lets anyone pick a discriminator when everyone is premium', async () => {
|
||||
setCachedInstancePremiumMode('everyone');
|
||||
const result = await createService().generateDiscriminator({username: 'someone', requestedDiscriminator: 42});
|
||||
expect(result).toEqual({discriminator: 42, available: true});
|
||||
});
|
||||
|
||||
test('follows the custom discriminator limit in mirror mode', async () => {
|
||||
setCachedInstancePremiumMode('mirror');
|
||||
const result = await createService().generateDiscriminator({username: 'someone', requestedDiscriminator: 42});
|
||||
expect(result.available).toBe(true);
|
||||
expect(result.discriminator).not.toBe(42);
|
||||
});
|
||||
});
|
||||
@@ -23,12 +23,11 @@ function roomFinished(name: string): WebhookEvent {
|
||||
|
||||
function harness(pinnedServerId: string | null) {
|
||||
const deleteRoomServer = vi.fn(async () => {});
|
||||
const disconnectAllVoiceUsersInChannel = vi.fn(async () => ({disconnectedCount: 0}));
|
||||
const voiceRoomStore = {
|
||||
getPinnedRoomServer: vi.fn(async () => (pinnedServerId ? {regionId: 'eu', serverId: pinnedServerId} : null)),
|
||||
deleteRoomServer,
|
||||
} as unknown as IVoiceRoomStore;
|
||||
const gatewayService = {disconnectAllVoiceUsersInChannel} as unknown as IGatewayService;
|
||||
const gatewayService = {} as unknown as IGatewayService;
|
||||
const liveKitService = {} as unknown as ILiveKitService;
|
||||
const service = new LiveKitWebhookService(
|
||||
voiceRoomStore,
|
||||
@@ -36,35 +35,32 @@ function harness(pinnedServerId: string | null) {
|
||||
liveKitService,
|
||||
new VoiceTopology({} as unknown as IVoiceRepository, null),
|
||||
);
|
||||
return {service, deleteRoomServer, disconnectAllVoiceUsersInChannel};
|
||||
return {service, deleteRoomServer};
|
||||
}
|
||||
|
||||
describe('LiveKitWebhookService room_finished', () => {
|
||||
it('clears the guild pin without disconnecting anybody', async () => {
|
||||
const {service, deleteRoomServer, disconnectAllVoiceUsersInChannel} = harness('eu-1');
|
||||
const {service, deleteRoomServer} = harness('eu-1');
|
||||
|
||||
await service.handleRoomFinished(roomFinished(`guild_${GUILD_ID}_channel_${CHANNEL_ID}`), 'unknown-key');
|
||||
|
||||
expect(deleteRoomServer).toHaveBeenCalledTimes(1);
|
||||
expect(disconnectAllVoiceUsersInChannel).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('clears the pin even when no server is pinned, and still disconnects nobody', async () => {
|
||||
const {service, deleteRoomServer, disconnectAllVoiceUsersInChannel} = harness(null);
|
||||
const {service, deleteRoomServer} = harness(null);
|
||||
|
||||
await service.handleRoomFinished(roomFinished(`guild_${GUILD_ID}_channel_${CHANNEL_ID}`), 'unknown-key');
|
||||
|
||||
expect(deleteRoomServer).toHaveBeenCalledTimes(1);
|
||||
expect(disconnectAllVoiceUsersInChannel).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('ignores a room name it cannot parse', async () => {
|
||||
const {service, deleteRoomServer, disconnectAllVoiceUsersInChannel} = harness('eu-1');
|
||||
const {service, deleteRoomServer} = harness('eu-1');
|
||||
|
||||
await service.handleRoomFinished(roomFinished('not_a_voice_room'), 'unknown-key');
|
||||
|
||||
expect(deleteRoomServer).not.toHaveBeenCalled();
|
||||
expect(disconnectAllVoiceUsersInChannel).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {createChannelID, createGuildID} from '@app/api/BrandedTypes';
|
||||
import {VoiceRoomStore} from '@app/api/infrastructure/VoiceRoomStore';
|
||||
import {BatchRecordingKVProvider} from '@app/api/test/mocks/BatchRecordingKVProvider';
|
||||
import {VOICE_OCCUPANCY_REGION_KEY_PREFIX, VOICE_OCCUPANCY_SERVER_KEY_PREFIX} from '@app/api/voice/VoiceConstants';
|
||||
import {computeHashSlot} from '@pkgs/kv_client/src/KVHashSlots';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
describe('VoiceRoomStore cluster hash slots', () => {
|
||||
it('keeps occupancy writes off batched commands that span hash slots', async () => {
|
||||
const kvClient = new BatchRecordingKVProvider();
|
||||
const store = new VoiceRoomStore(kvClient);
|
||||
const guildId = createGuildID(1234n);
|
||||
const channelId = createChannelID(5678n);
|
||||
const regionKey = `${VOICE_OCCUPANCY_REGION_KEY_PREFIX}:us-east`;
|
||||
const serverKey = `${VOICE_OCCUPANCY_SERVER_KEY_PREFIX}:us-east:voice-1`;
|
||||
const member = 'guild:1234:channel:5678';
|
||||
|
||||
expect(computeHashSlot(regionKey)).not.toBe(computeHashSlot(serverKey));
|
||||
|
||||
await store.pinRoomServer(guildId, channelId, 'us-east', 'voice-1', 'wss://voice-1.example');
|
||||
expect(await kvClient.smembers(regionKey)).toEqual([member]);
|
||||
expect(await kvClient.smembers(serverKey)).toEqual([member]);
|
||||
|
||||
await store.deleteRoomServer(guildId, channelId);
|
||||
expect(await kvClient.smembers(regionKey)).toEqual([]);
|
||||
expect(await kvClient.smembers(serverKey)).toEqual([]);
|
||||
|
||||
expect(kvClient.crossSlotBatches()).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user