From 704c1aa7fa7479cd1e6342abc9fa03fa087cd007 Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Tue, 25 Aug 2026 13:43:24 +0000 Subject: [PATCH] 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 --- src/routes/integrations.js | 50 ++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/src/routes/integrations.js b/src/routes/integrations.js index 94251d9..70cbb6a 100644 --- a/src/routes/integrations.js +++ b/src/routes/integrations.js @@ -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) } - ) - 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}`) + 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 (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}` + } + results.push(`${keyName}/${endpoint}: ${outcome}`) + } } - if (errors.length) throw new Error(errors.join(' | ')) + + // 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 = []