// SPDX-License-Identifier: AGPL-3.0-or-later import {MessageFlags} from '@fluxer/constants/src/ChannelConstants'; import type {MessageResponse} from '@fluxer/schema/src/domains/message/MessageResponseSchemas'; import {afterEach, beforeEach, describe, expect, it} from 'vitest'; import {createTestAccount} from '../../auth/tests/AuthTestUtils'; import {createGuild} from '../../guild/tests/GuildTestUtils'; import {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness'; import {HTTP_STATUS} from '../../test/TestConstants'; import {createBuilder, createBuilderWithoutAuth} from '../../test/TestRequestBuilder'; import {createWebhook, deleteWebhook} from './WebhookTestUtils'; describe('Webhook execution advanced', () => { let harness: ApiTestHarness; beforeEach(async () => { harness = await createApiTestHarness(); }); afterEach(async () => { await harness?.shutdown(); }); describe('Message content', () => { it('edits webhook messages with suppress embeds flags', async () => { const owner = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, 'Webhook Edit Flags Guild'); const channelId = guild.system_channel_id!; const webhook = await createWebhook(harness, channelId, owner.token, 'Edit Flags Webhook'); const createdMessage = await createBuilderWithoutAuth(harness) .post(`/webhooks/${webhook.id}/${webhook.token}?wait=true`) .body({ content: 'Message with embed', embeds: [{title: 'Embed to suppress'}], }) .expect(HTTP_STATUS.OK) .execute(); const updatedMessage = await createBuilderWithoutAuth(harness) .patch(`/webhooks/${webhook.id}/${webhook.token}/messages/${createdMessage.id}`) .body({flags: MessageFlags.SUPPRESS_EMBEDS}) .expect(HTTP_STATUS.OK) .execute(); expect(updatedMessage.flags & MessageFlags.SUPPRESS_EMBEDS).not.toBe(0); const fetchedMessage = await createBuilder(harness, owner.token) .get(`/channels/${channelId}/messages/${createdMessage.id}`) .expect(HTTP_STATUS.OK) .execute(); expect(fetchedMessage.flags & MessageFlags.SUPPRESS_EMBEDS).not.toBe(0); expect(fetchedMessage.embeds).toHaveLength(0); await deleteWebhook(harness, webhook.id, owner.token); }); it('executes webhook with embeds', async () => { const owner = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, 'Webhook Embed Guild'); const channelId = guild.system_channel_id!; const webhook = await createWebhook(harness, channelId, owner.token, 'Embed Webhook'); const embed = { title: 'Test Embed', description: 'This is a test embed from webhook', color: 0x00ff00, }; await createBuilderWithoutAuth(harness) .post(`/webhooks/${webhook.id}/${webhook.token}`) .body({embeds: [embed]}) .expect(HTTP_STATUS.NO_CONTENT) .execute(); await deleteWebhook(harness, webhook.id, owner.token); }); it('executes webhook with content and embeds', async () => { const owner = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, 'Webhook Content Embed Guild'); const channelId = guild.system_channel_id!; const webhook = await createWebhook(harness, channelId, owner.token, 'Content Embed Webhook'); const result = await createBuilderWithoutAuth(harness) .post(`/webhooks/${webhook.id}/${webhook.token}?wait=true`) .body({ content: 'Message with embed', embeds: [{title: 'Accompanying Embed'}], }) .expect(HTTP_STATUS.OK) .execute(); expect(result.content).toBe('Message with embed'); await deleteWebhook(harness, webhook.id, owner.token); }); it('executes webhook with an empty embed title', async () => { const owner = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, 'Webhook Empty Embed Title Guild'); const channelId = guild.system_channel_id!; const webhook = await createWebhook(harness, channelId, owner.token, 'Empty Embed Title Webhook'); const result = await createBuilderWithoutAuth(harness) .post(`/webhooks/${webhook.id}/${webhook.token}?wait=true`) .body({ embeds: [{title: '', description: 'Description with intentionally empty title'}], }) .expect(HTTP_STATUS.OK) .execute(); expect(result.embeds).toHaveLength(1); expect(result.embeds?.[0]?.title).toBe(''); expect(result.embeds?.[0]?.description).toBe('Description with intentionally empty title'); await deleteWebhook(harness, webhook.id, owner.token); }); it('executes webhook with an empty embed description', async () => { const owner = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, 'Webhook Empty Embed Description Guild'); const channelId = guild.system_channel_id!; const webhook = await createWebhook(harness, channelId, owner.token, 'Empty Embed Description Webhook'); const result = await createBuilderWithoutAuth(harness) .post(`/webhooks/${webhook.id}/${webhook.token}?wait=true`) .body({ embeds: [{title: 'Workflow run', description: ''}], }) .expect(HTTP_STATUS.OK) .execute(); expect(result.embeds).toHaveLength(1); expect(result.embeds?.[0]?.title).toBe('Workflow run'); expect(result.embeds?.[0]?.description).toBeNull(); await deleteWebhook(harness, webhook.id, owner.token); }); it('executes webhook with custom username', async () => { const owner = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, 'Webhook Username Guild'); const channelId = guild.system_channel_id!; const webhook = await createWebhook(harness, channelId, owner.token, 'Username Webhook'); const result = await createBuilderWithoutAuth(harness) .post(`/webhooks/${webhook.id}/${webhook.token}?wait=true`) .body({ content: 'Custom username message', username: 'Custom Bot Name', }) .expect(HTTP_STATUS.OK) .execute(); expect(result.content).toBe('Custom username message'); await deleteWebhook(harness, webhook.id, owner.token); }); }); describe('Validation', () => { it('rejects empty message', async () => { const owner = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, 'Webhook Validation Guild'); const channelId = guild.system_channel_id!; const webhook = await createWebhook(harness, channelId, owner.token, 'Empty Message Webhook'); await createBuilderWithoutAuth(harness) .post(`/webhooks/${webhook.id}/${webhook.token}`) .body({}) .expect(HTTP_STATUS.BAD_REQUEST, 'CANNOT_SEND_EMPTY_MESSAGE') .execute(); await deleteWebhook(harness, webhook.id, owner.token); }); it('rejects empty content string', async () => { const owner = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, 'Webhook Validation Guild'); const channelId = guild.system_channel_id!; const webhook = await createWebhook(harness, channelId, owner.token, 'Empty Content Webhook'); await createBuilderWithoutAuth(harness) .post(`/webhooks/${webhook.id}/${webhook.token}`) .body({content: ''}) .expect(HTTP_STATUS.BAD_REQUEST, 'CANNOT_SEND_EMPTY_MESSAGE') .execute(); await deleteWebhook(harness, webhook.id, owner.token); }); it('rejects whitespace only content', async () => { const owner = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, 'Webhook Validation Guild'); const channelId = guild.system_channel_id!; const webhook = await createWebhook(harness, channelId, owner.token, 'Whitespace Webhook'); await createBuilderWithoutAuth(harness) .post(`/webhooks/${webhook.id}/${webhook.token}`) .body({content: ' '}) .expect(HTTP_STATUS.BAD_REQUEST, 'CANNOT_SEND_EMPTY_MESSAGE') .execute(); await deleteWebhook(harness, webhook.id, owner.token); }); it('allows empty content with valid embed', async () => { const owner = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, 'Webhook Validation Guild'); const channelId = guild.system_channel_id!; const webhook = await createWebhook(harness, channelId, owner.token, 'Embed Only Webhook'); await createBuilderWithoutAuth(harness) .post(`/webhooks/${webhook.id}/${webhook.token}`) .body({ embeds: [{title: 'Embed only message'}], }) .expect(HTTP_STATUS.NO_CONTENT) .execute(); await deleteWebhook(harness, webhook.id, owner.token); }); }); describe('Wait parameter', () => { it('returns 204 without wait parameter', async () => { const owner = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, 'Webhook Wait Guild'); const channelId = guild.system_channel_id!; const webhook = await createWebhook(harness, channelId, owner.token, 'No Wait Webhook'); await createBuilderWithoutAuth(harness) .post(`/webhooks/${webhook.id}/${webhook.token}`) .body({content: 'No wait message'}) .expect(HTTP_STATUS.NO_CONTENT) .execute(); await deleteWebhook(harness, webhook.id, owner.token); }); it('returns message with wait=true', async () => { const owner = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, 'Webhook Wait Guild'); const channelId = guild.system_channel_id!; const webhook = await createWebhook(harness, channelId, owner.token, 'Wait True Webhook'); const result = await createBuilderWithoutAuth(harness) .post(`/webhooks/${webhook.id}/${webhook.token}?wait=true`) .body({content: 'Wait message'}) .expect(HTTP_STATUS.OK) .execute(); expect(result.id).toBeTruthy(); expect(result.content).toBe('Wait message'); expect(result.webhook_id).toBe(webhook.id); await deleteWebhook(harness, webhook.id, owner.token); }); it('returns 204 with wait=false', async () => { const owner = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, 'Webhook Wait Guild'); const channelId = guild.system_channel_id!; const webhook = await createWebhook(harness, channelId, owner.token, 'Wait False Webhook'); await createBuilderWithoutAuth(harness) .post(`/webhooks/${webhook.id}/${webhook.token}?wait=false`) .body({content: 'Wait false message'}) .expect(HTTP_STATUS.NO_CONTENT) .execute(); await deleteWebhook(harness, webhook.id, owner.token); }); }); describe('Slack compatible endpoint', () => { it('executes slack compatible webhook', async () => { const owner = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, 'Slack Webhook Guild'); const channelId = guild.system_channel_id!; const webhook = await createWebhook(harness, channelId, owner.token, 'Slack Compatible Webhook'); const {response, text} = await createBuilderWithoutAuth(harness) .post(`/webhooks/${webhook.id}/${webhook.token}/slack`) .body({text: 'Hello from Slack format'}) .expect(HTTP_STATUS.OK) .executeRaw(); expect(response.status).toBe(HTTP_STATUS.OK); expect(text).toBe('ok'); await deleteWebhook(harness, webhook.id, owner.token); }); it('rejects slack webhook with invalid token', async () => { const owner = await createTestAccount(harness); const guild = await createGuild(harness, owner.token, 'Slack Webhook Guild'); const channelId = guild.system_channel_id!; const webhook = await createWebhook(harness, channelId, owner.token, 'Slack Invalid Token'); await createBuilderWithoutAuth(harness) .post(`/webhooks/${webhook.id}/invalid_token/slack`) .body({text: 'Should fail'}) .expect(HTTP_STATUS.NOT_FOUND) .execute(); await deleteWebhook(harness, webhook.id, owner.token); }); }); });