Skip to content

Commit

Permalink
test: add helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
luisfun committed Jan 25, 2025
1 parent fd8467c commit 3a87af0
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 0 deletions.
82 changes: 82 additions & 0 deletions src/helpers/create-factory.test.ts
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')
})
})
92 changes: 92 additions & 0 deletions src/helpers/register.test.ts
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')
})
})
63 changes: 63 additions & 0 deletions src/helpers/retry429.test.ts
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)
})
})

0 comments on commit 3a87af0

Please sign in to comment.