met_office test: full Spot/Forecast x daily/hourly/three-hourly matrix

First guess (Forecast key -> hourly specifically) was tried and confirmed
wrong live. Rather than guess again, the test route now checks every
configured key against every endpoint and reports the full 2x3 matrix, so
the actual working combination is visible on screen instead of assumed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-08-25 13:43:24 +00:00
parent 210649d7ad
commit 704c1aa7fa

View file

@ -171,26 +171,40 @@ export async function integrationRoutes(app) {
const { rows: [loc] } = await pool.query(`SELECT config FROM integrations WHERE slug = 'hotel_location'`)
const lat = loc?.config?.latitude || '51.5072'
const lon = loc?.config?.longitude || '-0.1276'
// Two separate subscriptions, two separate keys — test each against the
// endpoint weather actually uses it for (Spot -> daily, Forecast ->
// hourly). forecast_api_key is optional, only tested if set.
const errors = []
const dailyRes = await fetch(
`https://data.hub.api.metoffice.gov.uk/sitespecific/v0/point/daily?latitude=${lat}&longitude=${lon}`,
{ headers: { apikey: creds.api_key }, signal: AbortSignal.timeout(8000) }
)
if (dailyRes.status === 401 || dailyRes.status === 403) errors.push('Spot key: invalid or not subscribed to the daily endpoint')
else if (!dailyRes.ok) errors.push(`Spot key: daily endpoint returned ${dailyRes.status}`)
// Which of Spot vs Forecast actually covers which timestep isn't
// documented anywhere we could find, and a first guess (Forecast ->
// hourly) turned out wrong live — so this tests every configured key
// against every endpoint and reports the full matrix, rather than
// assuming, so the actual working combination is on screen instead of
// guessed at again.
const keys = [['Spot', creds.api_key]]
if (creds.forecast_api_key) keys.push(['Forecast', creds.forecast_api_key])
const endpoints = ['daily', 'hourly', 'three-hourly']
if (creds.forecast_api_key) {
const hourlyRes = await fetch(
`https://data.hub.api.metoffice.gov.uk/sitespecific/v0/point/hourly?latitude=${lat}&longitude=${lon}`,
{ headers: { apikey: creds.forecast_api_key }, signal: AbortSignal.timeout(8000) }
const results = []
for (const [keyName, key] of keys) {
for (const endpoint of endpoints) {
let outcome
try {
const res = await fetch(
`https://data.hub.api.metoffice.gov.uk/sitespecific/v0/point/${endpoint}?latitude=${lat}&longitude=${lon}`,
{ headers: { apikey: key }, signal: AbortSignal.timeout(8000) }
)
if (hourlyRes.status === 401 || hourlyRes.status === 403) errors.push('Forecast key: invalid or not subscribed to the hourly endpoint')
else if (!hourlyRes.ok) errors.push(`Forecast key: hourly endpoint returned ${hourlyRes.status}`)
if (res.status === 401 || res.status === 403) outcome = 'not subscribed'
else if (!res.ok) outcome = `HTTP ${res.status}`
else outcome = 'OK'
} catch (e) {
outcome = `error: ${e.message}`
}
if (errors.length) throw new Error(errors.join(' | '))
results.push(`${keyName}/${endpoint}: ${outcome}`)
}
}
// Frontend renders `message` when ok, `error` when not — set both to the
// same diagnostic text so the full matrix shows up either way.
const summary = results.join(' | ')
const ok = results.some(r => r.endsWith('OK'))
return { ok, message: summary, error: summary }
}
else if (req.params.slug === 'sambapos') {
const errors = []