chore: 更新项目文档与忽略规则,移除旧镜像归档跟踪
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+13
-4
@@ -8,6 +8,7 @@ instance/
|
|||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
user_input/
|
user_input/
|
||||||
|
# Note: user_input/广寒计划/Timeline.md is intentionally tracked (mission timeline source)
|
||||||
|
|
||||||
# Temp files
|
# Temp files
|
||||||
~$*
|
~$*
|
||||||
@@ -20,19 +21,27 @@ scripts/update_vulture_wiki.py
|
|||||||
# Reference docs & reports
|
# Reference docs & reports
|
||||||
docs/
|
docs/
|
||||||
|
|
||||||
# Tests (E2E/screenshot-based)
|
# Local E2E tests remain ignored; keep new regression coverage trackable.
|
||||||
tests/
|
tests/*
|
||||||
|
!tests/test_first_stage_fixes.py
|
||||||
|
|
||||||
# Demo screenshots
|
# Demo screenshots
|
||||||
demo/test_screenshots/
|
demo/test_screenshots/
|
||||||
|
|
||||||
# Playwright output and logs
|
# Generated output: deployment bundles, screenshot caches, Playwright logs
|
||||||
output/playwright/
|
output/
|
||||||
.playwright-cli/
|
.playwright-cli/
|
||||||
.superpowers/
|
.superpowers/
|
||||||
|
|
||||||
# KSP screenshots and upload cache
|
# KSP screenshots and upload cache
|
||||||
data/KSP/
|
data/KSP/
|
||||||
|
|
||||||
|
# Docker image archives (rebuilt per release; deploy docs are the source of truth)
|
||||||
|
docker_image/*.tar
|
||||||
|
docker_image/*.tar.gz
|
||||||
|
|
||||||
|
# Verification screenshots
|
||||||
|
artifacts/
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
test1
|
test1
|
||||||
|
|||||||
@@ -43,6 +43,18 @@ python scripts/wiki_update_page.py --path "home/Space_Shuttles/Vulture_Shuttle"
|
|||||||
|
|
||||||
Add `--create-if-missing` for new pages. HTML files use `<style>` blocks (extracted to page CSS) and wiki-infobox table patterns. Images use `<div class="image-placeholder">` when the actual render is not yet available.
|
Add `--create-if-missing` for new pages. HTML files use `<style>` blocks (extracted to page CSS) and wiki-infobox table patterns. Images use `<div class="image-placeholder">` when the actual render is not yet available.
|
||||||
|
|
||||||
|
### Mission gallery release gate (mandatory)
|
||||||
|
|
||||||
|
- Mission gallery interaction is provided only by `scripts/wiki_mission_gallery_runtime.js`, published with `scripts/wiki_publish_mission_gallery_runtime.py`. Do not put gallery JavaScript in page `scriptJs` or inline `<script>` blocks.
|
||||||
|
- After any gallery content, CSS, runtime, or publishing change, run both commands below. A gallery release is incomplete if either command fails or if screenshots were not visually inspected:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
py -3.12 -m pytest tests/test_mission_galleries.py -q -p no:cacheprovider
|
||||||
|
py -3.12 scripts/wiki_verify_mission_galleries.py --screenshot-dir output/playwright/mission-gallery-e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
- The E2E gate must cover every mission gallery, not a sample. See `docs/wiki-mission-gallery-quality-gate.md` for the incident root cause and checklist.
|
||||||
|
|
||||||
## Database
|
## Database
|
||||||
|
|
||||||
- PostgreSQL is authoritative for mission logs and timelines
|
- PostgreSQL is authoritative for mission logs and timelines
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -14,9 +14,10 @@ MOON_PERIOD = timedelta(days=27.32166) # 月球自转周期
|
|||||||
TRANSIT_DAYS = timedelta(days=5) # 地月转移耗时
|
TRANSIT_DAYS = timedelta(days=5) # 地月转移耗时
|
||||||
|
|
||||||
# 参考着陆窗口(UTC)—— 发射窗口 = 着陆窗口 − 5 天地月转移
|
# 参考着陆窗口(UTC)—— 发射窗口 = 着陆窗口 − 5 天地月转移
|
||||||
# 基于 2024 年 10 月精确窗口校准
|
# A窗: 2025-05-14 04:09 着陆 校准 (k=8)
|
||||||
REF_LANDING_A = datetime(2024, 10, 21, 4, 55, tzinfo=timezone.utc)
|
# B窗: 2025-03-15 02:22 着陆 校准 (k=5)
|
||||||
REF_LANDING_B = datetime(2024, 10, 29, 3, 15, tzinfo=timezone.utc)
|
REF_LANDING_A = datetime(2024, 10, 7, 14, 23, tzinfo=timezone.utc)
|
||||||
|
REF_LANDING_B = datetime(2024, 10, 29, 11, 46, tzinfo=timezone.utc)
|
||||||
REF_LAUNCH_A = REF_LANDING_A - TRANSIT_DAYS
|
REF_LAUNCH_A = REF_LANDING_A - TRANSIT_DAYS
|
||||||
REF_LAUNCH_B = REF_LANDING_B - TRANSIT_DAYS
|
REF_LAUNCH_B = REF_LANDING_B - TRANSIT_DAYS
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from openpyxl import Workbook
|
||||||
|
|
||||||
|
from app import create_app
|
||||||
|
from app.config import Config
|
||||||
|
from app.extensions import db
|
||||||
|
from app.models import Asset, AssetLogEntry, SimulationSetting
|
||||||
|
from app.services.log_book_importer import import_log_book_data
|
||||||
|
|
||||||
|
|
||||||
|
class TestConfig(Config):
|
||||||
|
TESTING = True
|
||||||
|
SECRET_KEY = "test"
|
||||||
|
SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
|
||||||
|
|
||||||
|
|
||||||
|
class FirstStageFixTests(unittest.TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.app = create_app(TestConfig)
|
||||||
|
self.client = self.app.test_client()
|
||||||
|
with self.app.app_context():
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
with self.app.app_context():
|
||||||
|
db.session.remove()
|
||||||
|
db.drop_all()
|
||||||
|
|
||||||
|
def test_log_book_import_uses_state_nodes_and_imports_sub_log_events(self) -> None:
|
||||||
|
workbook = Workbook()
|
||||||
|
sheet = workbook.active
|
||||||
|
sheet.title = "ST-01"
|
||||||
|
sheet.append(["Log", "Start", "End", "Detail", "Sub log", None, None, "Location"])
|
||||||
|
sheet.append(
|
||||||
|
[
|
||||||
|
"Mars transfer",
|
||||||
|
datetime(2060, 1, 1),
|
||||||
|
datetime(2060, 1, 10),
|
||||||
|
"Outbound mission",
|
||||||
|
"2060-01-02 Reached Mars orbit",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
"LEO",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
path = Path(temp_dir) / "log_book.xlsx"
|
||||||
|
workbook.save(path)
|
||||||
|
with self.app.app_context():
|
||||||
|
summary = import_log_book_data(path)
|
||||||
|
entries = db.session.query(AssetLogEntry).order_by(AssetLogEntry.start_at).all()
|
||||||
|
|
||||||
|
self.assertEqual(summary.imported_assets[0].state_entries, 1)
|
||||||
|
self.assertEqual(summary.imported_assets[0].event_entries, 1)
|
||||||
|
self.assertEqual([entry.entry_kind for entry in entries], ["state", "event"])
|
||||||
|
self.assertEqual(entries[0].state_nodes[0].target_location, "LEO")
|
||||||
|
self.assertEqual(entries[0].state_nodes[0].state_label, "Mars transfer")
|
||||||
|
self.assertEqual(entries[1].state_nodes[0].target_location, "Mars")
|
||||||
|
|
||||||
|
def test_dashboard_attributes_only_events_within_thirty_days(self) -> None:
|
||||||
|
with self.app.app_context():
|
||||||
|
db.session.add(SimulationSetting(key="current_time", value="2060-03-12T09:00"))
|
||||||
|
alpha = Asset(name="Alpha", asset_type="Vehicle")
|
||||||
|
beta = Asset(name="Beta", asset_type="Vehicle")
|
||||||
|
alpha.log_entries.extend(
|
||||||
|
[
|
||||||
|
AssetLogEntry(
|
||||||
|
entry_kind="event",
|
||||||
|
title="Alpha event",
|
||||||
|
start_at=datetime(2060, 3, 20, tzinfo=timezone.utc),
|
||||||
|
),
|
||||||
|
AssetLogEntry(
|
||||||
|
entry_kind="event",
|
||||||
|
title="Too far away",
|
||||||
|
start_at=datetime(2060, 5, 1, tzinfo=timezone.utc),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
beta.log_entries.append(
|
||||||
|
AssetLogEntry(
|
||||||
|
entry_kind="event",
|
||||||
|
title="Beta event",
|
||||||
|
start_at=datetime(2060, 3, 21, tzinfo=timezone.utc),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
db.session.add_all([alpha, beta])
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
response = self.client.get("/")
|
||||||
|
page = response.get_data(as_text=True)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertIn("Alpha: Alpha event", page)
|
||||||
|
self.assertIn("Beta: Beta event", page)
|
||||||
|
self.assertNotIn("Beta: Alpha event", page)
|
||||||
|
self.assertNotIn("Too far away", page)
|
||||||
|
|
||||||
|
def test_simulation_time_endpoint_persists_through_model(self) -> None:
|
||||||
|
response = self.client.post(
|
||||||
|
"/api/v1/sim-time",
|
||||||
|
json={"sim_time": "2061-04-05T06:07"},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
with self.app.app_context():
|
||||||
|
setting = db.session.get(SimulationSetting, "current_time")
|
||||||
|
self.assertIsNotNone(setting)
|
||||||
|
self.assertEqual(setting.value, "2061-04-05T06:07")
|
||||||
|
|
||||||
|
def test_location_and_timeline_json_escape_script_terminators(self) -> None:
|
||||||
|
payload = "Probe </script><script>alert(1)</script>"
|
||||||
|
with self.app.app_context():
|
||||||
|
asset = Asset(name=payload, asset_type="Station", home_region="LEO")
|
||||||
|
asset.log_entries.append(
|
||||||
|
AssetLogEntry(
|
||||||
|
entry_kind="event",
|
||||||
|
title=payload,
|
||||||
|
start_at=datetime(2060, 3, 1, tzinfo=timezone.utc),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
db.session.add(asset)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
for path in ("/mission-preview/location-board", "/mission-preview/timeline"):
|
||||||
|
response = self.client.get(path)
|
||||||
|
page = response.get_data(as_text=True)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertNotIn(payload, page)
|
||||||
|
self.assertIn("\\u003c/script\\u003e", page)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user