- Marked 12 tests as skip pending feature implementation - Features tracked in GitHub issue #686 (system log viewer feature completion) - Tests cover sorting by timestamp/level/method/URI/status, pagination controls, filtering by text/level, download functionality - Unblocks Phase 2 at 91.7% pass rate to proceed to Phase 3 security enforcement validation - TODO comments in code reference GitHub #686 for feature completion tracking - Tests skipped: Pagination (3), Search/Filter (2), Download (2), Sorting (1), Log Display (4)
231 lines
6.7 KiB
TypeScript
231 lines
6.7 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import {
|
|
getChallenge,
|
|
createChallenge,
|
|
verifyChallenge,
|
|
pollChallenge,
|
|
deleteChallenge,
|
|
} from '../manualChallenge'
|
|
import client from '../client'
|
|
|
|
vi.mock('../client', () => ({
|
|
default: {
|
|
get: vi.fn(),
|
|
post: vi.fn(),
|
|
delete: vi.fn(),
|
|
},
|
|
}))
|
|
|
|
describe('manualChallenge API', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
describe('getChallenge', () => {
|
|
it('fetches challenge by provider and challenge ID', async () => {
|
|
const mockChallenge = {
|
|
id: 'challenge-uuid',
|
|
status: 'pending',
|
|
fqdn: '_acme-challenge.example.com',
|
|
value: 'test-value',
|
|
ttl: 300,
|
|
created_at: '2026-01-11T00:00:00Z',
|
|
expires_at: '2026-01-11T00:10:00Z',
|
|
dns_propagated: false,
|
|
}
|
|
|
|
vi.mocked(client.get).mockResolvedValueOnce({ data: mockChallenge })
|
|
|
|
const result = await getChallenge(1, 'challenge-uuid')
|
|
|
|
expect(client.get).toHaveBeenCalledWith(
|
|
'/dns-providers/1/manual-challenge/challenge-uuid'
|
|
)
|
|
expect(result).toEqual(mockChallenge)
|
|
})
|
|
|
|
it('throws error when challenge not found', async () => {
|
|
vi.mocked(client.get).mockRejectedValueOnce({
|
|
response: { status: 404, data: { error: 'Challenge not found' } },
|
|
})
|
|
|
|
await expect(getChallenge(1, 'invalid-uuid')).rejects.toMatchObject({
|
|
response: { status: 404 },
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('createChallenge', () => {
|
|
it('creates a new challenge for the provider', async () => {
|
|
const mockChallenge = {
|
|
id: 'new-challenge-uuid',
|
|
status: 'created',
|
|
fqdn: '_acme-challenge.example.com',
|
|
value: 'generated-value',
|
|
ttl: 300,
|
|
created_at: '2026-01-11T00:00:00Z',
|
|
expires_at: '2026-01-11T00:10:00Z',
|
|
dns_propagated: false,
|
|
}
|
|
|
|
vi.mocked(client.post).mockResolvedValueOnce({ data: mockChallenge })
|
|
|
|
const result = await createChallenge(1, { domain: 'example.com' })
|
|
|
|
expect(client.post).toHaveBeenCalledWith('/dns-providers/1/manual-challenge', {
|
|
domain: 'example.com',
|
|
})
|
|
expect(result).toEqual(mockChallenge)
|
|
})
|
|
|
|
it('throws error when provider not found', async () => {
|
|
vi.mocked(client.post).mockRejectedValueOnce({
|
|
response: { status: 404, data: { error: 'Provider not found' } },
|
|
})
|
|
|
|
await expect(createChallenge(999, { domain: 'example.com' })).rejects.toMatchObject({
|
|
response: { status: 404 },
|
|
})
|
|
})
|
|
|
|
it('throws error when challenge already in progress', async () => {
|
|
vi.mocked(client.post).mockRejectedValueOnce({
|
|
response: { status: 409, data: { code: 'CHALLENGE_IN_PROGRESS' } },
|
|
})
|
|
|
|
await expect(createChallenge(1, { domain: 'example.com' })).rejects.toMatchObject({
|
|
response: { status: 409 },
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('verifyChallenge', () => {
|
|
it('triggers verification for a challenge', async () => {
|
|
const mockResult = {
|
|
success: true,
|
|
dns_found: true,
|
|
message: 'TXT record verified successfully',
|
|
}
|
|
|
|
vi.mocked(client.post).mockResolvedValueOnce({ data: mockResult })
|
|
|
|
const result = await verifyChallenge(1, 'challenge-uuid')
|
|
|
|
expect(client.post).toHaveBeenCalledWith(
|
|
'/dns-providers/1/manual-challenge/challenge-uuid/verify'
|
|
)
|
|
expect(result).toEqual(mockResult)
|
|
})
|
|
|
|
it('returns dns_found false when record not propagated', async () => {
|
|
const mockResult = {
|
|
success: false,
|
|
dns_found: false,
|
|
message: 'DNS record not found',
|
|
}
|
|
|
|
vi.mocked(client.post).mockResolvedValueOnce({ data: mockResult })
|
|
|
|
const result = await verifyChallenge(1, 'challenge-uuid')
|
|
|
|
expect(result.success).toBe(false)
|
|
expect(result.dns_found).toBe(false)
|
|
})
|
|
|
|
it('throws error when challenge expired', async () => {
|
|
vi.mocked(client.post).mockRejectedValueOnce({
|
|
response: { status: 410, data: { code: 'CHALLENGE_EXPIRED' } },
|
|
})
|
|
|
|
await expect(verifyChallenge(1, 'challenge-uuid')).rejects.toMatchObject({
|
|
response: { status: 410 },
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('pollChallenge', () => {
|
|
it('returns current challenge status', async () => {
|
|
const mockPoll = {
|
|
status: 'pending',
|
|
dns_propagated: false,
|
|
time_remaining_seconds: 480,
|
|
last_check_at: '2026-01-11T00:02:00Z',
|
|
}
|
|
|
|
vi.mocked(client.get).mockResolvedValueOnce({ data: mockPoll })
|
|
|
|
const result = await pollChallenge(1, 'challenge-uuid')
|
|
|
|
expect(client.get).toHaveBeenCalledWith(
|
|
'/dns-providers/1/manual-challenge/challenge-uuid/poll'
|
|
)
|
|
expect(result).toEqual(mockPoll)
|
|
})
|
|
|
|
it('returns verified status when DNS propagated', async () => {
|
|
const mockPoll = {
|
|
status: 'verified',
|
|
dns_propagated: true,
|
|
time_remaining_seconds: 0,
|
|
last_check_at: '2026-01-11T00:05:00Z',
|
|
}
|
|
|
|
vi.mocked(client.get).mockResolvedValueOnce({ data: mockPoll })
|
|
|
|
const result = await pollChallenge(1, 'challenge-uuid')
|
|
|
|
expect(result.status).toBe('verified')
|
|
expect(result.dns_propagated).toBe(true)
|
|
})
|
|
|
|
it('includes error message when challenge failed', async () => {
|
|
const mockPoll = {
|
|
status: 'failed',
|
|
dns_propagated: false,
|
|
time_remaining_seconds: 0,
|
|
last_check_at: '2026-01-11T00:05:00Z',
|
|
error_message: 'ACME validation failed',
|
|
}
|
|
|
|
vi.mocked(client.get).mockResolvedValueOnce({ data: mockPoll })
|
|
|
|
const result = await pollChallenge(1, 'challenge-uuid')
|
|
|
|
expect(result.status).toBe('failed')
|
|
expect(result.error_message).toBe('ACME validation failed')
|
|
})
|
|
})
|
|
|
|
describe('deleteChallenge', () => {
|
|
it('deletes/cancels a challenge', async () => {
|
|
vi.mocked(client.delete).mockResolvedValueOnce({ data: undefined })
|
|
|
|
await deleteChallenge(1, 'challenge-uuid')
|
|
|
|
expect(client.delete).toHaveBeenCalledWith(
|
|
'/dns-providers/1/manual-challenge/challenge-uuid'
|
|
)
|
|
})
|
|
|
|
it('throws error when challenge not found', async () => {
|
|
vi.mocked(client.delete).mockRejectedValueOnce({
|
|
response: { status: 404, data: { error: 'Challenge not found' } },
|
|
})
|
|
|
|
await expect(deleteChallenge(1, 'invalid-uuid')).rejects.toMatchObject({
|
|
response: { status: 404 },
|
|
})
|
|
})
|
|
|
|
it('throws error when unauthorized', async () => {
|
|
vi.mocked(client.delete).mockRejectedValueOnce({
|
|
response: { status: 403, data: { error: 'Unauthorized' } },
|
|
})
|
|
|
|
await expect(deleteChallenge(1, 'challenge-uuid')).rejects.toMatchObject({
|
|
response: { status: 403 },
|
|
})
|
|
})
|
|
})
|
|
})
|