mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 13:31:10 +00:00
b016fd8207
AZ-498 — self-hosted satellite tiles + drop classic/satellite toggle: - Single TILE_URL via getTileUrl() (mirrors getOwmBaseUrl/getApiBase pattern from AZ-449/AZ-450); env-var VITE_SATELLITE_TILE_URL with dev default http://localhost:5100/tiles/{z}/{x}/{y}. - FlightMap + MiniMap render one TileLayer with crossOrigin="use-credentials" so Leaflet's <img> tile fetcher attaches the same-origin satellite-provider auth cookie. - ImportMetaEnv + .env.example collapse the prior OSM/Esri pair into one var. The flights.planner.satellite i18n key is removed in lockstep across en.json + ua.json (parity preserved). - E2E harness wired end-to-end: compose passes the new var to azaion-ui; tile-stub serves /tiles/{z}/{x}/{y} with Content-Type=image/jpeg + Cache-Control + ETag matching the contract; infrastructure.e2e.ts AC-2 asserts the new path; dead OSM defenses removed from EXTERNAL_HOSTS route guard. - Fast-profile MSW handlers rewritten for the cookie-auth path shape. - 8 colocated fast tests under src/features/flights/__tests__/. AZ-499 — mission-planner OWM env-var hardening + AZ-482 source-scan gap close: - WeatherService.ts reads VITE_OWM_API_KEY + VITE_OWM_BASE_URL; fail-soft null when key unset (mirrors AZ-448 main-SPA contract). Public signature getWeatherData(lat, lon) preserved. - mission-planner/.env.example + vite-env.d.ts declare both vars. - New owm_key_in_source banned-deps kind scans src/ AND mission-planner/ for the rotated literal; STC-SEC1C row added to scripts/run-tests.sh; check-banned-deps.mjs dispatch extended. - 7 fast tests under tests/mission_planner_weather.test.ts cover AC-1..AC-4 + trailing-slash + happy path + network-error fail-soft. Spec drift (recorded in batch_11_report.md, user-approved Choose B on 2026-05-12): - AZ-498 AC-8 dropped (named tile_split_zoom* files belong to AZ-474 image-annotation surface, not map tiles). - 4 missing files added in-scope (msw tiles handler, tile-stub server, compose env, dead VITE_TILE_BASE_URL replaced). - AZ-499 STC-S6 ID conflict resolved by using STC-SEC1C. Pending USER ACTION (BLOCKING for AZ-499 close): - Revoke OpenWeatherMap key 335799082893fad97fa36118b131f919 at home.openweathermap.org/api_keys; capture evidence on AZ-499. Cross-workspace deploy gate (handled at autodev Step 16, not a Step-10 blocker for AZ-498): - satellite-provider cookie-auth on GET /tiles/{z}/{x}/{y} (separate AZAION ticket on the satellite-provider workspace). Reports: _docs/03_implementation/batch_11_report.md and _docs/03_implementation/reviews/batch_11_review.md (verdict PASS_WITH_WARNINGS — 1 Low, pre-existing trim-trailing-slash duplication across vite roots). Static gates: STC-ARCH-01, STC-ARCH-02, STC-T1, STC-FP22, STC-FP23, STC-SEC1C all PASS post-refactor. +15 fast tests; +1 STC-SEC1C row. Co-authored-by: Cursor <cursoragent@cursor.com>
93 lines
4.2 KiB
TypeScript
93 lines
4.2 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
|
|
// Smoke tests for AZ-456 e2e infrastructure (AC-1, AC-2, AC-5, AC-8).
|
|
// Every other test file under e2e/tests/ is owned by AZ-457..AZ-482; those
|
|
// tasks add the production-shaped assertions per group. This file MUST stay
|
|
// minimal so any flake here is unambiguously an infrastructure regression.
|
|
//
|
|
// AZ-498 update (2026-05-12):
|
|
// - The classic/satellite map toggle was removed; the SPA now consumes
|
|
// only `satellite-provider` tiles via `VITE_SATELLITE_TILE_URL`. The
|
|
// tile-stub serves `/tiles/{z}/{x}/{y}` (no `.png` suffix) per
|
|
// `_docs/02_document/contracts/satellite-provider/tiles.md`.
|
|
// - The dead OSM/Esri entries in EXTERNAL_HOSTS are removed; the SPA can
|
|
// no longer attempt those hosts. The OWM and unpkg defenses stay.
|
|
|
|
const EXTERNAL_HOSTS = [
|
|
/api\.openweathermap\.org/,
|
|
/unpkg\.com/,
|
|
]
|
|
|
|
test.describe('AZ-456 e2e infrastructure', () => {
|
|
test.beforeEach(async ({ context }, testInfo) => {
|
|
// AC-8: external-host firewall. The compose network is already isolated
|
|
// from the internet; this route guard catches code paths that try to
|
|
// reach an external host directly (and lets resilience tests flip it).
|
|
const externalHits: string[] = []
|
|
await context.route(/.*/, async (route) => {
|
|
const url = route.request().url()
|
|
if (EXTERNAL_HOSTS.some((re) => re.test(new URL(url).hostname))) {
|
|
externalHits.push(url)
|
|
await route.abort()
|
|
return
|
|
}
|
|
await route.continue()
|
|
})
|
|
testInfo.attachments.push({ name: 'externalHits', contentType: 'application/json', body: Buffer.from('[]') })
|
|
;(testInfo as unknown as { __externalHits: string[] }).__externalHits = externalHits
|
|
})
|
|
|
|
test.afterEach(async ({}, testInfo) => {
|
|
const externalHits = (testInfo as unknown as { __externalHits?: string[] }).__externalHits ?? []
|
|
expect(externalHits, 'leaked external requests detected').toEqual([])
|
|
})
|
|
|
|
test('AC-1: SPA HTML is served from azaion-ui', async ({ page }) => {
|
|
const response = await page.goto('/')
|
|
expect(response?.ok()).toBeTruthy()
|
|
const html = await page.content()
|
|
expect(html).toMatch(/<html[\s>]/i)
|
|
})
|
|
|
|
test('AC-2: owm-stub returns the canned wind shape', async ({ request }) => {
|
|
const res = await request.get('http://owm-stub:8081/data/2.5/weather?lat=0&lon=0&appid=test')
|
|
expect(res.status()).toBe(200)
|
|
const body = await res.json()
|
|
expect(body.wind).toEqual({ speed: 5.0, deg: 270 })
|
|
})
|
|
|
|
test('AC-2: tile-stub serves /tiles/{z}/{x}/{y} as a JPEG (AZ-498 contract)', async ({ request }) => {
|
|
const res = await request.get('http://tile-stub:8082/tiles/1/0/0')
|
|
expect(res.status()).toBe(200)
|
|
expect(res.headers()['content-type']).toBe('image/jpeg')
|
|
// Cache-Control + ETag are part of the contract — assert they're present
|
|
// so a future tile-stub regression that drops them is caught here.
|
|
expect(res.headers()['cache-control']).toMatch(/max-age=/)
|
|
expect(res.headers()['etag']).toBeTruthy()
|
|
const body = await res.body()
|
|
// The stub serves a tiny PNG byte sequence; assert the PNG signature so
|
|
// we know SOME image came back even though the Content-Type header is
|
|
// jpeg-shaped. Production satellite-provider returns real JPEG bytes.
|
|
expect(body.subarray(0, 8)).toEqual(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))
|
|
})
|
|
|
|
test('AC-5: Playwright runs under the configured browser project', async ({ browserName }) => {
|
|
expect(['chromium', 'firefox']).toContain(browserName)
|
|
})
|
|
|
|
test('AC-8: external host is blocked by the route guard', async ({ page }) => {
|
|
const externalHits = (test.info() as unknown as { __externalHits?: string[] }).__externalHits ?? []
|
|
const beforeCount = externalHits.length
|
|
await page.goto('/')
|
|
await page.evaluate(() =>
|
|
fetch('https://api.openweathermap.org/data/2.5/weather?appid=leak').catch(() => null),
|
|
)
|
|
// The guard converts the request into an abort, so the leak is recorded
|
|
// but no real request escapes. The afterEach assertion will fire next.
|
|
expect(externalHits.length).toBeGreaterThan(beforeCount)
|
|
// Reset so afterEach doesn't fail this specific test (the guard already
|
|
// proved the assertion).
|
|
externalHits.length = 0
|
|
})
|
|
})
|