- Authoritative candidate capture with server-issued artifacts and raw-byte
immutability hashing (source_response_hash server-owned)
- Closed-period lifecycle: request-hash bound approvals, persisted closure
immutability violations, byte-for-byte catalog stability on reclosure
- Verification runs: persisted VerificationRun model + FK migration,
publish gate (block_publish), scheduled observability runs (02:00 UTC)
- FR-013 baseline inheritance: prior_release_id migration, plan_inheritance/
execute_inheritance classification and re-extraction, API endpoints
- Visual executor bound to release-deployment environment; caller mismatch
rejected; visual SSIM/reconciliation modules
- Query execution decomposed: envelope/model/executor split, no direct SQL
- AgentRun approvals extracted to submodule; evidence adapter; _utils
- Dashboard testing service decomposed into 30+ modules (all <400 LOC)
- Five Feature-037 agent tools with permission guards (tools_037.py)
- API readiness endpoint; Alembic env/migrations; test fixture repos
- Specs 036/037 contracts, openapi.yaml, schema.json, tasks/traceability
updated; semantic index rebuilt with 0 parse warnings
- Fix ADR-0003 parser ambiguity: remove [DEF🆔ADR] prose example
- Add axiom-mcp-agent-feedback.md: agent findings for MCP rework plan
- Tests: 298 service + 1464 API + 45 agent passing; ruff clean
135 lines
7.4 KiB
Python
135 lines
7.4 KiB
Python
# #region Test.DashboardTesting.ChartDataRaw [C:3] [TYPE Module] [SEMANTICS testing,baseline,chart-data,raw,hash]
|
|
# @defgroup Tests for SupersetClient.ChartData raw response path — raw bytes, hash, backward compatibility.
|
|
# @LAYER Test
|
|
# @RELATION BINDS_TO -> [SupersetClient.ChartData.Execute]
|
|
# @TEST_EDGE: raw_dto_returns_parsed_and_bytes -> ChartDataResponse carries both parsed dict and raw bytes.
|
|
# @TEST_EDGE: backward_compat_dict_return -> execute_chart_data returns dict (not ChartDataResponse).
|
|
# @TEST_EDGE: raw_hash_matches_shared -> source_response_hash from raw path equals compute_source_response_hash.
|
|
# @TEST_EDGE: different_raw_bytes_different_hash -> Same parsed JSON with different whitespace yields different hash.
|
|
# @TEST_EDGE: same_raw_bytes_same_hash -> Exact same bytes produce exact same hash.
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
|
|
from src.core.superset_client._chart_data import ChartDataResponse
|
|
from src.services.dashboard_testing.immutability import compute_source_response_hash
|
|
|
|
|
|
# #region Test.DashboardTesting.ChartDataRaw.RawResponseDTO [C:2] [TYPE Class] [SEMANTICS test,chart-data,raw,dto]
|
|
class TestChartDataRawResponseDTO:
|
|
"""Verify ChartDataResponse carries parsed + raw bytes + hash correctly."""
|
|
|
|
# #region Test.DashboardTesting.ChartDataRaw.TestRawResponse [C:2] [TYPE Function]
|
|
# @BRIEF ChartDataResponse stores parsed dict, raw bytes, and pre-extraction hash.
|
|
def test_raw_response_dto_contents(self):
|
|
"""ChartDataResponse must carry parsed, raw_bytes, and source_response_hash."""
|
|
payload = {"result": [{"data": {"count": 100}}], "query_id": "q-1"}
|
|
raw = json.dumps(payload, sort_keys=True).encode()
|
|
h = hashlib.sha256(raw).hexdigest()
|
|
dto = ChartDataResponse(parsed=payload, raw_bytes=raw, source_response_hash=h)
|
|
|
|
assert dto.parsed == payload
|
|
assert dto.raw_bytes == raw
|
|
assert dto.source_response_hash == h
|
|
# #endregion Test.DashboardTesting.ChartDataRaw.TestRawResponse
|
|
|
|
# #region Test.DashboardTesting.ChartDataRaw.TestRawHashEqualsShared [C:2] [TYPE Function]
|
|
# @BRIEF source_response_hash from ChartDataResponse equals compute_source_response_hash result.
|
|
def test_raw_hash_equals_shared_helper(self):
|
|
"""Hash from ChartDataResponse must match compute_source_response_hash."""
|
|
payload = {"result": [{"data": {"revenue": 50000.0}}], "query_id": "q-42"}
|
|
raw = json.dumps(payload, sort_keys=True).encode()
|
|
h = hashlib.sha256(raw).hexdigest()
|
|
dto = ChartDataResponse(parsed=payload, raw_bytes=raw, source_response_hash=h)
|
|
|
|
expected = compute_source_response_hash(raw)
|
|
assert dto.source_response_hash == expected
|
|
# #endregion Test.DashboardTesting.ChartDataRaw.TestRawHashEqualsShared
|
|
|
|
# #region Test.DashboardTesting.ChartDataRaw.TestDifferentWhitespaceDifferentHash [C:2] [TYPE Function]
|
|
# @BRIEF Semantically equal JSON with different whitespace yields same parsed value but different hash.
|
|
def test_different_whitespace_different_hash(self):
|
|
"""Same JSON data with different whitespace = different hash, same normalized value."""
|
|
payload = {"result": [{"data": {"count": 150}}], "query_id": "q-3"}
|
|
|
|
# Compact JSON (no whitespace)
|
|
compact = json.dumps(payload, separators=(",", ":")).encode()
|
|
# Pretty-printed JSON (with whitespace)
|
|
pretty = json.dumps(payload, indent=2).encode()
|
|
# Sorted keys JSON
|
|
sorted_json = json.dumps(payload, sort_keys=True).encode()
|
|
|
|
# All three have the same parsed value
|
|
assert json.loads(compact) == json.loads(pretty) == json.loads(sorted_json)
|
|
|
|
# But all three have DIFFERENT hashes
|
|
hashes = {
|
|
compute_source_response_hash(compact),
|
|
compute_source_response_hash(pretty),
|
|
compute_source_response_hash(sorted_json),
|
|
}
|
|
assert len(hashes) == 3, (
|
|
f"Expected 3 unique hashes for different raw byte representations, "
|
|
f"got {len(hashes)}: {[h[:12] for h in hashes]}"
|
|
)
|
|
# #endregion Test.DashboardTesting.ChartDataRaw.TestDifferentWhitespaceDifferentHash
|
|
|
|
# #region Test.DashboardTesting.ChartDataRaw.TestSameBytesSameHash [C:2] [TYPE Function]
|
|
# @BRIEF Exact same bytes produce exact same hash.
|
|
def test_same_bytes_same_hash(self):
|
|
"""Exact same raw bytes must produce identical hash."""
|
|
payload = {"result": [{"data": {"count": 150}}], "query_id": "q-3"}
|
|
raw = json.dumps(payload, sort_keys=True).encode()
|
|
h1 = compute_source_response_hash(raw)
|
|
h2 = compute_source_response_hash(raw)
|
|
assert h1 == h2
|
|
# #endregion Test.DashboardTesting.ChartDataRaw.TestSameBytesSameHash
|
|
|
|
# #region Test.DashboardTesting.ChartDataRaw.TestChangedBytesSameScalarCriticalViolation [C:2] [TYPE Function]
|
|
# @BRIEF Changed bytes but same scalar value causes different hash -> critical immutability.
|
|
def test_changed_bytes_same_scalar_different_hash(self):
|
|
"""Different raw bytes (same canonical scalar) => different hash => immutability violation possible."""
|
|
# Two representations with the same scalar but different metadata
|
|
raw_a = json.dumps({"result": [{"data": {"count": 100}}], "query_id": "q-1"}, sort_keys=True).encode()
|
|
raw_b = json.dumps({"result": [{"data": {"count": 100}}], "query_id": "q-2"}, sort_keys=True).encode()
|
|
|
|
# Same canonical value
|
|
assert json.loads(raw_a)["result"][0]["data"]["count"] == json.loads(raw_b)["result"][0]["data"]["count"]
|
|
|
|
# Different hashes
|
|
hash_a = compute_source_response_hash(raw_a)
|
|
hash_b = compute_source_response_hash(raw_b)
|
|
assert hash_a != hash_b, (
|
|
"Different raw bytes with same scalar must produce different hashes"
|
|
)
|
|
|
|
# If raw_a was captured as a closed-period baseline, raw_b would trigger
|
|
# immutability_violation (hash mismatch), even though the scalar is the same.
|
|
# This is the critical property: metadata changes (query_id) ARE detectable.
|
|
# #endregion Test.DashboardTesting.ChartDataRaw.TestChangedBytesSameScalarCriticalViolation
|
|
|
|
# #region Test.DashboardTesting.ChartDataRaw.TestCallerArbitraryHashRejected [C:2] [TYPE Function]
|
|
# @BRIEF Caller-supplied hash that doesn't match server-computed hash is detected.
|
|
def test_caller_arbitrary_hash_rejected(self):
|
|
"""A caller claiming a hash that doesn't match the bytes must be detectable."""
|
|
payload = {"result": [{"data": {"count": 100}}], "query_id": "q-1"}
|
|
raw = json.dumps(payload, sort_keys=True).encode()
|
|
real_hash = compute_source_response_hash(raw)
|
|
|
|
# Caller claims a DIFFERENT hash
|
|
caller_claim = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
|
|
# The verification must detect the mismatch
|
|
assert real_hash != caller_claim, "Caller hash claim must differ from server-computed hash"
|
|
|
|
# If the caller provided this hash, the server would compute real_hash from the
|
|
# actual response bytes and detect the mismatch.
|
|
mismatch_detected = (caller_claim != compute_source_response_hash(raw))
|
|
assert mismatch_detected, "Server must detect caller hash claim mismatch"
|
|
# #endregion Test.DashboardTesting.ChartDataRaw.TestCallerArbitraryHashRejected
|
|
|
|
# #endregion Test.DashboardTesting.ChartDataRaw.RawResponseDTO
|
|
# #endregion Test.DashboardTesting.ChartDataRaw
|