import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { getWeatherData } from '../mission-planner/src/services/WeatherService' // AZ-499 — mission-planner WeatherService env-var hardening. // // Lives under tests/ (Blackbox-Tests-owned) rather than colocated under // mission-planner/ because mission-planner does not have its own runner; // the suite Vitest config already includes mission-planner/src in coverage // and tsconfig.test.json picks up tests/** for type-check (STC-T1). type FetchMock = ReturnType describe('AZ-499 — mission-planner getWeatherData (env vars + fail-soft)', () => { let fetchMock: FetchMock beforeEach(() => { fetchMock = vi.fn(async () => new Response(JSON.stringify({ wind: { speed: 5, deg: 90 } }), { status: 200 }), ) vi.spyOn(globalThis, 'fetch').mockImplementation(fetchMock) }) afterEach(() => { vi.restoreAllMocks() vi.unstubAllEnvs() }) it('AC-1: env-var resolved API key reaches the outgoing fetch URL', async () => { // Arrange vi.stubEnv('VITE_OWM_API_KEY', 'abc123') vi.stubEnv('VITE_OWM_BASE_URL', '') // Act await getWeatherData(50, 30) // Assert expect(fetchMock).toHaveBeenCalledTimes(1) const url = String(fetchMock.mock.calls[0][0]) expect(url).toContain('appid=abc123') expect(url).toContain('units=metric') }) it('AC-2: env-var resolved base URL prefixes the outgoing fetch URL', async () => { // Arrange vi.stubEnv('VITE_OWM_API_KEY', 'abc123') vi.stubEnv('VITE_OWM_BASE_URL', 'https://example.test/data/2.5') // Act await getWeatherData(50, 30) // Assert const url = String(fetchMock.mock.calls[0][0]) expect(url.startsWith('https://example.test/data/2.5/weather?')).toBe(true) }) it('AC-2: trailing slash on env base URL is stripped', async () => { // Arrange vi.stubEnv('VITE_OWM_API_KEY', 'abc123') vi.stubEnv('VITE_OWM_BASE_URL', 'https://example.test/data/2.5/') // Act await getWeatherData(50, 30) // Assert const url = String(fetchMock.mock.calls[0][0]) expect(url.startsWith('https://example.test/data/2.5/weather?')).toBe(true) }) it('AC-3: returns null and issues no fetch when VITE_OWM_API_KEY is unset', async () => { // Arrange vi.stubEnv('VITE_OWM_API_KEY', '') // Act const result = await getWeatherData(50, 30) // Assert expect(result).toBeNull() expect(fetchMock).not.toHaveBeenCalled() }) it('AC-4: defaults to public OWM base URL when only VITE_OWM_BASE_URL is unset', async () => { // Arrange vi.stubEnv('VITE_OWM_API_KEY', 'abc123') vi.stubEnv('VITE_OWM_BASE_URL', '') // Act await getWeatherData(50, 30) // Assert const url = String(fetchMock.mock.calls[0][0]) expect(url.startsWith('https://api.openweathermap.org/data/2.5/weather?')).toBe(true) }) it('returns the parsed wind shape on a successful response', async () => { // Arrange vi.stubEnv('VITE_OWM_API_KEY', 'abc123') // Act const result = await getWeatherData(50, 30) // Assert expect(result).toEqual({ windSpeed: 5, windAngle: 90 }) }) it('returns null when fetch rejects (network error fail-soft)', async () => { // Arrange vi.stubEnv('VITE_OWM_API_KEY', 'abc123') fetchMock.mockRejectedValueOnce(new Error('boom')) // Act const result = await getWeatherData(50, 30) // Assert expect(result).toBeNull() }) })