From 27506bbaa9cb2a2d818c987320e0391f873cd14c Mon Sep 17 00:00:00 2001 From: adust09 Date: Mon, 16 Mar 2026 14:43:01 +0900 Subject: [PATCH 1/2] test: add JSDF bases tests (RED phase) - Add gsdf/msdf/asdf to known_branches in test_branch_values_are_known - Add test_includes_jsdf_bases for Yonaguni, Naha, Kure - Add test_colocated_bases_have_separate_entries for Misawa - Add buildMilitaryBasesGeoJSON tests with ASDF branch validation Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/tests/test_military_bases.py | 18 +++++++++++- .../src/__tests__/map/geoJSONBuilders.test.ts | 29 +++++++++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/backend/tests/test_military_bases.py b/backend/tests/test_military_bases.py index eecd967..d8fb1a1 100644 --- a/backend/tests/test_military_bases.py +++ b/backend/tests/test_military_bases.py @@ -35,7 +35,7 @@ class TestMilitaryBasesData: assert -180 <= entry["lng"] <= 180, f"{entry['name']} has invalid lng" def test_branch_values_are_known(self): - known_branches = {"air_force", "navy", "marines", "army"} + known_branches = {"air_force", "navy", "marines", "army", "gsdf", "msdf", "asdf"} raw = json.loads(BASES_PATH.read_text(encoding="utf-8")) for entry in raw: assert entry["branch"] in known_branches, f"{entry['name']} has unknown branch: {entry['branch']}" @@ -60,3 +60,19 @@ class TestFetchMilitaryBases: assert "Kadena Air Base" in names assert "Fleet Activities Yokosuka" in names assert "Andersen Air Force Base" in names + + def test_includes_jsdf_bases(self): + from services.fetchers.infrastructure import fetch_military_bases + fetch_military_bases() + with _data_lock: + names = {b["name"] for b in latest_data["military_bases"]} + assert "Yonaguni Garrison" in names + assert "Naha Air Base" in names + assert "Kure Naval Base" in names + + def test_colocated_bases_have_separate_entries(self): + from services.fetchers.infrastructure import fetch_military_bases + fetch_military_bases() + with _data_lock: + misawa_entries = [b for b in latest_data["military_bases"] if "Misawa" in b["name"]] + assert len(misawa_entries) == 2, f"Expected 2 Misawa entries, got {len(misawa_entries)}" diff --git a/frontend/src/__tests__/map/geoJSONBuilders.test.ts b/frontend/src/__tests__/map/geoJSONBuilders.test.ts index dfd0549..cbe1628 100644 --- a/frontend/src/__tests__/map/geoJSONBuilders.test.ts +++ b/frontend/src/__tests__/map/geoJSONBuilders.test.ts @@ -2,9 +2,34 @@ import { describe, it, expect } from 'vitest'; import { buildEarthquakesGeoJSON, buildJammingGeoJSON, buildCctvGeoJSON, buildKiwisdrGeoJSON, buildFirmsGeoJSON, buildInternetOutagesGeoJSON, buildDataCentersGeoJSON, - buildGdeltGeoJSON, buildLiveuaGeoJSON, buildFrontlineGeoJSON + buildGdeltGeoJSON, buildLiveuaGeoJSON, buildFrontlineGeoJSON, buildMilitaryBasesGeoJSON } from '@/components/map/geoJSONBuilders'; -import type { Earthquake, GPSJammingZone, FireHotspot, InternetOutage, DataCenter, GDELTIncident, LiveUAmapIncident, CCTVCamera, KiwiSDR } from '@/types/dashboard'; +import type { Earthquake, GPSJammingZone, FireHotspot, InternetOutage, DataCenter, GDELTIncident, LiveUAmapIncident, CCTVCamera, KiwiSDR, MilitaryBase } from '@/types/dashboard'; + +// ─── Military Bases ──────────────────────────────────────────────────────── + +describe('buildMilitaryBasesGeoJSON', () => { + it('returns null for empty/undefined input', () => { + expect(buildMilitaryBasesGeoJSON(undefined)).toBeNull(); + expect(buildMilitaryBasesGeoJSON([])).toBeNull(); + }); + + it('builds valid Feature for ASDF branch base', () => { + const bases: MilitaryBase[] = [ + { name: 'Naha Air Base', country: 'Japan', operator: 'ASDF 9th Air Wing', branch: 'asdf', lat: 26.196, lng: 127.646 }, + ]; + const result = buildMilitaryBasesGeoJSON(bases); + expect(result).not.toBeNull(); + expect(result!.type).toBe('FeatureCollection'); + expect(result!.features).toHaveLength(1); + + const f = result!.features[0]; + expect(f.geometry).toEqual({ type: 'Point', coordinates: [127.646, 26.196] }); + expect(f.properties?.type).toBe('military_base'); + expect(f.properties?.branch).toBe('asdf'); + expect(f.properties?.name).toBe('Naha Air Base'); + }); +}); // ─── Earthquakes ──────────────────────────────────────────────────────────── From 457f00ca427e862c474cde4efd51f89fa879e335 Mon Sep 17 00:00:00 2001 From: adust09 Date: Mon, 16 Mar 2026 14:44:32 +0900 Subject: [PATCH 2/2] feat: add 18 JSDF bases to military bases layer Add ASDF (8), MSDF (6), and GSDF (4) bases to military_bases.json. Colocated bases (Misawa, Yokosuka, Sasebo) have offset coordinates to avoid overlap with existing US entries. Add branchLabel entries for GSDF/MSDF/ASDF in MaplibreViewer popup. Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/data/military_bases.json | 144 +++++++++++++++++++++ frontend/src/components/MaplibreViewer.tsx | 1 + 2 files changed, 145 insertions(+) diff --git a/backend/data/military_bases.json b/backend/data/military_bases.json index d4ea047..4398bda 100644 --- a/backend/data/military_bases.json +++ b/backend/data/military_bases.json @@ -142,5 +142,149 @@ "branch": "navy", "lat": -7.316, "lng": 72.411 + }, + { + "name": "Naha Air Base", + "country": "Japan", + "operator": "ASDF 9th Air Wing", + "branch": "asdf", + "lat": 26.196, + "lng": 127.646 + }, + { + "name": "Misawa Air Base (ASDF)", + "country": "Japan", + "operator": "ASDF 3rd Air Wing", + "branch": "asdf", + "lat": 40.706, + "lng": 141.371 + }, + { + "name": "Chitose Air Base", + "country": "Japan", + "operator": "ASDF 2nd Air Wing", + "branch": "asdf", + "lat": 42.795, + "lng": 141.666 + }, + { + "name": "Nyutabaru Air Base", + "country": "Japan", + "operator": "ASDF 5th Air Wing", + "branch": "asdf", + "lat": 32.084, + "lng": 131.451 + }, + { + "name": "Tsuiki Air Base", + "country": "Japan", + "operator": "ASDF 8th Air Wing", + "branch": "asdf", + "lat": 33.685, + "lng": 131.041 + }, + { + "name": "Komatsu Air Base", + "country": "Japan", + "operator": "ASDF 6th Air Wing", + "branch": "asdf", + "lat": 36.395, + "lng": 136.407 + }, + { + "name": "Hamamatsu Air Base", + "country": "Japan", + "operator": "ASDF Air Defense Command", + "branch": "asdf", + "lat": 34.750, + "lng": 137.703 + }, + { + "name": "Iruma Air Base", + "country": "Japan", + "operator": "ASDF Central Air Command HQ", + "branch": "asdf", + "lat": 35.842, + "lng": 139.411 + }, + { + "name": "Yokosuka Naval Base (MSDF)", + "country": "Japan", + "operator": "MSDF Fleet HQ", + "branch": "msdf", + "lat": 35.290, + "lng": 139.665 + }, + { + "name": "Kure Naval Base", + "country": "Japan", + "operator": "MSDF Kure District", + "branch": "msdf", + "lat": 34.233, + "lng": 132.566 + }, + { + "name": "Sasebo Naval Base (MSDF)", + "country": "Japan", + "operator": "MSDF Sasebo District", + "branch": "msdf", + "lat": 33.165, + "lng": 129.715 + }, + { + "name": "Maizuru Naval Base", + "country": "Japan", + "operator": "MSDF Maizuru District", + "branch": "msdf", + "lat": 35.469, + "lng": 135.383 + }, + { + "name": "Kanoya Air Base (MSDF)", + "country": "Japan", + "operator": "MSDF Fleet Air Wing 1", + "branch": "msdf", + "lat": 31.367, + "lng": 130.845 + }, + { + "name": "Ominato Naval Base", + "country": "Japan", + "operator": "MSDF Ominato District", + "branch": "msdf", + "lat": 41.279, + "lng": 141.135 + }, + { + "name": "Camp Naha (GSDF)", + "country": "Japan", + "operator": "GSDF 15th Brigade", + "branch": "gsdf", + "lat": 26.334, + "lng": 127.769 + }, + { + "name": "Yonaguni Garrison", + "country": "Japan", + "operator": "GSDF Yonaguni Coastal Surveillance", + "branch": "gsdf", + "lat": 24.467, + "lng": 122.978 + }, + { + "name": "Miyako Island Garrison", + "country": "Japan", + "operator": "GSDF Miyako Garrison", + "branch": "gsdf", + "lat": 24.791, + "lng": 125.281 + }, + { + "name": "Amami Garrison", + "country": "Japan", + "operator": "GSDF Amami Garrison", + "branch": "gsdf", + "lat": 28.381, + "lng": 129.494 } ] diff --git a/frontend/src/components/MaplibreViewer.tsx b/frontend/src/components/MaplibreViewer.tsx index ca65d6b..2c13fed 100644 --- a/frontend/src/components/MaplibreViewer.tsx +++ b/frontend/src/components/MaplibreViewer.tsx @@ -1890,6 +1890,7 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele if (!base) return null; const branchLabel: Record = { air_force: 'AIR FORCE', navy: 'NAVY', marines: 'MARINES', army: 'ARMY', + gsdf: 'GSDF (陸自)', msdf: 'MSDF (海自)', asdf: 'ASDF (空自)', }; return (