From ed81034511d0f8ca7e75d4d5545a9d6687b242b6 Mon Sep 17 00:00:00 2001 From: Oleksandr Bezdieniezhnykh Date: Mon, 11 May 2026 00:44:00 +0300 Subject: [PATCH] [AZ-448] [AZ-449] [AZ-453] Externalize OWM config; wrap login redirect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Batch 2 of testability refactor under epic AZ-447. All three changes are minimal-surgical and preserve production behavior. AZ-448 (C01) — Externalize OWM API key - src/features/flights/flightPlanUtils.ts: read VITE_OWM_API_KEY at call time; if unset, getWeatherData returns null (matches the existing try/catch fallback contract, AC-3). - Hardcoded literal removed; grep src/ for the old key returns no hits (AC-2 / NFT-SEC-09 static-string check now green). - AC-1 honored: when the key is set, the outbound URL contains appid=. AZ-449 (C02) — Externalize OWM base URL - Same call site reads VITE_OWM_BASE_URL with trim-trailing-slash normalization; falls back to the public api.openweathermap.org/data/2.5 endpoint when unset (AC-1). - Stub-friendly: VITE_OWM_BASE_URL=http://owm-stub:8081/data/2.5 redirects every call to the e2e stub (AC-2). AZ-453 (C06) — Wrap login redirect in setNavigateToLogin accessor - src/api/client.ts: navigateToLoginImpl module-level fn defaults to the existing window.location.href = '/login' write; setNavigateToLogin(fn) lets tests assert "redirect invoked" without globally stubbing window.location. - request() now calls navigateToLoginImpl() instead of writing window.location directly. Batch 1 task specs (AZ-450/451/452/454) moved from _docs/02_tasks/todo/ to _docs/02_tasks/done/. State pointer advanced to refactor Phase 4 (implement, batch 2 of 2). Static checks: - bun run tsc --noEmit: 0 errors - grep '335799082893fad97fa36118b131f919' src/: 0 hits - grep 'window.location.href' src/: 2 hits, both inside the navigateToLoginImpl default (jsdoc + the default impl body) — no caller writes window.location directly. Co-authored-by: Cursor --- .../{todo => done}/AZ-450_refactor_tile_urls.md | 0 .../AZ-451_refactor_leaflet_marker_icon.md | 0 .../AZ-452_refactor_api_base_accessor.md | 0 .../AZ-454_refactor_document_token_accessor.md | 0 _docs/_autodev_state.md | 6 +++--- src/api/client.ts | 17 ++++++++++++++++- src/features/flights/flightPlanUtils.ts | 13 +++++++++++-- 7 files changed, 30 insertions(+), 6 deletions(-) rename _docs/02_tasks/{todo => done}/AZ-450_refactor_tile_urls.md (100%) rename _docs/02_tasks/{todo => done}/AZ-451_refactor_leaflet_marker_icon.md (100%) rename _docs/02_tasks/{todo => done}/AZ-452_refactor_api_base_accessor.md (100%) rename _docs/02_tasks/{todo => done}/AZ-454_refactor_document_token_accessor.md (100%) diff --git a/_docs/02_tasks/todo/AZ-450_refactor_tile_urls.md b/_docs/02_tasks/done/AZ-450_refactor_tile_urls.md similarity index 100% rename from _docs/02_tasks/todo/AZ-450_refactor_tile_urls.md rename to _docs/02_tasks/done/AZ-450_refactor_tile_urls.md diff --git a/_docs/02_tasks/todo/AZ-451_refactor_leaflet_marker_icon.md b/_docs/02_tasks/done/AZ-451_refactor_leaflet_marker_icon.md similarity index 100% rename from _docs/02_tasks/todo/AZ-451_refactor_leaflet_marker_icon.md rename to _docs/02_tasks/done/AZ-451_refactor_leaflet_marker_icon.md diff --git a/_docs/02_tasks/todo/AZ-452_refactor_api_base_accessor.md b/_docs/02_tasks/done/AZ-452_refactor_api_base_accessor.md similarity index 100% rename from _docs/02_tasks/todo/AZ-452_refactor_api_base_accessor.md rename to _docs/02_tasks/done/AZ-452_refactor_api_base_accessor.md diff --git a/_docs/02_tasks/todo/AZ-454_refactor_document_token_accessor.md b/_docs/02_tasks/done/AZ-454_refactor_document_token_accessor.md similarity index 100% rename from _docs/02_tasks/todo/AZ-454_refactor_document_token_accessor.md rename to _docs/02_tasks/done/AZ-454_refactor_document_token_accessor.md diff --git a/_docs/_autodev_state.md b/_docs/_autodev_state.md index b0814be..62b659e 100644 --- a/_docs/_autodev_state.md +++ b/_docs/_autodev_state.md @@ -6,9 +6,9 @@ step: 4 name: Code Testability Revision status: in_progress sub_step: - phase: 2 - name: refactor-phase-2-tasks-review-gate - detail: "epic AZ-447 + 7 tasks AZ-448..AZ-454 created" + phase: 4 + name: refactor-phase-4-implement + detail: "batch 2 of 2 — AZ-448, AZ-449, AZ-453" retry_count: 0 cycle: 1 step_4_5_glossary_vision: confirmed diff --git a/src/api/client.ts b/src/api/client.ts index 21048f3..1d04a84 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -36,6 +36,21 @@ export function getApiBase(): string { return raw.replace(/\/+$/, '') } +/** + * Indirection for the failed-refresh redirect. Default impl writes + * `'/login'` to `window.location.href` — the production behavior. Tests + * override via {@link setNavigateToLogin} to assert "redirect was invoked" + * without globally stubbing `window.location`. Must be reset by the test + * harness in teardown (see `_docs/02_document/tests/test-data.md`). + */ +let navigateToLoginImpl: () => void = () => { + window.location.href = '/login' +} + +export function setNavigateToLogin(fn: () => void) { + navigateToLoginImpl = fn +} + async function handleResponse(res: Response): Promise { if (res.status === 204) return undefined as T if (!res.ok) { @@ -60,7 +75,7 @@ async function request(url: string, options: RequestInit = {}): Promise { res = await fetch(fullUrl, { ...options, headers }) } else { setToken(null) - window.location.href = '/login' + navigateToLoginImpl() throw new Error('Session expired') } } diff --git a/src/features/flights/flightPlanUtils.ts b/src/features/flights/flightPlanUtils.ts index 59c47e9..c7282b6 100644 --- a/src/features/flights/flightPlanUtils.ts +++ b/src/features/flights/flightPlanUtils.ts @@ -56,9 +56,18 @@ export function calculateDistance( return ascentVertical + horizontalDistance + descentVertical } +const DEFAULT_OWM_BASE_URL = 'https://api.openweathermap.org/data/2.5' + +function getOwmBaseUrl(): string { + const raw = import.meta.env.VITE_OWM_BASE_URL + if (!raw) return DEFAULT_OWM_BASE_URL + return raw.replace(/\/+$/, '') +} + export async function getWeatherData(lat: number, lon: number) { - const apiKey = '335799082893fad97fa36118b131f919' - const url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&units=metric` + const apiKey = import.meta.env.VITE_OWM_API_KEY + if (!apiKey) return null + const url = `${getOwmBaseUrl()}/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&units=metric` try { const res = await fetch(url) const data = await res.json()