mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
144 lines
5.5 KiB
TypeScript
144 lines
5.5 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
import {createTestAccount, setUserACLs, type TestAccount} from '@app/api/auth/tests/AuthTestUtils';
|
|
import {VALID_GIF_BASE64, VALID_PNG_BASE64} from '@app/api/emoji/tests/EmojiTestUtils';
|
|
import {createGuild} from '@app/api/guild/tests/GuildTestUtils';
|
|
import {ensureSessionStarted} from '@app/api/message/tests/MessageTestUtils';
|
|
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
|
|
import {HTTP_STATUS} from '@app/api/test/TestConstants';
|
|
import {createBuilder} from '@app/api/test/TestRequestBuilder';
|
|
import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
|
|
import type {LimitKey} from '@fluxer/constants/src/LimitConfigMetadata';
|
|
import {AVATAR_MAX_SIZE, EMOJI_MAX_SIZE, STICKER_MAX_SIZE} from '@fluxer/constants/src/LimitConstants';
|
|
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
|
|
import type {LimitConfigSnapshot} from '@fluxer/limits/src/LimitTypes';
|
|
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';
|
|
|
|
const RAISED_EXPRESSION_SIZE = EMOJI_MAX_SIZE * 4;
|
|
const RAISED_AVATAR_SIZE = AVATAR_MAX_SIZE + 2 * 1024 * 1024;
|
|
|
|
interface LimitConfigReadResponse {
|
|
limit_config: LimitConfigSnapshot;
|
|
}
|
|
|
|
interface ValidationErrorResponse {
|
|
errors?: Array<{path?: string; code?: string; message?: string}>;
|
|
}
|
|
|
|
function paddedImage(mime: string, base64: string, byteLength: number): string {
|
|
const image = Buffer.from(base64, 'base64');
|
|
const padded = Buffer.concat([image, Buffer.alloc(byteLength - image.length)]);
|
|
return `data:${mime};base64,${padded.toString('base64')}`;
|
|
}
|
|
|
|
describe('configured image size limits', () => {
|
|
let harness: ApiTestHarness;
|
|
|
|
beforeAll(async () => {
|
|
harness = await createApiTestHarness();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await harness.reset();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await harness?.shutdown();
|
|
});
|
|
|
|
const raiseLimits = async (limits: Partial<Record<LimitKey, number>>): Promise<void> => {
|
|
const admin = await setUserACLs(harness, await createTestAccount(harness), [
|
|
AdminACLs.AUTHENTICATE,
|
|
AdminACLs.INSTANCE_LIMIT_CONFIG_VIEW,
|
|
AdminACLs.INSTANCE_LIMIT_CONFIG_UPDATE,
|
|
]);
|
|
const current = await createBuilder<LimitConfigReadResponse>(harness, admin.token)
|
|
.get('/admin/limit-config')
|
|
.expect(HTTP_STATUS.OK)
|
|
.execute();
|
|
await createBuilder(harness, admin.token)
|
|
.put('/admin/limit-config')
|
|
.body({
|
|
limit_config: {
|
|
traitDefinitions: current.limit_config.traitDefinitions,
|
|
rules: current.limit_config.rules.map((rule) => ({
|
|
id: rule.id,
|
|
filters: rule.filters,
|
|
limits: rule.id === 'very_large_guild' ? rule.limits : {...rule.limits, ...limits},
|
|
})),
|
|
},
|
|
})
|
|
.expect(HTTP_STATUS.OK)
|
|
.execute();
|
|
};
|
|
|
|
const createOwner = async (): Promise<TestAccount> => {
|
|
const owner = await createTestAccount(harness);
|
|
await ensureSessionStarted(harness, owner.token);
|
|
return owner;
|
|
};
|
|
|
|
it('accepts emoji and sticker uploads above the stock size once the limits are raised', async () => {
|
|
await raiseLimits({emoji_max_size: RAISED_EXPRESSION_SIZE, sticker_max_size: RAISED_EXPRESSION_SIZE});
|
|
const owner = await createOwner();
|
|
const guild = await createGuild(harness, owner.token, 'Raised expression limits');
|
|
const image = paddedImage('image/gif', VALID_GIF_BASE64, Math.max(EMOJI_MAX_SIZE, STICKER_MAX_SIZE) * 2);
|
|
|
|
await createBuilder(harness, owner.token)
|
|
.post(`/guilds/${guild.id}/emojis`)
|
|
.body({name: 'large_emoji', image})
|
|
.expect(HTTP_STATUS.OK)
|
|
.execute();
|
|
await createBuilder(harness, owner.token)
|
|
.post(`/guilds/${guild.id}/stickers`)
|
|
.body({name: 'large_sticker', description: 'large sticker', tags: [], image})
|
|
.expect(HTTP_STATUS.OK)
|
|
.execute();
|
|
});
|
|
|
|
it('rejects an emoji over a raised limit with the configured size', async () => {
|
|
await raiseLimits({emoji_max_size: RAISED_EXPRESSION_SIZE});
|
|
const owner = await createOwner();
|
|
const guild = await createGuild(harness, owner.token, 'Raised emoji ceiling');
|
|
|
|
const json = await createBuilder<ValidationErrorResponse>(harness, owner.token)
|
|
.post(`/guilds/${guild.id}/emojis`)
|
|
.body({name: 'too_large', image: paddedImage('image/gif', VALID_GIF_BASE64, RAISED_EXPRESSION_SIZE + 1)})
|
|
.expect(HTTP_STATUS.BAD_REQUEST)
|
|
.execute();
|
|
expect(json.errors?.[0]).toMatchObject({
|
|
path: 'image',
|
|
code: ValidationErrorCodes.IMAGE_SIZE_EXCEEDS_LIMIT,
|
|
message: `Image size exceeds ${RAISED_EXPRESSION_SIZE} bytes.`,
|
|
});
|
|
});
|
|
|
|
it('accepts an avatar above the stock size once avatar_max_size is raised', async () => {
|
|
await raiseLimits({avatar_max_size: RAISED_AVATAR_SIZE});
|
|
const account = await createOwner();
|
|
|
|
const json = await createBuilder<{avatar: string | null}>(harness, account.token)
|
|
.patch('/users/@me')
|
|
.body({avatar: paddedImage('image/png', VALID_PNG_BASE64, AVATAR_MAX_SIZE + 1024 * 1024)})
|
|
.expect(HTTP_STATUS.OK)
|
|
.execute();
|
|
expect(json.avatar).toBeTruthy();
|
|
});
|
|
|
|
it('rejects an avatar over a raised limit with the configured size', async () => {
|
|
await raiseLimits({avatar_max_size: RAISED_AVATAR_SIZE});
|
|
const account = await createOwner();
|
|
|
|
const json = await createBuilder<ValidationErrorResponse>(harness, account.token)
|
|
.patch('/users/@me')
|
|
.body({avatar: paddedImage('image/png', VALID_PNG_BASE64, RAISED_AVATAR_SIZE + 1)})
|
|
.expect(HTTP_STATUS.BAD_REQUEST)
|
|
.execute();
|
|
expect(json.errors?.[0]).toMatchObject({
|
|
path: 'avatar',
|
|
code: ValidationErrorCodes.IMAGE_SIZE_EXCEEDS_LIMIT,
|
|
message: `Image size exceeds ${RAISED_AVATAR_SIZE} bytes.`,
|
|
});
|
|
});
|
|
});
|