| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- import { ipcMain } from 'electron'
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
- import { http_api_port } from '../../src/common/constants'
- import events from '../../src/common/events'
- import { setList } from '../../src/main/actions'
- import { clearData } from '../_base'
- const { closeMock, serveMock } = vi.hoisted(() => {
- const close = vi.fn()
- const serve = vi.fn((options: object, callback?: () => void) => {
- callback?.()
- return {
- close,
- options,
- }
- })
- return {
- closeMock: close,
- serveMock: serve,
- }
- })
- vi.mock('@hono/node-server', () => ({
- serve: serveMock,
- }))
- import { app, start, stop } from '../../src/main/http'
- describe('http api test', () => {
- beforeEach(async () => {
- await clearData()
- })
- afterEach(() => {
- vi.restoreAllMocks()
- serveMock.mockClear()
- closeMock.mockClear()
- })
- it('should log request metadata for incoming requests', async () => {
- const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
- const response = await app.request('/api/list', {
- headers: {
- 'user-agent': 'vitest',
- },
- })
- expect(response.status).toBe(200)
- expect(logSpy).toHaveBeenCalledWith(
- expect.stringMatching(/^> ".+"$/),
- 'GET',
- '/api/list',
- '"vitest"',
- )
- })
- it('should respond on root endpoint', async () => {
- const response = await app.request('/')
- expect(response.status).toBe(200)
- expect(await response.text()).toBe('Hello SwitchHosts!')
- })
- it('should respond on remote test endpoint', async () => {
- const response = await app.request('/remote-test')
- expect(response.status).toBe(200)
- expect(await response.text()).toMatch(/^# remote-test\n# .+/)
- })
- it('should flatten list data for api list endpoint', async () => {
- await setList([
- { id: 'top-1', title: 'Top 1' },
- {
- id: 'folder-1',
- type: 'folder',
- children: [
- { id: 'child-1', title: 'Child 1' },
- { id: 'child-2', title: 'Child 2' },
- ],
- },
- ])
- const response = await app.request('/api/list')
- const body = await response.json()
- expect(response.status).toBe(200)
- expect(body.success).toBe(true)
- expect(body.data.map((item: { id: string }) => item.id)).toEqual([
- 'top-1',
- 'folder-1',
- 'child-1',
- 'child-2',
- ])
- })
- it('should reject toggle requests without id', async () => {
- const response = await app.request('/api/toggle')
- expect(response.status).toBe(200)
- expect(await response.text()).toBe('bad id.')
- })
- it('should return not found for unknown toggle id', async () => {
- const response = await app.request('/api/toggle?id=missing')
- expect(response.status).toBe(200)
- expect(await response.text()).toBe('not found.')
- })
- it('should broadcast toggle event for existing item', async () => {
- const emitSpy = vi.spyOn(ipcMain, 'emit')
- await setList([
- { id: 'item-1', on: false, title: 'Item 1' },
- ])
- const response = await app.request('/api/toggle?id=item-1')
- expect(response.status).toBe(200)
- expect(await response.text()).toBe('ok')
- expect(emitSpy).toHaveBeenCalledWith('x_broadcast', null, {
- event: events.toggle_item,
- args: [ 'item-1', true ],
- })
- })
- it('should listen on localhost when local-only mode is enabled', () => {
- expect(start(true)).toBe(true)
- expect(serveMock.mock.calls[0]?.[0]).toEqual({
- fetch: app.fetch,
- port: http_api_port,
- hostname: '127.0.0.1',
- })
- expect(typeof serveMock.mock.calls[0]?.[1]).toBe('function')
- stop()
- expect(closeMock).toHaveBeenCalledOnce()
- })
- it('should listen on all interfaces when local-only mode is disabled', () => {
- expect(start(false)).toBe(true)
- expect(serveMock.mock.calls[0]?.[0]).toEqual({
- fetch: app.fetch,
- port: http_api_port,
- hostname: '0.0.0.0',
- })
- expect(typeof serveMock.mock.calls[0]?.[1]).toBe('function')
- stop()
- expect(closeMock).toHaveBeenCalledOnce()
- })
- it('should return false when serve throws', () => {
- const error = new Error('listen failed')
- serveMock.mockImplementationOnce(() => {
- throw error
- })
- const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
- expect(start(true)).toBe(false)
- expect(errorSpy).toHaveBeenCalledWith(error)
- })
- it('should swallow close errors when stopping server', () => {
- const error = new Error('close failed')
- const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
- const failingClose = vi.fn(() => {
- throw error
- })
- serveMock.mockImplementationOnce((options: object, callback?: () => void) => {
- callback?.()
- return {
- close: failingClose,
- options,
- }
- })
- expect(start(true)).toBe(true)
- stop()
- expect(errorSpy).toHaveBeenCalledWith(error)
- })
- })
|