- Updated the client submodule to the latest commit for enhanced compatibility. - Refactored HTML export logic to utilize a dedicated function for building HTML documents, improving code clarity and maintainability. - Enhanced test assertions across various files to include descriptive messages, aiding in debugging and understanding test failures. All quality checks pass.
132 lines
4.7 KiB
Python
132 lines
4.7 KiB
Python
"""Cross-language constant synchronization validation.
|
|
|
|
This module validates that constants defined in Python match their counterparts
|
|
in Rust (client/src-tauri/src/constants.rs) to prevent drift between codebases.
|
|
|
|
These tests serve as documentation and early warning for constant mismatches.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from noteflow.config.constants import (
|
|
DEFAULT_GRPC_PORT,
|
|
DEFAULT_SAMPLE_RATE,
|
|
SECONDS_PER_HOUR,
|
|
)
|
|
|
|
# Path to Rust constants file
|
|
RUST_CONSTANTS_PATH = Path(__file__).parent.parent / "client/src-tauri/src/constants.rs"
|
|
|
|
# Expected constant values for validation tests
|
|
EXPECTED_SAMPLE_RATE_HZ = 16000 # 16kHz standard for speech recognition
|
|
EXPECTED_GRPC_PORT = 50051 # Standard gRPC port convention
|
|
EXPECTED_SECONDS_PER_HOUR = 3600 # 60 * 60
|
|
|
|
|
|
def _parse_rust_constant(content: str, module: str, name: str) -> str | None:
|
|
"""Extract a constant value from Rust source.
|
|
|
|
Args:
|
|
content: Full Rust file content
|
|
module: Module name (e.g., 'audio', 'grpc', 'time')
|
|
name: Constant name (e.g., 'DEFAULT_SAMPLE_RATE')
|
|
|
|
Returns:
|
|
The constant value as a string, or None if not found
|
|
"""
|
|
# Find the module block
|
|
module_pattern = rf"pub mod {module}\s*\{{"
|
|
module_match = re.search(module_pattern, content)
|
|
if not module_match:
|
|
return None
|
|
|
|
# Find module end (matching braces)
|
|
start = module_match.end()
|
|
brace_count = 1
|
|
end = start
|
|
while brace_count > 0 and end < len(content):
|
|
if content[end] == "{":
|
|
brace_count += 1
|
|
elif content[end] == "}":
|
|
brace_count -= 1
|
|
end += 1
|
|
|
|
module_content = content[start:end]
|
|
|
|
# Find the constant within the module
|
|
const_pattern = rf"pub const {name}:\s*\w+\s*=\s*([^;]+);"
|
|
const_match = re.search(const_pattern, module_content)
|
|
if const_match:
|
|
return const_match.group(1).strip()
|
|
return None
|
|
|
|
|
|
@pytest.fixture
|
|
def rust_constants() -> str:
|
|
"""Load Rust constants file content."""
|
|
if not RUST_CONSTANTS_PATH.exists():
|
|
pytest.skip("Rust constants file not found (client not present)")
|
|
return RUST_CONSTANTS_PATH.read_text()
|
|
|
|
|
|
class TestCrossLanguageConstantSync:
|
|
"""Validate Python constants match Rust counterparts.
|
|
|
|
These tests ensure that shared constants remain synchronized across
|
|
the Python backend and Rust/Tauri client.
|
|
"""
|
|
|
|
def test_default_sample_rate_matches_rust(self, rust_constants: str) -> None:
|
|
"""DEFAULT_SAMPLE_RATE must match between Python and Rust."""
|
|
rust_value = _parse_rust_constant(rust_constants, "audio", "DEFAULT_SAMPLE_RATE")
|
|
assert rust_value is not None, "DEFAULT_SAMPLE_RATE not found in Rust constants"
|
|
assert int(rust_value) == DEFAULT_SAMPLE_RATE, (
|
|
f"Sample rate mismatch: Python={DEFAULT_SAMPLE_RATE}, Rust={rust_value}"
|
|
)
|
|
|
|
def test_default_grpc_port_matches_rust(self, rust_constants: str) -> None:
|
|
"""DEFAULT_GRPC_PORT must match between Python and Rust."""
|
|
rust_value = _parse_rust_constant(rust_constants, "grpc", "DEFAULT_PORT")
|
|
assert rust_value is not None, "DEFAULT_PORT not found in Rust constants"
|
|
assert int(rust_value) == DEFAULT_GRPC_PORT, (
|
|
f"gRPC port mismatch: Python={DEFAULT_GRPC_PORT}, Rust={rust_value}"
|
|
)
|
|
|
|
def test_seconds_per_hour_matches_rust(self, rust_constants: str) -> None:
|
|
"""SECONDS_PER_HOUR must match between Python and Rust."""
|
|
rust_value = _parse_rust_constant(rust_constants, "time", "SECONDS_PER_HOUR")
|
|
assert rust_value is not None, "SECONDS_PER_HOUR not found in Rust constants"
|
|
assert int(rust_value) == SECONDS_PER_HOUR, (
|
|
f"Seconds per hour mismatch: Python={SECONDS_PER_HOUR}, Rust={rust_value}"
|
|
)
|
|
|
|
|
|
class TestConstantValues:
|
|
"""Validate constant values are within expected ranges.
|
|
|
|
These tests document expected values and catch accidental changes.
|
|
"""
|
|
|
|
def test_sample_rate_is_16khz(self) -> None:
|
|
"""Standard sample rate for speech recognition is 16kHz."""
|
|
assert DEFAULT_SAMPLE_RATE == EXPECTED_SAMPLE_RATE_HZ, (
|
|
f"DEFAULT_SAMPLE_RATE should be {EXPECTED_SAMPLE_RATE_HZ} Hz"
|
|
)
|
|
|
|
def test_grpc_port_is_standard(self) -> None:
|
|
"""Default gRPC port follows convention (50051)."""
|
|
assert DEFAULT_GRPC_PORT == EXPECTED_GRPC_PORT, (
|
|
f"DEFAULT_GRPC_PORT should be {EXPECTED_GRPC_PORT}"
|
|
)
|
|
|
|
def test_seconds_per_hour_is_correct(self) -> None:
|
|
"""Seconds per hour must be 3600."""
|
|
assert SECONDS_PER_HOUR == EXPECTED_SECONDS_PER_HOUR, (
|
|
f"SECONDS_PER_HOUR should be {EXPECTED_SECONDS_PER_HOUR}"
|
|
)
|