스냅샷 어드민 웹 UI + WBS-7.10 Tabler 테이블 그리드 조회
settings/account_snapshot SQLite를 직접 편집하는 잠금/승인/변경이력 기반 웹 에디터를 추가하고, 2026-06-21 비판적 리뷰에서 요청된 테이블별 그리드 조회 기능(Tabler CDN)을 /tables 경로로 덧붙인다. - 잠금(lock)·승인(approval)·undo·변경로그 전체 감사 추적 - KIS Collection 대시보드 통합(별도 SQLite, 워크스페이스 DB와 분리) - WBS-7.10: 워크스페이스/KIS수집/정성매도전략 3개 SQLite, 11개 테이블을 Tabler 그리드로 조회 — 테이블명은 고정 화이트리스트와 정확히 일치할 때만 SQL에 사용(SQL 인젝션 방지, 단위테스트로 검증)
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
import tools.validate_snapshot_admin_web_v1 as validator
|
||||
from src.quant_engine.snapshot_admin_server_v1 import (
|
||||
build_ui_state,
|
||||
fetch_table_rows,
|
||||
list_browsable_tables,
|
||||
render_collection_html,
|
||||
render_index_html,
|
||||
render_tables_html,
|
||||
)
|
||||
from src.quant_engine.snapshot_admin_store_v1 import import_seed_json
|
||||
|
||||
|
||||
def test_render_index_html_contains_spreadsheet_surface():
|
||||
html = render_index_html()
|
||||
assert "Snapshot Admin" in html
|
||||
assert "contenteditable" in html
|
||||
assert "/api/settings/save" in html
|
||||
assert "/api/account_snapshot/save" in html
|
||||
assert "Lock target" in html
|
||||
assert "Lock row" in html
|
||||
assert "Approve pending" in html
|
||||
assert "Refresh diff" in html
|
||||
assert "Export approval packet" in html
|
||||
assert "Selection Inspector" in html
|
||||
assert "Recent row history" in html
|
||||
assert "Save view" in html
|
||||
assert "Apply TSV to selection" in html
|
||||
assert "Ctrl+S" in html
|
||||
assert "KIS Collection" in html
|
||||
assert "Recent collector snapshots" in html
|
||||
assert "Collection detail" in html
|
||||
assert "Filter runs / snapshots / errors" in html
|
||||
assert "Filter change log" in html
|
||||
assert "Timeline" in html
|
||||
assert "/collection" in html
|
||||
assert "Open collection dashboard" in html
|
||||
|
||||
|
||||
def test_render_collection_html_contains_dashboard_surface():
|
||||
html = render_collection_html()
|
||||
assert "KIS Collection Dashboard" in html
|
||||
assert "/api/state" in html
|
||||
assert "Download raw JSON" in html
|
||||
assert "Download CSV" in html
|
||||
assert "Filter runs / snapshots / errors" in html
|
||||
assert "Ticker quick search" in html
|
||||
assert "Date quick search" in html
|
||||
|
||||
|
||||
def test_build_ui_state_exposes_expected_columns(tmp_path):
|
||||
db_path = tmp_path / "snapshot_admin.db"
|
||||
seed_path = ROOT / "GatherTradingData.json"
|
||||
import_seed_json(db_path, seed_path)
|
||||
|
||||
state = build_ui_state(db_path)
|
||||
assert state["summary"]["settings_rows"] > 0
|
||||
assert state["summary"]["account_snapshot_rows"] > 0
|
||||
assert state["summary"]["topology"]["mode"] == "single_workspace_sqlite"
|
||||
assert state["summary"]["topology"]["settings_and_snapshot_share_db"] is True
|
||||
assert state["summary"]["topology"]["collector_separate_db"] is True
|
||||
assert state["account_snapshot_columns"][0] == "captured_at"
|
||||
assert "settings" in state["validation"]
|
||||
assert state["version"]["app"]
|
||||
assert "fingerprint" in state["version"]["source"]
|
||||
assert "collection" in state
|
||||
assert "counts" in state["collection"]
|
||||
assert "latest_report" in state["collection"]
|
||||
assert state["summary"]["topology"]["mode"] == "single_workspace_sqlite"
|
||||
|
||||
|
||||
def test_snapshot_admin_workflow_and_script_exist():
|
||||
workflow = ROOT / ".gitea" / "workflows" / "snapshot_admin.yml"
|
||||
package = json.loads((ROOT / "package.json").read_text(encoding="utf-8"))
|
||||
assert workflow.exists()
|
||||
assert "--reload" in package["scripts"]["ops:snapshot-web"]
|
||||
assert "ops:snapshot-validate" in package["scripts"]
|
||||
assert "ops:snapshot-web-validate" in package["scripts"]
|
||||
|
||||
|
||||
def test_render_tables_html_contains_tabler_grid_surface():
|
||||
html = render_tables_html()
|
||||
assert "tabler" in html.lower()
|
||||
assert "tableSelect" in html
|
||||
assert "/api/tables" in html
|
||||
assert "/api/table_rows" in html
|
||||
assert "gridTable" in html
|
||||
|
||||
|
||||
def test_list_browsable_tables_covers_all_three_databases(tmp_path):
|
||||
db_path = tmp_path / "snapshot_admin.db"
|
||||
import_seed_json(db_path, ROOT / "GatherTradingData.json")
|
||||
|
||||
tables = list_browsable_tables(db_path)
|
||||
names = {row["table"] for row in tables}
|
||||
assert {"settings", "account_snapshot", "workspace_change_log"} <= names
|
||||
assert {"collection_runs", "collection_snapshots", "collection_source_errors"} <= names
|
||||
assert {"sell_strategy_results", "satellite_recommendations"} <= names
|
||||
|
||||
settings_row = next(row for row in tables if row["table"] == "settings")
|
||||
assert settings_row["exists"] is True
|
||||
assert settings_row["row_count"] > 0
|
||||
|
||||
|
||||
def test_fetch_table_rows_paginates_and_rejects_unknown_table(tmp_path):
|
||||
db_path = tmp_path / "snapshot_admin.db"
|
||||
import_seed_json(db_path, ROOT / "GatherTradingData.json")
|
||||
|
||||
page1 = fetch_table_rows("settings", db_path, limit=2, offset=0)
|
||||
assert page1["columns"]
|
||||
assert len(page1["rows"]) == 2
|
||||
assert page1["total"] > 2
|
||||
|
||||
page2 = fetch_table_rows("settings", db_path, limit=2, offset=2)
|
||||
assert page1["rows"] != page2["rows"]
|
||||
|
||||
import pytest
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
fetch_table_rows("settings; DROP TABLE settings;--", db_path)
|
||||
|
||||
|
||||
def test_snapshot_admin_web_validation_script_passes():
|
||||
out = ROOT / "Temp" / "snapshot_admin_web_validation_v1.json"
|
||||
if out.exists():
|
||||
out.unlink()
|
||||
|
||||
rc = validator.main()
|
||||
payload = json.loads(out.read_text(encoding="utf-8"))
|
||||
|
||||
assert rc == 0
|
||||
assert payload["gate"] == "PASS"
|
||||
assert payload["formula_id"] == "SNAPSHOT_ADMIN_WEB_VALIDATION_V1"
|
||||
assert payload["settings_rows"] > 0
|
||||
assert payload["account_snapshot_rows"] > 0
|
||||
Reference in New Issue
Block a user