This commit is contained in:
@@ -14,9 +14,11 @@ const guildId = '223456789012345678';
|
||||
const userId = '323456789012345678';
|
||||
const settings = vi.hoisted(() => ({ enabled: true, guildId: '223456789012345678' }));
|
||||
const handle = vi.hoisted(() => vi.fn());
|
||||
const readSettings = vi.hoisted(() => vi.fn());
|
||||
const claimInteraction = vi.hoisted(() => vi.fn());
|
||||
vi.mock('../../src/lib/server/discord/store.js', () => ({
|
||||
getDiscordSettings: () => settings,
|
||||
claimDiscordInteraction: () => true
|
||||
getDiscordSettings: readSettings,
|
||||
claimDiscordInteraction: claimInteraction
|
||||
}));
|
||||
vi.mock('../../src/lib/server/discord/interactions.js', () => ({
|
||||
handleDiscordInteraction: handle
|
||||
@@ -54,6 +56,11 @@ beforeEach(() => {
|
||||
);
|
||||
vi.stubEnv('EC_DISCORD_APPLICATION_ID', applicationId);
|
||||
settings.enabled = true;
|
||||
readSettings.mockReset().mockImplementation(() => settings);
|
||||
claimInteraction.mockReset().mockReturnValue(true);
|
||||
vi.spyOn(console, 'info').mockImplementation(() => {});
|
||||
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
handle.mockReset().mockResolvedValue({
|
||||
type: 4,
|
||||
data: { flags: 64, content: 'Private results', allowed_mentions: { parse: [] } }
|
||||
@@ -63,6 +70,7 @@ afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.unstubAllEnvs();
|
||||
vi.unstubAllGlobals();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('Discord signed webhook boundary', () => {
|
||||
@@ -114,19 +122,76 @@ describe('Discord signed webhook boundary', () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('does not dispatch disabled or foreign-guild interactions', async () => {
|
||||
it.each(['disabled', 'foreign-guild'])(
|
||||
'acknowledges %s interactions before checking configuration without dispatching them',
|
||||
async (mode) => {
|
||||
const fetcher = vi.fn().mockResolvedValue(new Response('{}'));
|
||||
vi.stubGlobal('fetch', fetcher);
|
||||
const { POST } = await import('../../src/routes/api/discord/interactions/+server.js');
|
||||
settings.enabled = mode !== 'disabled';
|
||||
const result = await POST({
|
||||
request: signedRequest({
|
||||
...interaction(),
|
||||
guild_id: mode === 'foreign-guild' ? userId : guildId
|
||||
})
|
||||
});
|
||||
expect(await result.json()).toEqual({ type: 5, data: { flags: 64 } });
|
||||
expect(readSettings).not.toHaveBeenCalled();
|
||||
expect(claimInteraction).not.toHaveBeenCalled();
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
expect(fetcher).toHaveBeenCalledOnce();
|
||||
expect(fetcher.mock.calls[0][1].body).toContain('not enabled');
|
||||
expect(handle).not.toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
|
||||
it('explains a signed application ID mismatch instead of making the button time out', async () => {
|
||||
const { POST } = await import('../../src/routes/api/discord/interactions/+server.js');
|
||||
const foreign = await POST({
|
||||
request: signedRequest({ ...interaction(), guild_id: userId })
|
||||
const result = await POST({
|
||||
request: signedRequest({ ...interaction(), application_id: guildId })
|
||||
});
|
||||
expect((await foreign.json()).data.flags).toBe(64);
|
||||
settings.enabled = false;
|
||||
expect(
|
||||
(await (await POST({ request: signedRequest(interaction()) })).json()).data.flags
|
||||
).toBe(64);
|
||||
expect(result.status).toBe(200);
|
||||
expect(await result.json()).toMatchObject({
|
||||
type: 4,
|
||||
data: { flags: 64, content: expect.stringContaining('EC_DISCORD_APPLICATION_ID') }
|
||||
});
|
||||
expect(claimInteraction).not.toHaveBeenCalled();
|
||||
expect(handle).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each(['ec:d:01234567890123456789', 'ec:b:t5:all:0:', 'ec:maps:t5', 'ec:leader:t5'])(
|
||||
'acknowledges %s before any database work, even when the replay write fails',
|
||||
async (customId) => {
|
||||
const fetcher = vi.fn().mockResolvedValue(new Response('{}'));
|
||||
vi.stubGlobal('fetch', fetcher);
|
||||
claimInteraction.mockImplementation(() => {
|
||||
throw new Error('private database failure');
|
||||
});
|
||||
const { POST } = await import('../../src/routes/api/discord/interactions/+server.js');
|
||||
const result = await POST({
|
||||
request: signedRequest({
|
||||
...interaction(),
|
||||
type: 3,
|
||||
data: { custom_id: customId, component_type: 2 }
|
||||
})
|
||||
});
|
||||
expect(result.status).toBe(200);
|
||||
expect(await result.json()).toEqual({ type: 5, data: { flags: 64 } });
|
||||
expect(claimInteraction).not.toHaveBeenCalled();
|
||||
expect(readSettings).not.toHaveBeenCalled();
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
expect(claimInteraction).toHaveBeenCalledOnce();
|
||||
expect(handle).not.toHaveBeenCalled();
|
||||
expect(fetcher.mock.calls[0][1].body).toContain('could not complete');
|
||||
expect(JSON.stringify(vi.mocked(console.error).mock.calls)).not.toContain(
|
||||
'private database failure'
|
||||
);
|
||||
expect(JSON.stringify(vi.mocked(console.error).mock.calls)).not.toContain(
|
||||
'private-interaction-token'
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it('defers privately, dispatches once per interaction and delivers without bot credentials', async () => {
|
||||
const fetcher = vi.fn().mockResolvedValue(new Response('{}'));
|
||||
vi.stubGlobal('fetch', fetcher);
|
||||
@@ -139,6 +204,8 @@ describe('Discord signed webhook boundary', () => {
|
||||
pending
|
||||
);
|
||||
expect(handle).not.toHaveBeenCalled();
|
||||
expect(readSettings).not.toHaveBeenCalled();
|
||||
expect(claimInteraction).not.toHaveBeenCalled();
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
expect(handle).toHaveBeenCalledTimes(1);
|
||||
expect(fetcher).toHaveBeenCalledOnce();
|
||||
|
||||
Reference in New Issue
Block a user