feat: differentiate weather fetch errors by cause
This commit is contained in:
@@ -69,4 +69,51 @@ describe('fetchOpenWeatherCurrent', () => {
|
||||
|
||||
expect(trackPlausibleEvent).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('throws UNAUTHORIZED when status is 401', async () => {
|
||||
apiFetch.mockResolvedValue({
|
||||
ok: false,
|
||||
status: 401,
|
||||
json: async () => ({ error: 'Unauthorized' })
|
||||
})
|
||||
|
||||
const { fetchOpenWeatherCurrent, WeatherApiError } = await import('./weather.js')
|
||||
const err = await fetchOpenWeatherCurrent({ lat: '54', lon: '10' }).catch((e) => e)
|
||||
expect(err).toBeInstanceOf(WeatherApiError)
|
||||
expect((err as any).code).toBe('UNAUTHORIZED')
|
||||
})
|
||||
|
||||
it('throws NOT_FOUND when status is 404', async () => {
|
||||
apiFetch.mockResolvedValue({
|
||||
ok: false,
|
||||
status: 404,
|
||||
json: async () => ({ error: 'Not Found' })
|
||||
})
|
||||
|
||||
const { fetchOpenWeatherCurrent, WeatherApiError } = await import('./weather.js')
|
||||
const err = await fetchOpenWeatherCurrent({ lat: '54', lon: '10' }).catch((e) => e)
|
||||
expect(err).toBeInstanceOf(WeatherApiError)
|
||||
expect((err as any).code).toBe('NOT_FOUND')
|
||||
})
|
||||
|
||||
it('throws BAD_REQUEST when status is 400', async () => {
|
||||
apiFetch.mockResolvedValue({
|
||||
ok: false,
|
||||
status: 400,
|
||||
json: async () => ({ error: 'Bad Request' })
|
||||
})
|
||||
|
||||
const { fetchOpenWeatherCurrent, WeatherApiError } = await import('./weather.js')
|
||||
const err = await fetchOpenWeatherCurrent({ lat: '54', lon: '10' }).catch((e) => e)
|
||||
expect(err).toBeInstanceOf(WeatherApiError)
|
||||
expect((err as any).code).toBe('BAD_REQUEST')
|
||||
})
|
||||
|
||||
it('throws BAD_REQUEST when coordinates or query are missing', async () => {
|
||||
const { fetchOpenWeatherCurrent, WeatherApiError } = await import('./weather.js')
|
||||
const err = await fetchOpenWeatherCurrent({}).catch((e) => e)
|
||||
expect(err).toBeInstanceOf(WeatherApiError)
|
||||
expect((err as any).code).toBe('BAD_REQUEST')
|
||||
expect(apiFetch).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,9 +7,12 @@ import {
|
||||
} from './analytics.js'
|
||||
|
||||
export class WeatherApiError extends Error {
|
||||
code: 'NO_KEY' | 'OFFLINE' | 'REQUEST_FAILED'
|
||||
code: 'NO_KEY' | 'OFFLINE' | 'REQUEST_FAILED' | 'UNAUTHORIZED' | 'NOT_FOUND' | 'BAD_REQUEST'
|
||||
|
||||
constructor(message: string, code: 'NO_KEY' | 'OFFLINE' | 'REQUEST_FAILED' = 'REQUEST_FAILED') {
|
||||
constructor(
|
||||
message: string,
|
||||
code: 'NO_KEY' | 'OFFLINE' | 'REQUEST_FAILED' | 'UNAUTHORIZED' | 'NOT_FOUND' | 'BAD_REQUEST' = 'REQUEST_FAILED'
|
||||
) {
|
||||
super(message)
|
||||
this.name = 'WeatherApiError'
|
||||
this.code = code
|
||||
@@ -38,7 +41,7 @@ export async function fetchOpenWeatherCurrent(
|
||||
} else if (params.q?.trim()) {
|
||||
searchParams.set('q', params.q.trim())
|
||||
} else {
|
||||
throw new WeatherApiError('lat/lon or location query required')
|
||||
throw new WeatherApiError('lat/lon or location query required', 'BAD_REQUEST')
|
||||
}
|
||||
|
||||
const userKey = getOwmApiKeyForActiveUser().trim()
|
||||
@@ -65,6 +68,15 @@ export async function fetchOpenWeatherCurrent(
|
||||
if (res.status === 503) {
|
||||
throw new WeatherApiError('No OpenWeatherMap API key configured', 'NO_KEY')
|
||||
}
|
||||
if (res.status === 401) {
|
||||
throw new WeatherApiError('Invalid OpenWeatherMap API key', 'UNAUTHORIZED')
|
||||
}
|
||||
if (res.status === 404) {
|
||||
throw new WeatherApiError('Location or coordinates not found', 'NOT_FOUND')
|
||||
}
|
||||
if (res.status === 400) {
|
||||
throw new WeatherApiError('Invalid or missing location parameters', 'BAD_REQUEST')
|
||||
}
|
||||
|
||||
const data = await res.json()
|
||||
if (!res.ok) {
|
||||
|
||||
Reference in New Issue
Block a user