feat: add test framework, unit tests, and GitLab CI pipeline
- Add pytest config in backend/pyproject.toml with asyncio mode and coverage - Add backend/requirements-test.txt with pytest, pytest-cov, pytest-asyncio, aiosqlite - Write 86 backend unit tests covering auth (JWT tokens), schemas (Pydantic models), config (Settings env overrides), log_parser (log parsing, IP resolution, geo data), models (SQLAlchemy table definitions + column defaults), and theme export - Add Vitest config for Angular frontend (vitest.config.ts, src/test-setup.ts) - Add .gitlab-ci.yml with stages: backend-test, frontend-test, backend-lint, docker-build, and quality-gate - CI enforces 10% minimum coverage threshold for backend
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
"""Tests for app.models — SQLAlchemy model definitions and relationships."""
|
||||
|
||||
import pytest
|
||||
|
||||
from app.models import (
|
||||
Base,
|
||||
Show,
|
||||
ShowSchedule,
|
||||
Event,
|
||||
StationConfig,
|
||||
HistoryEntry,
|
||||
TeamMember,
|
||||
CommunityHighlight,
|
||||
Underwriter,
|
||||
VisitorStatDaily,
|
||||
VisitorStatGeo,
|
||||
)
|
||||
|
||||
|
||||
class TestModelDefinitions:
|
||||
"""Verify that models are properly defined."""
|
||||
|
||||
def test_show_table_name(self):
|
||||
assert Show.__tablename__ == "shows"
|
||||
|
||||
def test_show_schedule_table_name(self):
|
||||
assert ShowSchedule.__tablename__ == "show_schedules"
|
||||
|
||||
def test_event_table_name(self):
|
||||
assert Event.__tablename__ == "events"
|
||||
|
||||
def test_station_config_table_name(self):
|
||||
assert StationConfig.__tablename__ == "station_config"
|
||||
|
||||
def test_history_entry_table_name(self):
|
||||
assert HistoryEntry.__tablename__ == "history_entries"
|
||||
|
||||
def test_team_member_table_name(self):
|
||||
assert TeamMember.__tablename__ == "team_members"
|
||||
|
||||
def test_community_highlight_table_name(self):
|
||||
assert CommunityHighlight.__tablename__ == "community_highlights"
|
||||
|
||||
def test_underwriter_table_name(self):
|
||||
assert Underwriter.__tablename__ == "underwriters"
|
||||
|
||||
def test_visitor_stats_daily_table_name(self):
|
||||
assert VisitorStatDaily.__tablename__ == "visitor_stats_daily"
|
||||
|
||||
def test_visitor_stats_geo_table_name(self):
|
||||
assert VisitorStatGeo.__tablename__ == "visitor_stats_geo"
|
||||
|
||||
|
||||
class TestModelDefaults:
|
||||
"""Verify SQLAlchemy column-level defaults (set at INSERT time)."""
|
||||
|
||||
def _col_default_value(self, model_cls, col_name):
|
||||
"""Extract the Column default value from a model's metadata."""
|
||||
col = model_cls.__table__.columns[col_name]
|
||||
default = col.default
|
||||
if default is None:
|
||||
return None
|
||||
# DefaultClause.arg is a property (not callable) in SQLAlchemy 2.0
|
||||
if hasattr(default, "arg"):
|
||||
return default.arg
|
||||
return default
|
||||
|
||||
def test_show_defaults(self):
|
||||
assert self._col_default_value(Show, "display_order") == 0
|
||||
assert self._col_default_value(Show, "active") is True
|
||||
|
||||
def test_event_defaults(self):
|
||||
assert self._col_default_value(Event, "active") is True
|
||||
|
||||
def test_station_config_defaults(self):
|
||||
assert self._col_default_value(StationConfig, "callsign") == "KMTN"
|
||||
assert self._col_default_value(StationConfig, "name_primary") == "KMountain"
|
||||
assert self._col_default_value(StationConfig, "name_secondary") == "Flower Radio"
|
||||
assert self._col_default_value(StationConfig, "frequency") == "98.7 FM"
|
||||
assert self._col_default_value(StationConfig, "founded_year") == 1987
|
||||
assert self._col_default_value(StationConfig, "nonprofit_type") == "501(c)(3)"
|
||||
|
||||
def test_history_entry_defaults(self):
|
||||
assert self._col_default_value(HistoryEntry, "active") is True
|
||||
|
||||
def test_team_member_defaults(self):
|
||||
assert self._col_default_value(TeamMember, "active") is True
|
||||
|
||||
def test_underwriter_defaults(self):
|
||||
assert self._col_default_value(Underwriter, "display_order") == 0
|
||||
assert self._col_default_value(Underwriter, "active") is True
|
||||
|
||||
|
||||
class TestBaseModel:
|
||||
"""Verify Base declarative model setup."""
|
||||
|
||||
def test_base_has_metadata(self):
|
||||
assert hasattr(Base, "metadata")
|
||||
|
||||
def test_all_models_registered_in_metadata(self):
|
||||
tables = Base.metadata.tables
|
||||
assert "shows" in tables
|
||||
assert "show_schedules" in tables
|
||||
assert "events" in tables
|
||||
assert "station_config" in tables
|
||||
assert "history_entries" in tables
|
||||
assert "team_members" in tables
|
||||
assert "community_highlights" in tables
|
||||
assert "underwriters" in tables
|
||||
assert "visitor_stats_daily" in tables
|
||||
assert "visitor_stats_geo" in tables
|
||||
Reference in New Issue
Block a user