-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
237 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { Button, Command, Modal } from '../builders' | ||
import { DiscordHono } from '../discord-hono' | ||
import { createFactory } from './create-factory' | ||
|
||
describe('createFactory', () => { | ||
const factory = createFactory() | ||
|
||
it('should create a DiscordHono instance', () => { | ||
const discord = factory.discord() | ||
expect(discord).toBeInstanceOf(DiscordHono) | ||
}) | ||
|
||
it('should create a command wrapper', () => { | ||
const commandMock = new Command('name', 'description') | ||
const handlerMock = vi.fn() | ||
const result = factory.command(commandMock, handlerMock) | ||
expect(result).toEqual({ command: commandMock, handler: handlerMock }) | ||
}) | ||
|
||
it('should create a component wrapper', () => { | ||
const componentMock = new Button('str', 'label') | ||
const handlerMock = vi.fn() | ||
const result = factory.component(componentMock, handlerMock) | ||
expect(result).toEqual({ component: componentMock, handler: handlerMock }) | ||
}) | ||
|
||
it('should create an autocomplete wrapper', () => { | ||
const commandMock = new Command('name', 'description') | ||
const autocompleteMock = vi.fn() | ||
const handlerMock = vi.fn() | ||
const result = factory.autocomplete(commandMock, autocompleteMock, handlerMock) | ||
expect(result).toEqual({ command: commandMock, autocomplete: autocompleteMock, handler: handlerMock }) | ||
}) | ||
|
||
it('should create a modal wrapper', () => { | ||
const modalMock = new Modal('unique_id', 'title') | ||
const handlerMock = vi.fn() | ||
const result = factory.modal(modalMock, handlerMock) | ||
expect(result).toEqual({ modal: modalMock, handler: handlerMock }) | ||
}) | ||
|
||
it('should create a cron wrapper', () => { | ||
const cronExpression = '0 0 * * *' | ||
const handlerMock = vi.fn() | ||
const result = factory.cron(cronExpression, handlerMock) | ||
expect(result).toEqual({ cron: cronExpression, handler: handlerMock }) | ||
}) | ||
|
||
it('should load wrappers into DiscordHono instance', () => { | ||
const app = new DiscordHono() | ||
const commandMock = new Command('name', 'description') | ||
const componentMock = new Button('str', 'label') | ||
const modalMock = new Modal('unique_id', 'title') | ||
const handlerMock = vi.fn() | ||
|
||
const wrappers = [ | ||
factory.command(commandMock, handlerMock), | ||
factory.component(componentMock, handlerMock), | ||
factory.modal(modalMock, handlerMock), | ||
factory.cron('0 0 * * *', handlerMock), | ||
] | ||
|
||
vi.spyOn(app, 'command') | ||
vi.spyOn(app, 'component') | ||
vi.spyOn(app, 'modal') | ||
vi.spyOn(app, 'cron') | ||
|
||
factory.loader(app, wrappers) | ||
|
||
expect(app.command).toHaveBeenCalledWith('name', handlerMock) | ||
expect(app.component).toHaveBeenCalledWith('str', handlerMock) | ||
expect(app.modal).toHaveBeenCalledWith('unique_id', handlerMock) | ||
expect(app.cron).toHaveBeenCalledWith('0 0 * * *', handlerMock) | ||
}) | ||
|
||
it('should throw an error for unknown wrapper type', () => { | ||
const app = new DiscordHono() | ||
const unknownWrapper = { unknownProp: 'value' } | ||
|
||
expect(() => factory.loader(app, [unknownWrapper as any])).toThrow('Interaction Loader Unknown Object') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { Rest } from '../rest/rest' | ||
import { _applications_$_commands, _applications_$_guilds_$_commands } from '../rest/rest-path' | ||
import { register } from './register' | ||
|
||
vi.mock('../rest/rest') | ||
vi.mock('../utils') | ||
|
||
describe('register function', () => { | ||
const mockPut = vi.fn() | ||
const mockCommands = [{ name: 'test', description: 'A test command' }] | ||
const mockApplicationId = '123456789' | ||
const mockToken = 'mock-token' | ||
|
||
beforeEach(() => { | ||
vi.clearAllMocks() | ||
// @ts-expect-error | ||
;(Rest as jest.Mock).mockImplementation(() => ({ | ||
put: mockPut, | ||
})) | ||
}) | ||
|
||
afterEach(() => { | ||
vi.restoreAllMocks() | ||
}) | ||
|
||
it('should throw an error if token is not provided', async () => { | ||
await expect(register(mockCommands, mockApplicationId, undefined)).rejects.toThrow('DISCORD_TOKEN') | ||
}) | ||
|
||
it('should throw an error if application_id is not provided', async () => { | ||
await expect(register(mockCommands, undefined, mockToken)).rejects.toThrow('DISCORD_APPLICATION_ID') | ||
}) | ||
|
||
it('should register commands for a specific guild', async () => { | ||
const mockGuildId = '987654321' | ||
const mockResponse = { ok: true, status: 200, statusText: 'OK' } | ||
mockPut.mockResolvedValue(mockResponse) | ||
|
||
const result = await register(mockCommands, mockApplicationId, mockToken, mockGuildId) | ||
|
||
expect(mockPut).toHaveBeenCalledWith( | ||
_applications_$_guilds_$_commands, | ||
[mockApplicationId, mockGuildId], | ||
expect.any(Array), | ||
) | ||
expect(result).toContain('✅ Success') | ||
}) | ||
|
||
it('should register global commands when guild_id is not provided', async () => { | ||
const mockResponse = { ok: true, status: 200, statusText: 'OK' } | ||
mockPut.mockResolvedValue(mockResponse) | ||
|
||
const result = await register(mockCommands, mockApplicationId, mockToken) | ||
|
||
expect(mockPut).toHaveBeenCalledWith(_applications_$_commands, [mockApplicationId], expect.any(Array)) | ||
expect(result).toContain('✅ Success') | ||
}) | ||
|
||
it('should handle error responses', async () => { | ||
const mockErrorResponse = { | ||
ok: false, | ||
status: 400, | ||
statusText: 'Bad Request', | ||
url: 'https://discord.com/api/v10/applications/123456789/commands', | ||
text: vi.fn().mockResolvedValue('Invalid command structure'), | ||
} | ||
mockPut.mockResolvedValue(mockErrorResponse) | ||
|
||
const result = await register(mockCommands, mockApplicationId, mockToken) | ||
|
||
expect(result).toContain('⚠️ Error') | ||
expect(result).toContain('Error registering commands') | ||
expect(result).toContain('Invalid command structure') | ||
}) | ||
|
||
it('should handle error when reading response body fails', async () => { | ||
const mockErrorResponse = { | ||
ok: false, | ||
status: 500, | ||
statusText: 'Internal Server Error', | ||
url: 'https://discord.com/api/v10/applications/123456789/commands', | ||
text: vi.fn().mockRejectedValue(new Error('Failed to read body')), | ||
} | ||
mockPut.mockResolvedValue(mockErrorResponse) | ||
|
||
const result = await register(mockCommands, mockApplicationId, mockToken) | ||
|
||
expect(result).toContain('⚠️ Error') | ||
expect(result).toContain('Error registering commands') | ||
expect(result).toContain('Error reading body from request') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { retry429 } from './retry429' | ||
|
||
describe('retry429', () => { | ||
it('returns response immediately on successful request', async () => { | ||
const mockFetch = vi.fn().mockResolvedValue({ status: 200 }) | ||
const result = await retry429(mockFetch, 3) | ||
expect(result.status).toBe(200) | ||
expect(mockFetch).toHaveBeenCalledTimes(1) | ||
}) | ||
|
||
it('retries according to Retry-After header on 429 error', async () => { | ||
const mockFetch = vi | ||
.fn() | ||
.mockResolvedValueOnce({ status: 429, headers: new Headers({ 'Retry-After': '1' }) }) | ||
.mockResolvedValueOnce({ status: 200 }) | ||
|
||
vi.useFakeTimers() | ||
const promise = retry429(mockFetch, 3) | ||
await vi.runAllTimersAsync() | ||
const result = await promise | ||
vi.useRealTimers() | ||
|
||
expect(result.status).toBe(200) | ||
expect(mockFetch).toHaveBeenCalledTimes(2) | ||
}) | ||
|
||
it('applies additional delay', async () => { | ||
const mockFetch = vi | ||
.fn() | ||
.mockResolvedValueOnce({ status: 429, headers: new Headers({ 'Retry-After': '1' }) }) | ||
.mockResolvedValueOnce({ status: 200 }) | ||
|
||
vi.useFakeTimers() | ||
const promise = retry429(mockFetch, 3, 500) | ||
await vi.runAllTimersAsync() | ||
const result = await promise | ||
vi.useRealTimers() | ||
|
||
expect(result.status).toBe(200) | ||
expect(mockFetch).toHaveBeenCalledTimes(2) | ||
//expect(vi.getTimerCount()).toBe(0) // Confirm all timers have been executed | ||
}) | ||
|
||
it('returns last response when max retry count is reached', async () => { | ||
const mockFetch = vi.fn().mockResolvedValue({ status: 429, headers: new Headers({ 'Retry-After': '1' }) }) | ||
|
||
vi.useFakeTimers() | ||
const promise = retry429(mockFetch, 3) | ||
await vi.runAllTimersAsync() | ||
const result = await promise | ||
vi.useRealTimers() | ||
|
||
expect(result.status).toBe(429) | ||
expect(mockFetch).toHaveBeenCalledTimes(4) // Initial + 3 retries | ||
}) | ||
|
||
it('does not retry when Retry-After header is missing', async () => { | ||
const mockFetch = vi.fn().mockResolvedValue({ status: 429, headers: new Headers() }) | ||
const result = await retry429(mockFetch, 3) | ||
expect(result.status).toBe(429) | ||
expect(mockFetch).toHaveBeenCalledTimes(1) | ||
}) | ||
}) |