mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +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
@@ -1,29 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
CaptchaConfigSchema,
|
||||
CaptchaConfigUpdateRequest,
|
||||
DEFAULT_CAPTCHA_CONFIG,
|
||||
} from '@fluxer/schema/src/domains/admin/CaptchaSchemas';
|
||||
import {describe, expect, test} from 'vitest';
|
||||
|
||||
describe('captcha configuration', () => {
|
||||
test('defaults to enabled at the mobile-friendly difficulty', () => {
|
||||
expect(CaptchaConfigSchema.parse({})).toEqual({enabled: true, cost: 5000, max_counter: 1000});
|
||||
expect(DEFAULT_CAPTCHA_CONFIG).toEqual({enabled: true, cost: 5000, max_counter: 1000});
|
||||
});
|
||||
|
||||
test('rejects difficulty outside the supported range', () => {
|
||||
expect(CaptchaConfigUpdateRequest.safeParse({cost: 999}).success).toBe(false);
|
||||
expect(CaptchaConfigUpdateRequest.safeParse({cost: 20001}).success).toBe(false);
|
||||
expect(CaptchaConfigUpdateRequest.safeParse({max_counter: 99}).success).toBe(false);
|
||||
expect(CaptchaConfigUpdateRequest.safeParse({max_counter: 20001}).success).toBe(false);
|
||||
expect(CaptchaConfigSchema.safeParse({cost: 20001}).success).toBe(false);
|
||||
});
|
||||
|
||||
test('keeps a partial update partial without filling defaults', () => {
|
||||
expect(CaptchaConfigUpdateRequest.parse({})).toEqual({});
|
||||
expect(CaptchaConfigUpdateRequest.parse({enabled: false})).toEqual({enabled: false});
|
||||
expect(CaptchaConfigUpdateRequest.parse({cost: 1000, max_counter: 100})).toEqual({cost: 1000, max_counter: 100});
|
||||
});
|
||||
});
|
||||
@@ -1,54 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {AuditLogActionType} from '@fluxer/constants/src/AuditLogActionType';
|
||||
import {GuildAuditLogEntryResponse} from '@fluxer/schema/src/domains/guild/GuildAuditLogSchemas';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
function entryWith(fields: Record<string, unknown>): Record<string, unknown> {
|
||||
return {
|
||||
id: '1420000000000000100',
|
||||
action_type: AuditLogActionType.MEMBER_BAN_ADD,
|
||||
user_id: '1420000000000000001',
|
||||
target_id: '1420000000000000002',
|
||||
...fields,
|
||||
};
|
||||
}
|
||||
|
||||
describe('GuildAuditLogEntryResponse', () => {
|
||||
it('parses delete_message_seconds as a number', () => {
|
||||
const parsed = GuildAuditLogEntryResponse.parse(entryWith({options: {delete_message_seconds: 3600}}));
|
||||
expect(parsed.options).toEqual({delete_message_seconds: 3600});
|
||||
});
|
||||
|
||||
it('rejects delete_message_seconds sent as a string', () => {
|
||||
const result = GuildAuditLogEntryResponse.safeParse(entryWith({options: {delete_message_seconds: '3600'}}));
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('parses the legacy delete_member_days string', () => {
|
||||
const parsed = GuildAuditLogEntryResponse.parse(entryWith({options: {delete_member_days: '7'}}));
|
||||
expect(parsed.options).toEqual({delete_member_days: '7'});
|
||||
});
|
||||
|
||||
it('parses a permissions_diff change value', () => {
|
||||
const changes = [
|
||||
{key: 'permissions', old_value: '1024', new_value: '3072'},
|
||||
{key: 'permissions_diff', new_value: {added: ['SEND_MESSAGES'], removed: []}},
|
||||
];
|
||||
const parsed = GuildAuditLogEntryResponse.parse(
|
||||
entryWith({action_type: AuditLogActionType.ROLE_UPDATE, options: {role_name: 'Moderators'}, changes}),
|
||||
);
|
||||
expect(parsed.changes).toEqual(changes);
|
||||
expect(parsed.options).toEqual({role_name: 'Moderators'});
|
||||
});
|
||||
|
||||
it('strips unknown option keys', () => {
|
||||
const parsed = GuildAuditLogEntryResponse.parse(
|
||||
entryWith({
|
||||
action_type: AuditLogActionType.MEMBER_UPDATE,
|
||||
options: {channel_id: '1420000000000000003', timeout_reason: 'raw', future_option: 1},
|
||||
}),
|
||||
);
|
||||
expect(parsed.options).toEqual({channel_id: '1420000000000000003'});
|
||||
});
|
||||
});
|
||||
@@ -1,235 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
ReportFlowMessageSubmissionRequest,
|
||||
ReportFlowQuery,
|
||||
ReportFlowResponse,
|
||||
ReportFlowUserSubmissionRequest,
|
||||
} from '@fluxer/schema/src/domains/report/ReportFlowSchemas';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
const userFlowResponse = {
|
||||
target_type: 'user',
|
||||
surface: 'in_app',
|
||||
revision_hash: '9c41d2a07e5b18f3',
|
||||
locale: 'en-US',
|
||||
start_screen_id: 'profile_intro',
|
||||
guidelines_url: 'https://fluxer.app/guidelines',
|
||||
screens: [
|
||||
{
|
||||
id: 'profile_intro',
|
||||
title: 'Report profile',
|
||||
subtitle: "Tell us what's wrong with this profile. They won't be told who reported them.",
|
||||
urgent: false,
|
||||
options: [
|
||||
{
|
||||
id: 'learn_more',
|
||||
label: 'Read the Fluxer Community Guidelines',
|
||||
outcome: {
|
||||
type: 'link',
|
||||
screen_id: null,
|
||||
reason: null,
|
||||
notice_id: null,
|
||||
url: 'https://fluxer.app/guidelines',
|
||||
},
|
||||
},
|
||||
],
|
||||
options_heading: 'Learn more',
|
||||
checklist: null,
|
||||
next_screen_id: 'profile_parts',
|
||||
},
|
||||
{
|
||||
id: 'profile_parts',
|
||||
title: 'Which parts of their profile are a problem?',
|
||||
subtitle: 'Pick as many as you need.',
|
||||
urgent: false,
|
||||
options: [],
|
||||
options_heading: null,
|
||||
checklist: {
|
||||
items: [
|
||||
{id: 'photo', label: 'Pictures', description: 'What they use as avatar and banner'},
|
||||
{id: 'name', label: 'Names', description: 'Username, display name or community nickname'},
|
||||
{id: 'descriptors', label: 'Profile text', description: 'Bio, custom status or pronouns'},
|
||||
],
|
||||
min_checked: 1,
|
||||
outcome: {type: 'screen', screen_id: 'root_user', reason: null, notice_id: null, url: null},
|
||||
},
|
||||
next_screen_id: null,
|
||||
},
|
||||
],
|
||||
notices: [
|
||||
{
|
||||
id: 'need_more_info',
|
||||
title: "We can't act on this yet",
|
||||
body: "To review this, we need a message where they say how old they are or make it clear they're under Fluxer's minimum age. Report that message instead.",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const messageSubmission = {
|
||||
channel_id: '1427410010101010101',
|
||||
message_id: '1427410099999999999',
|
||||
revision_hash: '6b1f0c9e2a7d4413',
|
||||
locale: 'de',
|
||||
steps: [
|
||||
{screen_id: 'root_message', option_id: 'abuse'},
|
||||
{screen_id: 'abuse', option_id: 'sexual'},
|
||||
{screen_id: 'sexual', option_id: 'minor_sexual'},
|
||||
{screen_id: 'minor_sexual', option_id: 'csam'},
|
||||
],
|
||||
};
|
||||
|
||||
const userSubmission = {
|
||||
user_id: '1427410010101010102',
|
||||
guild_id: '1427410010101010103',
|
||||
revision_hash: '9c41d2a07e5b18f3',
|
||||
locale: 'en-US',
|
||||
steps: [
|
||||
{screen_id: 'profile_intro'},
|
||||
{screen_id: 'profile_parts', item_ids: ['photo']},
|
||||
{screen_id: 'root_user', option_id: 'abuse'},
|
||||
{screen_id: 'profile_abuse', option_id: 'harassment'},
|
||||
],
|
||||
};
|
||||
|
||||
describe('ReportFlowResponse', () => {
|
||||
it('parses the user flow example', () => {
|
||||
expect(ReportFlowResponse.parse(userFlowResponse)).toEqual(userFlowResponse);
|
||||
});
|
||||
|
||||
it('accepts reasons and locales it does not know', () => {
|
||||
const response = {
|
||||
...userFlowResponse,
|
||||
locale: 'tlh-Latn',
|
||||
screens: [
|
||||
{
|
||||
...userFlowResponse.screens[0],
|
||||
options: [
|
||||
{
|
||||
id: 'future_option',
|
||||
label: 'A future option',
|
||||
outcome: {type: 'submit', screen_id: null, reason: 'reason_added_later', notice_id: null, url: null},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(ReportFlowResponse.safeParse(response).success).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects an unknown outcome type', () => {
|
||||
const response = {
|
||||
...userFlowResponse,
|
||||
screens: [
|
||||
{
|
||||
...userFlowResponse.screens[0],
|
||||
options: [
|
||||
{
|
||||
id: 'x',
|
||||
label: 'X',
|
||||
outcome: {type: 'redirect', screen_id: null, reason: null, notice_id: null, url: null},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(ReportFlowResponse.safeParse(response).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ReportFlowQuery', () => {
|
||||
it('defaults the surface to in_app', () => {
|
||||
expect(ReportFlowQuery.parse({})).toEqual({surface: 'in_app'});
|
||||
});
|
||||
|
||||
it('accepts any locale tag up to 35 characters', () => {
|
||||
expect(ReportFlowQuery.parse({surface: 'dsa', locale: 'xx-YY'})).toEqual({surface: 'dsa', locale: 'xx-YY'});
|
||||
expect(ReportFlowQuery.safeParse({locale: 'a'.repeat(36)}).success).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects an unknown surface', () => {
|
||||
expect(ReportFlowQuery.safeParse({surface: 'nope'}).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ReportFlowMessageSubmissionRequest', () => {
|
||||
it('parses the message submission example', () => {
|
||||
const parsed = ReportFlowMessageSubmissionRequest.parse(messageSubmission);
|
||||
expect(parsed.steps).toEqual(messageSubmission.steps);
|
||||
expect(parsed.revision_hash).toBe('6b1f0c9e2a7d4413');
|
||||
expect(parsed.locale).toBe('de');
|
||||
});
|
||||
|
||||
it('accepts a checklist step and an unknown locale string', () => {
|
||||
const body = {
|
||||
...messageSubmission,
|
||||
locale: 'zz-Unknown',
|
||||
steps: [
|
||||
{screen_id: 'root_message', option_id: 'private_info'},
|
||||
{screen_id: 'private_info', item_ids: ['email', 'phone']},
|
||||
],
|
||||
};
|
||||
expect(ReportFlowMessageSubmissionRequest.safeParse(body).success).toBe(true);
|
||||
});
|
||||
|
||||
it('accepts 16 steps and rejects 17', () => {
|
||||
const step = {screen_id: 'root_message', option_id: 'abuse'};
|
||||
const sixteen = {...messageSubmission, steps: Array.from({length: 16}, () => step)};
|
||||
const seventeen = {...messageSubmission, steps: Array.from({length: 17}, () => step)};
|
||||
expect(ReportFlowMessageSubmissionRequest.safeParse(sixteen).success).toBe(true);
|
||||
expect(ReportFlowMessageSubmissionRequest.safeParse(seventeen).success).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects an empty walk', () => {
|
||||
expect(ReportFlowMessageSubmissionRequest.safeParse({...messageSubmission, steps: []}).success).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts 32 item ids and rejects 33 or none', () => {
|
||||
const items = (count: number) => Array.from({length: count}, (_, index) => `item_${index}`);
|
||||
const body = (itemIds: Array<string>) => ({
|
||||
...messageSubmission,
|
||||
steps: [{screen_id: 'private_info', item_ids: itemIds}],
|
||||
});
|
||||
expect(ReportFlowMessageSubmissionRequest.safeParse(body(items(32))).success).toBe(true);
|
||||
expect(ReportFlowMessageSubmissionRequest.safeParse(body(items(33))).success).toBe(false);
|
||||
expect(ReportFlowMessageSubmissionRequest.safeParse(body([])).success).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts a 48-character id and rejects a 49-character id', () => {
|
||||
const body = (id: string) => ({...messageSubmission, steps: [{screen_id: id, option_id: 'abuse'}]});
|
||||
expect(ReportFlowMessageSubmissionRequest.safeParse(body('a'.repeat(48))).success).toBe(true);
|
||||
expect(ReportFlowMessageSubmissionRequest.safeParse(body('a'.repeat(49))).success).toBe(false);
|
||||
expect(
|
||||
ReportFlowMessageSubmissionRequest.safeParse({
|
||||
...messageSubmission,
|
||||
steps: [{screen_id: 'root_message', option_id: 'b'.repeat(49)}],
|
||||
}).success,
|
||||
).toBe(false);
|
||||
expect(
|
||||
ReportFlowMessageSubmissionRequest.safeParse({
|
||||
...messageSubmission,
|
||||
steps: [{screen_id: 'private_info', item_ids: ['c'.repeat(49)]}],
|
||||
}).success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('requires the revision hash and the message ids', () => {
|
||||
const {revision_hash: _revisionHash, ...withoutHash} = messageSubmission;
|
||||
const {message_id: _messageId, ...withoutMessage} = messageSubmission;
|
||||
expect(ReportFlowMessageSubmissionRequest.safeParse(withoutHash).success).toBe(false);
|
||||
expect(ReportFlowMessageSubmissionRequest.safeParse(withoutMessage).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ReportFlowUserSubmissionRequest', () => {
|
||||
it('parses the user submission example with info steps', () => {
|
||||
const parsed = ReportFlowUserSubmissionRequest.parse(userSubmission);
|
||||
expect(parsed.steps).toEqual(userSubmission.steps);
|
||||
expect(parsed.locale).toBe('en-US');
|
||||
});
|
||||
|
||||
it('makes the guild and locale optional', () => {
|
||||
const {guild_id: _guildId, locale: _locale, ...body} = userSubmission;
|
||||
expect(ReportFlowUserSubmissionRequest.safeParse(body).success).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -128,17 +128,6 @@ export const RpcRequest = z.discriminatedUnion('type', [
|
||||
)
|
||||
.describe('List of subscriptions to delete'),
|
||||
}),
|
||||
z.object({
|
||||
type: z
|
||||
.literal('send_apns_push')
|
||||
.describe('Request type for sending an APNs notification through the API HTTP/2 client'),
|
||||
user_id: SnowflakeType.describe('ID of the user receiving the notification'),
|
||||
subscription_id: createStringType().describe('ID of the APNs subscription to report back to the gateway'),
|
||||
device_token: createStringType(1, 4096).describe('APNs device token'),
|
||||
app_id: createStringType(1, 128).describe('Client app channel or bundle mapping identifier'),
|
||||
provider_environment: z.enum(['production', 'development']).describe('APNs provider environment'),
|
||||
payload: z.record(z.string(), z.unknown()).describe('Notification payload built by the gateway'),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('get_user_blocked_ids').describe('Request type for fetching blocked user IDs'),
|
||||
user_ids: z.array(SnowflakeType).max(RPC_USER_BATCH_MAX).describe('IDs of users to fetch blocked lists for'),
|
||||
@@ -485,17 +474,6 @@ export const RpcResponse = z.discriminatedUnion('type', [
|
||||
type: z.literal('delete_push_subscriptions').describe('Response type for push subscription deletion'),
|
||||
data: z.object({success: z.boolean().describe('Whether the deletion was successful')}).describe('Deletion result'),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('send_apns_push').describe('Response type for APNs push delivery'),
|
||||
data: z
|
||||
.object({
|
||||
success: z.boolean().describe('Whether APNs accepted the notification'),
|
||||
should_delete: z.boolean().describe('Whether the gateway should delete this subscription'),
|
||||
reason: z.string().optional().describe('APNs or local failure reason'),
|
||||
status_code: z.number().optional().describe('APNs HTTP status code'),
|
||||
})
|
||||
.describe('APNs delivery result'),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('get_user_blocked_ids').describe('Response type for blocked user IDs'),
|
||||
data: z.record(SnowflakeStringType, z.array(SnowflakeStringType)).describe('Blocked user IDs keyed by user ID'),
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {ListGuildEmojisResponse, LookupGuildResponse} from '@fluxer/schema/src/domains/admin/AdminSchemas';
|
||||
import {ListUserRelationshipsResponse} from '@fluxer/schema/src/domains/admin/AdminUserSchemas';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
function snowflakeAt(index: number): string {
|
||||
return String(100000000000000000n + BigInt(index));
|
||||
}
|
||||
|
||||
describe('LookupGuildResponse', () => {
|
||||
const validGuild = {
|
||||
id: '123456789012345678',
|
||||
owner_id: '111111111111111111',
|
||||
owner_username: 'owner',
|
||||
owner_global_name: null,
|
||||
owner_discriminator: '0001',
|
||||
name: 'Test Guild',
|
||||
vanity_url_code: null,
|
||||
icon: null,
|
||||
banner: null,
|
||||
splash: null,
|
||||
embed_splash: null,
|
||||
features: [],
|
||||
verification_level: 0,
|
||||
mfa_level: 0,
|
||||
nsfw_level: 0,
|
||||
explicit_content_filter: 0,
|
||||
default_message_notifications: 0,
|
||||
afk_channel_id: null,
|
||||
afk_timeout: 300,
|
||||
system_channel_id: null,
|
||||
system_channel_flags: 0,
|
||||
rules_channel_id: null,
|
||||
disabled_operations: 0,
|
||||
member_count: 1,
|
||||
channels: [],
|
||||
roles: [],
|
||||
};
|
||||
|
||||
it('accepts more roles than the old response ceiling', () => {
|
||||
const result = LookupGuildResponse.safeParse({
|
||||
guild: {
|
||||
...validGuild,
|
||||
roles: Array.from({length: 251}, (_, index) => ({
|
||||
id: snowflakeAt(index),
|
||||
name: `role-${index}`,
|
||||
color: 0,
|
||||
position: index,
|
||||
permissions: '0',
|
||||
hoist: false,
|
||||
mentionable: false,
|
||||
})),
|
||||
},
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.guild?.roles).toHaveLength(251);
|
||||
}
|
||||
});
|
||||
|
||||
it('accepts more channels than the old response ceiling', () => {
|
||||
const result = LookupGuildResponse.safeParse({
|
||||
guild: {
|
||||
...validGuild,
|
||||
channels: Array.from({length: 501}, (_, index) => ({
|
||||
id: snowflakeAt(index),
|
||||
name: `channel-${index}`,
|
||||
type: 0,
|
||||
position: index,
|
||||
parent_id: null,
|
||||
nsfw: false,
|
||||
url: null,
|
||||
})),
|
||||
},
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.guild?.channels).toHaveLength(501);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('ListGuildEmojisResponse', () => {
|
||||
it('accepts more emojis than the old response ceiling', () => {
|
||||
const result = ListGuildEmojisResponse.safeParse({
|
||||
guild_id: '123456789012345678',
|
||||
emojis: Array.from({length: 501}, (_, index) => ({
|
||||
id: snowflakeAt(index),
|
||||
name: `emoji_${index}`,
|
||||
animated: false,
|
||||
creator_id: '111111111111111111',
|
||||
media_url: 'https://example.com/emoji.png',
|
||||
})),
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.emojis).toHaveLength(501);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('ListUserRelationshipsResponse', () => {
|
||||
it('accepts more friends than the old response ceiling', () => {
|
||||
const result = ListUserRelationshipsResponse.safeParse({
|
||||
friends: Array.from({length: 10001}, (_, index) => ({
|
||||
target_user_id: snowflakeAt(index),
|
||||
category: 'friend',
|
||||
nickname: null,
|
||||
since: null,
|
||||
target: null,
|
||||
})),
|
||||
incoming_requests: [],
|
||||
outgoing_requests: [],
|
||||
blocked: [],
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.friends).toHaveLength(10001);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,132 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {ChannelTypes, MessageFlags, MessageTypes, WebhookTypes} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {
|
||||
ChannelFollowerStatsResponse,
|
||||
ChannelFollowRequest,
|
||||
FollowedChannelResponse,
|
||||
} from '@fluxer/schema/src/domains/channel/ChannelFollowSchemas';
|
||||
import {ChannelCreateRequest, ChannelUpdateRequest} from '@fluxer/schema/src/domains/channel/ChannelRequestSchemas';
|
||||
import {MessageResponseSchema} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
|
||||
import {
|
||||
WebhookCreateResponse,
|
||||
WebhookResponse,
|
||||
WebhookTokenResponse,
|
||||
} from '@fluxer/schema/src/domains/webhook/WebhookSchemas';
|
||||
import {ChannelTypeSchema} from '@fluxer/schema/src/primitives/ChannelValidators';
|
||||
import {MessageTypeSchema} from '@fluxer/schema/src/primitives/MessageValidators';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
const creator = {
|
||||
id: '100',
|
||||
username: 'owner',
|
||||
discriminator: '0001',
|
||||
global_name: null,
|
||||
avatar: null,
|
||||
avatar_color: null,
|
||||
flags: 0,
|
||||
mention_flags: 0,
|
||||
};
|
||||
|
||||
const incomingWebhook = {
|
||||
id: '1',
|
||||
guild_id: '2',
|
||||
channel_id: '3',
|
||||
name: 'Hook',
|
||||
avatar: null,
|
||||
type: WebhookTypes.INCOMING,
|
||||
token: 'secret',
|
||||
};
|
||||
|
||||
describe('announcement channel type', () => {
|
||||
it('accepts type 5 as a channel type', () => {
|
||||
expect(ChannelTypeSchema.parse(ChannelTypes.GUILD_ANNOUNCEMENT)).toBe(5);
|
||||
});
|
||||
|
||||
it('creates an announcement channel with the text channel fields', () => {
|
||||
const parsed = ChannelCreateRequest.parse({
|
||||
type: ChannelTypes.GUILD_ANNOUNCEMENT,
|
||||
name: 'news',
|
||||
topic: 'Updates',
|
||||
rate_limit_per_user: 10,
|
||||
});
|
||||
expect(parsed).toMatchObject({type: 5, name: 'news', topic: 'Updates', rate_limit_per_user: 10, nsfw: false});
|
||||
});
|
||||
|
||||
it('updates an announcement channel through its own variant', () => {
|
||||
expect(ChannelUpdateRequest.parse({type: ChannelTypes.GUILD_ANNOUNCEMENT, topic: 'New'})).toMatchObject({
|
||||
type: 5,
|
||||
topic: 'New',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('channel follow add message type', () => {
|
||||
it('accepts type 12 as a message type', () => {
|
||||
expect(MessageTypeSchema.parse(MessageTypes.CHANNEL_FOLLOW_ADD)).toBe(12);
|
||||
});
|
||||
});
|
||||
|
||||
describe('channel follow schemas', () => {
|
||||
it('parses the target channel of a follow request', () => {
|
||||
expect(ChannelFollowRequest.parse({webhook_channel_id: '123'})).toEqual({webhook_channel_id: 123n});
|
||||
expect(ChannelFollowRequest.safeParse({webhook_channel_id: 'abc'}).success).toBe(false);
|
||||
expect(ChannelFollowRequest.safeParse({}).success).toBe(false);
|
||||
});
|
||||
|
||||
it('describes the followed channel and its webhook', () => {
|
||||
expect(FollowedChannelResponse.parse({channel_id: '1', webhook_id: '2'})).toEqual({
|
||||
channel_id: '1',
|
||||
webhook_id: '2',
|
||||
});
|
||||
});
|
||||
|
||||
it('reports follower counts as integers', () => {
|
||||
expect(ChannelFollowerStatsResponse.parse({channel_count: 3, guild_count: 2})).toEqual({
|
||||
channel_count: 3,
|
||||
guild_count: 2,
|
||||
});
|
||||
expect(ChannelFollowerStatsResponse.safeParse({channel_count: 1.5, guild_count: 1}).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('webhook responses', () => {
|
||||
it('keeps the token required on token responses', () => {
|
||||
expect(WebhookTokenResponse.parse(incomingWebhook)).toEqual(incomingWebhook);
|
||||
const {token: _token, ...withoutToken} = incomingWebhook;
|
||||
expect(WebhookTokenResponse.safeParse(withoutToken).success).toBe(false);
|
||||
expect(WebhookCreateResponse.safeParse({...withoutToken, user: creator}).success).toBe(false);
|
||||
});
|
||||
|
||||
it('omits the token and includes the source on follower webhooks', () => {
|
||||
const {token: _token, ...withoutToken} = incomingWebhook;
|
||||
const follower = {
|
||||
...withoutToken,
|
||||
type: WebhookTypes.CHANNEL_FOLLOWER,
|
||||
user: creator,
|
||||
source_guild: {id: '7', name: 'Source', icon: null},
|
||||
source_channel: {id: '8', name: 'news'},
|
||||
};
|
||||
const parsed = WebhookResponse.parse(follower);
|
||||
expect(parsed).not.toHaveProperty('token');
|
||||
expect(parsed.source_guild).toEqual({id: '7', name: 'Source', icon: null});
|
||||
expect(parsed.source_channel).toEqual({id: '8', name: 'news'});
|
||||
});
|
||||
|
||||
it('rejects unknown webhook types', () => {
|
||||
expect(WebhookResponse.safeParse({...incomingWebhook, type: 3, user: creator}).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('message responses', () => {
|
||||
it('allows a message reference without a message id', () => {
|
||||
const shape = MessageResponseSchema.shape.message_reference;
|
||||
const reference = {type: 0, guild_id: '1', channel_id: '2'};
|
||||
expect(shape.parse(reference)).toEqual(reference);
|
||||
});
|
||||
|
||||
it('accepts the crosspost flags', () => {
|
||||
const flags = MessageFlags.CROSSPOSTED | MessageFlags.IS_CROSSPOST | MessageFlags.SOURCE_MESSAGE_DELETED;
|
||||
expect(MessageResponseSchema.shape.flags.parse(flags)).toBe(flags);
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {MAX_GUILD_STICKER_TAGS} from '@fluxer/constants/src/LimitConstants';
|
||||
import {GuildStickerResponse} from '@fluxer/schema/src/domains/guild/GuildEmojiSchemas';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
const buildSticker = (tagCount: number) => ({
|
||||
id: '1234567890123456789',
|
||||
name: 'sticker',
|
||||
description: 'A sticker',
|
||||
tags: Array.from({length: tagCount}, (_, index) => `tag${index}`),
|
||||
animated: false,
|
||||
nsfw: false,
|
||||
});
|
||||
|
||||
describe('GuildStickerResponse', () => {
|
||||
it('accepts the maximum tag count', () => {
|
||||
expect(GuildStickerResponse.safeParse(buildSticker(MAX_GUILD_STICKER_TAGS)).success).toBe(true);
|
||||
});
|
||||
it('rejects one tag beyond the maximum', () => {
|
||||
expect(GuildStickerResponse.safeParse(buildSticker(MAX_GUILD_STICKER_TAGS + 1)).success).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,84 +1,8 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
MAX_GUILD_ROLES,
|
||||
MAX_GUILD_STICKER_TAGS,
|
||||
MAX_TEMP_BAN_DURATION_SECONDS,
|
||||
MIN_TEMP_BAN_DURATION_SECONDS,
|
||||
} from '@fluxer/constants/src/LimitConstants';
|
||||
import {
|
||||
DiscoveryApplicationPatchRequest,
|
||||
DiscoveryApplicationRequest,
|
||||
DiscoverySearchQuery,
|
||||
} from '@fluxer/schema/src/domains/guild/GuildDiscoverySchemas';
|
||||
import {
|
||||
GuildBanCreateRequest,
|
||||
GuildMemberUpdateRequest,
|
||||
GuildStickerCreateRequest,
|
||||
} from '@fluxer/schema/src/domains/guild/GuildRequestSchemas';
|
||||
import {TemplateChannel} from '@fluxer/schema/src/domains/guild/GuildTemplateSchemas';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
describe.each([
|
||||
{name: 'application', schema: DiscoveryApplicationRequest.shape.primary_language},
|
||||
{name: 'application patch', schema: DiscoveryApplicationPatchRequest.shape.primary_language},
|
||||
{name: 'search', schema: DiscoverySearchQuery.shape.language},
|
||||
])('discovery $name language', ({schema}) => {
|
||||
it.each(['en-US', 'sv-SE', undefined])('preserves supported or omitted language %j', (language) => {
|
||||
expect(schema.parse(language)).toBe(language);
|
||||
});
|
||||
it.each(['unsupported', 'EN-US', ' en-US '])('rejects unsupported language %j without normalization', (language) => {
|
||||
expect(schema.safeParse(language).error?.issues).toEqual([
|
||||
{code: 'custom', message: 'Unsupported language code', path: []},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GuildBanCreateRequest', () => {
|
||||
it('accepts permanent bans and arbitrary temporary durations within range', () => {
|
||||
expect(GuildBanCreateRequest.safeParse({ban_duration_seconds: 0}).success).toBe(true);
|
||||
expect(GuildBanCreateRequest.safeParse({ban_duration_seconds: MIN_TEMP_BAN_DURATION_SECONDS}).success).toBe(true);
|
||||
expect(GuildBanCreateRequest.safeParse({ban_duration_seconds: 3601}).success).toBe(true);
|
||||
expect(GuildBanCreateRequest.safeParse({ban_duration_seconds: MAX_TEMP_BAN_DURATION_SECONDS}).success).toBe(true);
|
||||
});
|
||||
it('rejects temporary ban durations outside the allowed range', () => {
|
||||
expect(GuildBanCreateRequest.safeParse({ban_duration_seconds: MIN_TEMP_BAN_DURATION_SECONDS - 1}).success).toBe(
|
||||
false,
|
||||
);
|
||||
expect(GuildBanCreateRequest.safeParse({ban_duration_seconds: MAX_TEMP_BAN_DURATION_SECONDS + 1}).success).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const buildRoleIds = (count: number) =>
|
||||
Array.from({length: count}, (_, index) => `${1234567890123456789n + BigInt(index)}`);
|
||||
|
||||
describe('GuildMemberUpdateRequest', () => {
|
||||
it('accepts every role a member can hold', () => {
|
||||
expect(GuildMemberUpdateRequest.safeParse({roles: buildRoleIds(MAX_GUILD_ROLES)}).success).toBe(true);
|
||||
});
|
||||
it('rejects one role beyond the guild role limit', () => {
|
||||
expect(GuildMemberUpdateRequest.safeParse({roles: buildRoleIds(MAX_GUILD_ROLES + 1)}).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GuildStickerCreateRequest', () => {
|
||||
const buildTags = (count: number) => Array.from({length: count}, (_, index) => `tag${index}`);
|
||||
const image = `data:image/png;base64,${Buffer.from('sticker').toString('base64')}`;
|
||||
it('accepts the maximum tag count', () => {
|
||||
expect(
|
||||
GuildStickerCreateRequest.safeParse({name: 'sticker', tags: buildTags(MAX_GUILD_STICKER_TAGS), image}).success,
|
||||
).toBe(true);
|
||||
});
|
||||
it('rejects one tag beyond the maximum', () => {
|
||||
expect(
|
||||
GuildStickerCreateRequest.safeParse({name: 'sticker', tags: buildTags(MAX_GUILD_STICKER_TAGS + 1), image})
|
||||
.success,
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TemplateChannel permission overwrites', () => {
|
||||
it.each([
|
||||
['role', 0],
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {JobLedgerEntrySchema, ListJobsQuery, ListJobsRequest} from '@fluxer/schema/src/domains/admin/JobsSchemas';
|
||||
import {describe, expect, test} from 'vitest';
|
||||
|
||||
describe('jobs schemas', () => {
|
||||
test('the job status enum rejects failed, which the ledger never writes', () => {
|
||||
expect(ListJobsQuery.safeParse({status: 'failed'}).success).toBe(false);
|
||||
expect(ListJobsRequest.safeParse({status: 'failed'}).success).toBe(false);
|
||||
expect(JobLedgerEntrySchema.shape.status.safeParse('failed').success).toBe(false);
|
||||
});
|
||||
|
||||
test.each(['queued', 'running', 'succeeded', 'cancelled', 'deadletter'])(
|
||||
'accepts ledger status %s across query, request, and response',
|
||||
(status) => {
|
||||
expect(ListJobsQuery.parse({status}).status).toBe(status);
|
||||
expect(ListJobsRequest.parse({status}).status).toBe(status);
|
||||
expect(JobLedgerEntrySchema.shape.status.parse(status)).toBe(status);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('job pagination cursor', () => {
|
||||
const cursor = {
|
||||
cursor_bucket_day: '2024-02-29',
|
||||
cursor_created_at: '2024-02-29T12:00:00Z',
|
||||
cursor_job_id: '123',
|
||||
};
|
||||
|
||||
test('accepts a complete cursor and applies query defaults', () => {
|
||||
expect(ListJobsQuery.parse(cursor)).toEqual({...cursor, limit: 50, max_lookback_days: 14});
|
||||
});
|
||||
|
||||
test.each(['cursor_bucket_day', 'cursor_created_at', 'cursor_job_id'] as const)(
|
||||
'rejects a cursor missing %s',
|
||||
(field) => {
|
||||
expect(ListJobsQuery.safeParse({...cursor, [field]: undefined})).toMatchObject({
|
||||
success: false,
|
||||
error: {issues: [{path: [field]}]},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.each([
|
||||
{cursor_bucket_day: '2023-02-29'},
|
||||
{cursor_bucket_day: '2024-04-31'},
|
||||
{cursor_created_at: '2024-02-30T12:00:00Z'},
|
||||
{cursor_job_id: '01'},
|
||||
])('rejects malformed cursor component %j', (invalid) => {
|
||||
expect(ListJobsQuery.safeParse({...cursor, ...invalid}).success).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,3 @@
|
||||
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,
|
||||
@@ -15,127 +5,6 @@ import {
|
||||
} 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_ANNOUNCEMENT,
|
||||
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,
|
||||
rtc_p2p: true,
|
||||
};
|
||||
expect(ChannelUpdateRequestBody.parse(input)).toEqual({...input, topic: 'Topic', parent_id: 123n});
|
||||
});
|
||||
|
||||
it('keeps only text and announcement conversions as a body type', () => {
|
||||
expect(ChannelUpdateRequestBody.parse({type: ChannelTypes.GUILD_TEXT})).toEqual({type: ChannelTypes.GUILD_TEXT});
|
||||
expect(ChannelUpdateRequestBody.parse({type: ChannelTypes.GUILD_ANNOUNCEMENT})).toEqual({
|
||||
type: ChannelTypes.GUILD_ANNOUNCEMENT,
|
||||
});
|
||||
expect(ChannelUpdateRequestBody.parse({type: ChannelTypes.GUILD_VOICE})).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
const bulkDeleteDefaults = {
|
||||
scope: 'selected',
|
||||
include_dms: true,
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
type ReportAdminResponseSchema,
|
||||
type ResolveReportResponse,
|
||||
SearchReportsRequest,
|
||||
} from '@fluxer/schema/src/domains/admin/AdminSchemas';
|
||||
import {
|
||||
ReportFlowQuery,
|
||||
ReportFlowSurface,
|
||||
ReportFlowTargetType,
|
||||
} from '@fluxer/schema/src/domains/report/ReportFlowSchemas';
|
||||
import {
|
||||
createInt32EnumType,
|
||||
createNamedLiteralUnion,
|
||||
createNamedStringLiteralUnion,
|
||||
withFieldDescription,
|
||||
withOpenApiType,
|
||||
} from '@fluxer/schema/src/primitives/SchemaPrimitives';
|
||||
import {schemaMetadata} from '@fluxer/schema/src/SchemaMetadata';
|
||||
import {describe, expect, expectTypeOf, it} from 'vitest';
|
||||
import {z} from 'zod';
|
||||
|
||||
describe('named literal union inference', () => {
|
||||
it('infers string literals from a bare union', () => {
|
||||
const schema = createNamedStringLiteralUnion([
|
||||
['asc', 'asc', 'Ascending'],
|
||||
['desc', 'desc', 'Descending'],
|
||||
]);
|
||||
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<'asc' | 'desc'>();
|
||||
expect(schema.options.map((option) => option.value)).toEqual(['asc', 'desc']);
|
||||
});
|
||||
it('infers string literals through withOpenApiType', () => {
|
||||
const schema = withOpenApiType(
|
||||
createNamedStringLiteralUnion([
|
||||
['asc', 'asc', 'Ascending'],
|
||||
['desc', 'desc', 'Descending'],
|
||||
]),
|
||||
'TypeTestSortOrder',
|
||||
);
|
||||
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<'asc' | 'desc'>();
|
||||
expect(schemaMetadata.get(schema)?.name).toBe('TypeTestSortOrder');
|
||||
});
|
||||
it('infers string literals through withFieldDescription', () => {
|
||||
const schema = withFieldDescription(
|
||||
createNamedStringLiteralUnion([
|
||||
['asc', 'asc', 'Ascending'],
|
||||
['desc', 'desc', 'Descending'],
|
||||
]),
|
||||
'Sort order',
|
||||
);
|
||||
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<'asc' | 'desc'>();
|
||||
expect(schema.description).toBe('Sort order');
|
||||
});
|
||||
it('infers string literals through both wrappers and object modifiers', () => {
|
||||
const schema = z.object({
|
||||
order: withFieldDescription(
|
||||
withOpenApiType(
|
||||
createNamedStringLiteralUnion([
|
||||
['asc', 'asc', 'Ascending'],
|
||||
['desc', 'desc', 'Descending'],
|
||||
]),
|
||||
'TypeTestNestedSortOrder',
|
||||
),
|
||||
'Sort order',
|
||||
).optional(),
|
||||
fallback: withOpenApiType(
|
||||
createNamedStringLiteralUnion([
|
||||
['asc', 'asc'],
|
||||
['desc', 'desc'],
|
||||
]),
|
||||
'TypeTestFallbackSortOrder',
|
||||
).default('asc'),
|
||||
});
|
||||
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<{order?: 'asc' | 'desc'; fallback: 'asc' | 'desc'}>();
|
||||
expect(schema.parse({})).toEqual({fallback: 'asc'});
|
||||
});
|
||||
it('infers number literals from a bare union', () => {
|
||||
const schema = createNamedLiteralUnion([
|
||||
[0, 'ROLE', 'A role'],
|
||||
[1, 'MEMBER', 'A member'],
|
||||
]);
|
||||
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<0 | 1>();
|
||||
expect(schema.options.map((option) => option.value)).toEqual([0, 1]);
|
||||
});
|
||||
it('infers number literals through withOpenApiType and withFieldDescription', () => {
|
||||
const named = withOpenApiType(
|
||||
createNamedLiteralUnion([
|
||||
[0, 'ROLE', 'A role'],
|
||||
[1, 'MEMBER', 'A member'],
|
||||
]),
|
||||
'TypeTestOverwriteType',
|
||||
);
|
||||
const described = withFieldDescription(
|
||||
createNamedLiteralUnion([
|
||||
[0, 'ROLE', 'A role'],
|
||||
[1, 'MEMBER', 'A member'],
|
||||
]),
|
||||
'Overwrite type',
|
||||
);
|
||||
expectTypeOf<z.infer<typeof named>>().toEqualTypeOf<0 | 1>();
|
||||
expectTypeOf<z.infer<typeof described>>().toEqualTypeOf<0 | 1>();
|
||||
expect(named.safeParse(2).success).toBe(false);
|
||||
expect(described.safeParse(1).success).toBe(true);
|
||||
});
|
||||
it('infers the widened value type from pairs that are not literals', () => {
|
||||
const pairs: ReadonlyArray<readonly [string, string]> = [
|
||||
['asc', 'asc'],
|
||||
['desc', 'desc'],
|
||||
];
|
||||
const schema = createNamedStringLiteralUnion(pairs);
|
||||
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<string>();
|
||||
expect(schema.safeParse('asc').success).toBe(true);
|
||||
});
|
||||
it('infers the report flow target and surface unions', () => {
|
||||
expectTypeOf<ReportFlowTargetType>().toEqualTypeOf<'message' | 'user' | 'guild'>();
|
||||
expectTypeOf<ReportFlowSurface>().toEqualTypeOf<'in_app' | 'dsa'>();
|
||||
expectTypeOf<ReportFlowQuery['surface']>().toEqualTypeOf<'in_app' | 'dsa'>();
|
||||
expect(ReportFlowTargetType.options.map((option) => option.value)).toEqual(['message', 'user', 'guild']);
|
||||
expect(ReportFlowSurface.options.map((option) => option.value)).toEqual(['in_app', 'dsa']);
|
||||
expect(ReportFlowQuery.parse({}).surface).toBe('in_app');
|
||||
});
|
||||
});
|
||||
|
||||
describe('int32 enum inference', () => {
|
||||
it('infers number literals from a bare enum', () => {
|
||||
const schema = createInt32EnumType(
|
||||
[
|
||||
[0, 'PENDING', 'Pending'],
|
||||
[1, 'RESOLVED', 'Resolved'],
|
||||
],
|
||||
'Status',
|
||||
'TypeTestBareStatus',
|
||||
);
|
||||
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<0 | 1>();
|
||||
expect(schema.parse(1)).toBe(1);
|
||||
});
|
||||
it('infers number literals through withOpenApiType and withFieldDescription', () => {
|
||||
const named = withOpenApiType(
|
||||
createInt32EnumType(
|
||||
[
|
||||
[0, 'PENDING', 'Pending'],
|
||||
[1, 'RESOLVED', 'Resolved'],
|
||||
],
|
||||
'Status',
|
||||
'TypeTestWrappedStatus',
|
||||
),
|
||||
'TypeTestWrappedStatus',
|
||||
);
|
||||
const described = withFieldDescription(
|
||||
createInt32EnumType([
|
||||
[0, 'MESSAGE'],
|
||||
[1, 'USER'],
|
||||
[2, 'GUILD'],
|
||||
]),
|
||||
'Report type',
|
||||
);
|
||||
expectTypeOf<z.infer<typeof named>>().toEqualTypeOf<0 | 1>();
|
||||
expectTypeOf<z.infer<typeof described>>().toEqualTypeOf<0 | 1 | 2>();
|
||||
expect(named.safeParse(2).success).toBe(false);
|
||||
expect(described.safeParse(2).success).toBe(true);
|
||||
expect(schemaMetadata.get(named)).toEqual({
|
||||
name: 'TypeTestWrappedStatus',
|
||||
format: 'int32',
|
||||
enumEntries: [
|
||||
{value: 0, name: 'PENDING', description: 'Pending'},
|
||||
{value: 1, name: 'RESOLVED', description: 'Resolved'},
|
||||
],
|
||||
});
|
||||
});
|
||||
it('infers the widened value type from pairs that are not literals', () => {
|
||||
const pairs: ReadonlyArray<readonly [number, string]> = [
|
||||
[0, 'PENDING'],
|
||||
[1, 'RESOLVED'],
|
||||
];
|
||||
const schema = createInt32EnumType(pairs);
|
||||
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<number>();
|
||||
expect(schema.safeParse(1).success).toBe(true);
|
||||
});
|
||||
it('infers the report status and type literals on the admin schemas', () => {
|
||||
expectTypeOf<z.infer<typeof ReportAdminResponseSchema>['status']>().toEqualTypeOf<0 | 1>();
|
||||
expectTypeOf<z.infer<typeof ReportAdminResponseSchema>['report_type']>().toEqualTypeOf<0 | 1 | 2>();
|
||||
expectTypeOf<z.infer<typeof ResolveReportResponse>['status']>().toEqualTypeOf<0 | 1>();
|
||||
expectTypeOf<SearchReportsRequest['status']>().toEqualTypeOf<0 | 1 | undefined>();
|
||||
expectTypeOf<SearchReportsRequest['report_type']>().toEqualTypeOf<0 | 1 | 2 | undefined>();
|
||||
expect(SearchReportsRequest.safeParse({status: 2}).success).toBe(false);
|
||||
expect(SearchReportsRequest.safeParse({report_type: 2}).success).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -4,88 +4,6 @@ import {isValidSingleUnicodeEmoji} from '@fluxer/schema/src/primitives/EmojiVali
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
describe('isValidSingleUnicodeEmoji', () => {
|
||||
describe('valid single emojis', () => {
|
||||
it('accepts simple emoji', () => {
|
||||
expect(isValidSingleUnicodeEmoji('👍')).toBe(true);
|
||||
});
|
||||
it('accepts common face emojis', () => {
|
||||
expect(isValidSingleUnicodeEmoji('😀')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('😂')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('🥺')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('😡')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('🤔')).toBe(true);
|
||||
});
|
||||
it('accepts emoji with skin tone modifier', () => {
|
||||
expect(isValidSingleUnicodeEmoji('👍🏿')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('👍🏻')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('👍🏽')).toBe(true);
|
||||
});
|
||||
it('accepts ZWJ sequence emojis', () => {
|
||||
expect(isValidSingleUnicodeEmoji('👨👩👧👦')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('👩💻')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('🧑🎄')).toBe(true);
|
||||
});
|
||||
it('accepts ZWJ sequence with skin tone at correct position', () => {
|
||||
expect(isValidSingleUnicodeEmoji('🧑🏿🎄')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('👩🏻💻')).toBe(true);
|
||||
});
|
||||
it('accepts flag emojis', () => {
|
||||
expect(isValidSingleUnicodeEmoji('🇺🇸')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('🇬🇧')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('🏳️🌈')).toBe(true);
|
||||
});
|
||||
it('accepts single regional indicator symbols', () => {
|
||||
expect(isValidSingleUnicodeEmoji('🇦')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('🇧')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('🇵')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('🇿')).toBe(true);
|
||||
});
|
||||
it('accepts all 26 regional indicator symbols', () => {
|
||||
for (let cp = 0x1f1e6; cp <= 0x1f1ff; cp++) {
|
||||
expect(isValidSingleUnicodeEmoji(String.fromCodePoint(cp))).toBe(true);
|
||||
}
|
||||
});
|
||||
it('accepts keycap emojis', () => {
|
||||
expect(isValidSingleUnicodeEmoji('1️⃣')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('#️⃣')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('*️⃣')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('0️⃣')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('9️⃣')).toBe(true);
|
||||
});
|
||||
it('accepts variation selector emojis', () => {
|
||||
expect(isValidSingleUnicodeEmoji('❤️')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('☀️')).toBe(true);
|
||||
});
|
||||
it('accepts text-style emojis without variation selector', () => {
|
||||
expect(isValidSingleUnicodeEmoji('❤')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('☀')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('☺')).toBe(true);
|
||||
});
|
||||
it('accepts copyright, registered, and trademark symbols', () => {
|
||||
expect(isValidSingleUnicodeEmoji('©')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('©️')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('®')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('®️')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('™')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('™️')).toBe(true);
|
||||
});
|
||||
it('accepts animal and nature emojis', () => {
|
||||
expect(isValidSingleUnicodeEmoji('🐱')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('🌸')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('🌍')).toBe(true);
|
||||
});
|
||||
it('accepts food and object emojis', () => {
|
||||
expect(isValidSingleUnicodeEmoji('🍕')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('🎸')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('💎')).toBe(true);
|
||||
});
|
||||
it('accepts symbol emojis', () => {
|
||||
expect(isValidSingleUnicodeEmoji('✅')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('❌')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('⚠️')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('💯')).toBe(true);
|
||||
});
|
||||
});
|
||||
describe('invalid inputs', () => {
|
||||
it('rejects empty string', () => {
|
||||
expect(isValidSingleUnicodeEmoji('')).toBe(false);
|
||||
@@ -132,10 +50,6 @@ describe('isValidSingleUnicodeEmoji', () => {
|
||||
it('rejects skin tone at wrong position in ZWJ sequence', () => {
|
||||
expect(isValidSingleUnicodeEmoji('🧑🎄🏿')).toBe(false);
|
||||
});
|
||||
it('accepts standalone skin tone modifier as valid emoji', () => {
|
||||
expect(isValidSingleUnicodeEmoji('🏿')).toBe(true);
|
||||
expect(isValidSingleUnicodeEmoji('🏻')).toBe(true);
|
||||
});
|
||||
it('rejects standalone ZWJ character', () => {
|
||||
expect(isValidSingleUnicodeEmoji('\u200D')).toBe(false);
|
||||
});
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {WebhookTypeSchema} from '@fluxer/schema/src/primitives/WebhookValidators';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
describe('WebhookTypeSchema', () => {
|
||||
it.each([1, 2])('accepts webhook type %i', (value) => {
|
||||
expect(WebhookTypeSchema.parse(value)).toBe(value);
|
||||
});
|
||||
|
||||
it.each([0, 3, -1, 1.5, '1', null])('rejects unknown or nonnumeric webhook type %j', (value) => {
|
||||
expect(WebhookTypeSchema.safeParse(value).success).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user