mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
234 lines
8.6 KiB
TypeScript
234 lines
8.6 KiB
TypeScript
import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants';
|
|
import {
|
|
CreateVoiceServerRequest,
|
|
CreateVoiceServerRequestBody,
|
|
UpdateVoiceRegionRequest,
|
|
UpdateVoiceRegionRequestBody,
|
|
UpdateVoiceServerRequest,
|
|
UpdateVoiceServerRequestBody,
|
|
} from '@fluxer/schema/src/domains/admin/AdminVoiceSchemas';
|
|
import {ChannelUpdateRequest, ChannelUpdateRequestBody} from '@fluxer/schema/src/domains/channel/ChannelRequestSchemas';
|
|
import {
|
|
BulkDeleteSelfMessagesFilter,
|
|
BulkDeleteSelfMessagesRequest,
|
|
HarvestSelfDataRequest,
|
|
} from '@fluxer/schema/src/domains/user/UserRequestSchemas';
|
|
import {describe, expect, it} from 'vitest';
|
|
|
|
const server = {
|
|
server_id: 'primary',
|
|
endpoint: 'wss://voice.example.com',
|
|
api_key: 'key',
|
|
api_secret: 'secret',
|
|
};
|
|
|
|
describe('voice request bodies', () => {
|
|
it('requires server creation fields but not the URL-supplied region', () => {
|
|
const body = CreateVoiceServerRequestBody.parse(server);
|
|
expect(body).toEqual({
|
|
...server,
|
|
is_active: true,
|
|
soft_connection_limit: null,
|
|
vip_only: false,
|
|
required_guild_features: [],
|
|
allowed_guild_ids: [],
|
|
allowed_user_ids: [],
|
|
});
|
|
expect(CreateVoiceServerRequestBody.safeParse({}).success).toBe(false);
|
|
expect(CreateVoiceServerRequest.safeParse(server).success).toBe(false);
|
|
expect(CreateVoiceServerRequest.parse({...server, region_id: 'eu'})).toEqual({...body, region_id: 'eu'});
|
|
});
|
|
|
|
it.each(['server_id', 'endpoint', 'api_key', 'api_secret'] as const)('requires creation field %s', (field) => {
|
|
expect(CreateVoiceServerRequestBody.safeParse({...server, [field]: undefined}).success).toBe(false);
|
|
});
|
|
|
|
it('keeps patch bodies empty without injecting defaults', () => {
|
|
expect(UpdateVoiceRegionRequestBody.parse({})).toEqual({});
|
|
expect(UpdateVoiceServerRequestBody.parse({})).toEqual({});
|
|
expect(UpdateVoiceRegionRequest.parse({id: 'eu'})).toEqual({id: 'eu'});
|
|
expect(UpdateVoiceServerRequest.parse({region_id: 'eu', server_id: 'primary'})).toEqual({
|
|
region_id: 'eu',
|
|
server_id: 'primary',
|
|
});
|
|
});
|
|
|
|
it('does not treat client-supplied path fields as body properties', () => {
|
|
expect(UpdateVoiceRegionRequestBody.parse({id: 'ignored', name: 'Europe'})).toEqual({name: 'Europe'});
|
|
expect(UpdateVoiceServerRequestBody.parse({region_id: 'ignored', server_id: 'ignored'})).toEqual({});
|
|
});
|
|
|
|
it('retains explicit false, null, and snowflake transformations', () => {
|
|
expect(
|
|
UpdateVoiceServerRequestBody.parse({is_active: false, soft_connection_limit: null, allowed_user_ids: ['123']}),
|
|
).toEqual({is_active: false, soft_connection_limit: null, allowed_user_ids: [123n]});
|
|
});
|
|
});
|
|
|
|
describe.each([
|
|
{name: 'create body', schema: CreateVoiceServerRequestBody, required: server},
|
|
{name: 'create service request', schema: CreateVoiceServerRequest, required: {...server, region_id: 'eu'}},
|
|
{name: 'update body', schema: UpdateVoiceServerRequestBody, required: {}},
|
|
{name: 'update service request', schema: UpdateVoiceServerRequest, required: {region_id: 'eu', server_id: 'primary'}},
|
|
])('$name coordinates', ({schema, required}) => {
|
|
it.each([
|
|
{},
|
|
{latitude: 0, longitude: 0},
|
|
{latitude: null, longitude: null},
|
|
])('accepts the paired state %j', (coordinates) => {
|
|
expect(schema.parse({...required, ...coordinates})).toMatchObject(coordinates);
|
|
});
|
|
|
|
it.each([
|
|
{latitude: 0},
|
|
{longitude: 0},
|
|
{latitude: null},
|
|
{longitude: null},
|
|
{latitude: 0, longitude: null},
|
|
{latitude: null, longitude: 0},
|
|
])('rejects the unpaired state %j', (coordinates) => {
|
|
expect(schema.safeParse({...required, ...coordinates})).toMatchObject({
|
|
success: false,
|
|
error: {
|
|
issues: [{path: ['latitude'], message: 'Latitude and longitude must both be provided or both be omitted'}],
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('channel update request body', () => {
|
|
it('permits an empty body while the service validator still requires the stored channel type', () => {
|
|
expect(ChannelUpdateRequestBody.parse({})).toEqual({});
|
|
expect(ChannelUpdateRequest.safeParse({}).success).toBe(false);
|
|
});
|
|
|
|
it.each([
|
|
ChannelTypes.GUILD_TEXT,
|
|
ChannelTypes.GUILD_VOICE,
|
|
ChannelTypes.GUILD_CATEGORY,
|
|
ChannelTypes.GUILD_LINK,
|
|
ChannelTypes.GROUP_DM,
|
|
])('preserves field transformations for stored channel type %i', (type) => {
|
|
const input = {name: ' General ', owner_id: '123'};
|
|
const expected = {name: 'General', owner_id: 123n};
|
|
expect(ChannelUpdateRequestBody.parse(input)).toEqual(expected);
|
|
expect(ChannelUpdateRequest.parse({...input, type})).toEqual({...expected, type});
|
|
});
|
|
|
|
it('preserves guild-only fields in the documented body', () => {
|
|
const input = {topic: ' Topic ', parent_id: '123', nsfw_override: false, bitrate: 64000, rtc_region: null};
|
|
expect(ChannelUpdateRequestBody.parse(input)).toEqual({...input, topic: 'Topic', parent_id: 123n});
|
|
});
|
|
|
|
it('does not accept the injected discriminator as a body property', () => {
|
|
expect(ChannelUpdateRequestBody.parse({type: ChannelTypes.GUILD_VOICE})).toEqual({});
|
|
});
|
|
});
|
|
|
|
const bulkDeleteDefaults = {
|
|
scope: 'selected',
|
|
include_dms: true,
|
|
include_dms_closed: true,
|
|
include_group_dms: true,
|
|
include_guilds: true,
|
|
guild_filter_mode: 'exclude',
|
|
excluded_guild_ids: [],
|
|
included_guild_ids: [],
|
|
};
|
|
const noSelectedContexts = {
|
|
include_dms: false,
|
|
include_dms_closed: false,
|
|
include_group_dms: false,
|
|
include_guilds: false,
|
|
};
|
|
const earlierDate = '2026-01-01T00:00:00Z';
|
|
const laterDate = '2026-02-01T00:00:00Z';
|
|
const selectedContextIssue = {
|
|
code: 'custom',
|
|
message: 'Enable at least one of include_dms, include_dms_closed, include_group_dms, or include_guilds.',
|
|
path: ['include_dms'],
|
|
};
|
|
const dateRangeIssue = {
|
|
code: 'custom',
|
|
message: 'start_date must be earlier than end_date.',
|
|
path: ['end_date'],
|
|
};
|
|
|
|
describe.each([
|
|
{name: 'bulk-delete filter', schema: BulkDeleteSelfMessagesFilter},
|
|
{name: 'bulk-delete request', schema: BulkDeleteSelfMessagesRequest},
|
|
{name: 'harvest request', schema: HarvestSelfDataRequest},
|
|
])('$name', ({schema}) => {
|
|
it('applies the shared defaults to an empty request', () => {
|
|
expect(schema.parse({})).toEqual(bulkDeleteDefaults);
|
|
});
|
|
|
|
it.each([
|
|
'include_dms',
|
|
'include_dms_closed',
|
|
'include_group_dms',
|
|
'include_guilds',
|
|
] as const)('accepts %s as the only selected context', (field) => {
|
|
const selection = {...noSelectedContexts, [field]: true};
|
|
expect(schema.parse(selection)).toEqual({...bulkDeleteDefaults, ...selection});
|
|
});
|
|
|
|
it('does not require selected contexts for inaccessible-only scope', () => {
|
|
const selection = {...noSelectedContexts, scope: 'inaccessible_only'};
|
|
expect(schema.parse(selection)).toEqual({...bulkDeleteDefaults, ...selection});
|
|
});
|
|
|
|
it('preserves the guild filter and transforms both guild ID lists', () => {
|
|
expect(
|
|
schema.parse({guild_filter_mode: 'include_only', included_guild_ids: ['123'], excluded_guild_ids: ['456']}),
|
|
).toEqual({
|
|
...bulkDeleteDefaults,
|
|
guild_filter_mode: 'include_only',
|
|
included_guild_ids: [123n],
|
|
excluded_guild_ids: [456n],
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
{start_date: null, end_date: null},
|
|
{start_date: earlierDate},
|
|
{end_date: laterDate},
|
|
{start_date: earlierDate, end_date: null},
|
|
{start_date: null, end_date: laterDate},
|
|
{start_date: earlierDate, end_date: laterDate},
|
|
])('accepts the date bounds %j', (dates) => {
|
|
expect(schema.parse(dates)).toEqual({...bulkDeleteDefaults, ...dates});
|
|
});
|
|
|
|
it.each([
|
|
{input: noSelectedContexts, issues: [selectedContextIssue]},
|
|
{input: {start_date: earlierDate, end_date: earlierDate}, issues: [dateRangeIssue]},
|
|
{input: {start_date: laterDate, end_date: earlierDate}, issues: [dateRangeIssue]},
|
|
{
|
|
input: {...noSelectedContexts, start_date: laterDate, end_date: earlierDate},
|
|
issues: [selectedContextIssue, dateRangeIssue],
|
|
},
|
|
])('reports exact refinement issues in order for $input', ({input, issues}) => {
|
|
expect(schema.safeParse(input).error?.issues).toEqual(issues);
|
|
});
|
|
});
|
|
|
|
describe('bulk-delete sudo verification', () => {
|
|
it.each([
|
|
{password: 'correct horse battery staple'},
|
|
{mfa_method: 'totp', mfa_code: '123456'},
|
|
{mfa_method: 'webauthn', webauthn_challenge: 'challenge'},
|
|
])('preserves sudo fields only on the delete request: %j', (verification) => {
|
|
expect(BulkDeleteSelfMessagesRequest.parse(verification)).toEqual({...bulkDeleteDefaults, ...verification});
|
|
expect(BulkDeleteSelfMessagesFilter.parse(verification)).toEqual(bulkDeleteDefaults);
|
|
expect(HarvestSelfDataRequest.parse(verification)).toEqual(bulkDeleteDefaults);
|
|
});
|
|
|
|
it('still validates sudo fields on the extended request', () => {
|
|
expect(BulkDeleteSelfMessagesRequest.safeParse({mfa_method: 'unknown'})).toMatchObject({
|
|
success: false,
|
|
error: {issues: [{code: 'invalid_union', path: ['mfa_method']}]},
|
|
});
|
|
});
|
|
});
|