import { describe, it, expect, vi, beforeEach, afterEach } from 'bun:test'; import TechnikerMeAppender from '../src/appenders/TechnikerMeAppender'; describe('TechnikerMeAppender', () => { let technikerMeAppender: TechnikerMeAppender; let mockFetch: any; let originalFetch: any; beforeEach(() => { technikerMeAppender = new TechnikerMeAppender(); // Mock fetch originalFetch = global.fetch; mockFetch = vi.fn(); global.fetch = mockFetch; }); afterEach(() => { global.fetch = originalFetch; vi.clearAllMocks(); }); describe('log method', () => { it('should queue messages and attempt to post them', () => { mockFetch.mockResolvedValue(new Response()); technikerMeAppender.log('2023-01-01T00:00:00.000Z', 'Info', 'test-category', 'test message'); // Should attempt to fetch expect(mockFetch).toHaveBeenCalledWith( 'https://logserver.techniker.me/api/logs', expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, mode: 'no-cors' }) ); const callArgs = mockFetch.mock.calls[0]; const body = JSON.parse(callArgs[1].body); expect(body).toEqual({ timestamp: '2023-01-01T00:00:00.000Z', level: 'Info', category: 'test-category', message: 'test message', domain: '' // Empty since we're in Node.js environment }); }); it('should include domain in browser environment', () => { // Mock window.location const originalWindow = global.window; global.window = { location: { hostname: 'example.com' } } as any; const appender = new TechnikerMeAppender(); mockFetch.mockResolvedValue(new Response()); appender.log('2023-01-01T00:00:00.000Z', 'Info', 'test-category', 'test message'); const callArgs = mockFetch.mock.calls[0]; const body = JSON.parse(callArgs[1].body); expect(body.domain).toBe('example.com'); // Restore global.window = originalWindow; }); it('should handle missing window gracefully', () => { const originalWindow = global.window; delete (global as any).window; const appender = new TechnikerMeAppender(); mockFetch.mockResolvedValue(new Response()); appender.log('2023-01-01T00:00:00.000Z', 'Info', 'test-category', 'test message'); const callArgs = mockFetch.mock.calls[0]; const body = JSON.parse(callArgs[1].body); expect(body.domain).toBe(''); // Restore global.window = originalWindow; }); }); describe('error handling', () => { it('should handle missing fetch API', () => { const originalFetch = global.fetch; delete (global as any).fetch; const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); technikerMeAppender.log('2023-01-01T00:00:00.000Z', 'Info', 'test-category', 'test message'); expect(consoleSpy).toHaveBeenCalledWith('Fetch API is not available in this environment'); consoleSpy.mockRestore(); global.fetch = originalFetch; }); }); describe('message queuing', () => { it('should attempt to send messages', () => { mockFetch.mockResolvedValue(new Response()); technikerMeAppender.log('2023-01-01T00:00:00.000Z', 'Info', 'cat1', 'msg1'); expect(mockFetch).toHaveBeenCalledTimes(1); expect(mockFetch).toHaveBeenCalledWith( 'https://logserver.techniker.me/api/logs', expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, mode: 'no-cors' }) ); }); // Note: Complex async queuing behavior is tested by the core functionality test above }); });