Implement console/db.py with Database class providing all data access methods for the console application, plus 36 passing tests in console/tests/test_db.py covering vehicle navigation, parts catalog, search, VIN cache, stats, manufacturers, and admin CRUD operations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
274 lines
8.0 KiB
Python
274 lines
8.0 KiB
Python
"""
|
|
Tests for the Database abstraction layer.
|
|
|
|
All tests run against the real SQLite database at vehicle_database/vehicle_database.db.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from console.db import Database
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def db():
|
|
"""Provide a shared Database instance for all tests in this module."""
|
|
return Database()
|
|
|
|
|
|
# =========================================================================
|
|
# Vehicle navigation
|
|
# =========================================================================
|
|
|
|
class TestGetBrands:
|
|
def test_returns_nonempty_list(self, db):
|
|
brands = db.get_brands()
|
|
assert isinstance(brands, list)
|
|
assert len(brands) > 0
|
|
|
|
def test_each_brand_has_name_key(self, db):
|
|
brands = db.get_brands()
|
|
for b in brands:
|
|
assert "name" in b
|
|
|
|
def test_each_brand_has_id_and_country(self, db):
|
|
brands = db.get_brands()
|
|
for b in brands:
|
|
assert "id" in b
|
|
assert "country" in b
|
|
|
|
|
|
class TestGetModels:
|
|
def test_no_filter_returns_nonempty(self, db):
|
|
models = db.get_models()
|
|
assert isinstance(models, list)
|
|
assert len(models) > 0
|
|
|
|
def test_filter_by_uppercase_brand(self, db):
|
|
models = db.get_models(brand="TOYOTA")
|
|
assert isinstance(models, list)
|
|
assert len(models) > 0
|
|
|
|
def test_filter_by_lowercase_brand(self, db):
|
|
"""Brand filtering must be case-insensitive."""
|
|
models = db.get_models(brand="toyota")
|
|
assert isinstance(models, list)
|
|
assert len(models) > 0
|
|
|
|
def test_each_model_has_id_and_name(self, db):
|
|
models = db.get_models()
|
|
for m in models[:5]:
|
|
assert "id" in m
|
|
assert "name" in m
|
|
|
|
|
|
class TestGetYears:
|
|
def test_returns_list(self, db):
|
|
years = db.get_years()
|
|
assert isinstance(years, list)
|
|
assert len(years) > 0
|
|
|
|
def test_filter_by_brand(self, db):
|
|
years = db.get_years(brand="TOYOTA")
|
|
assert isinstance(years, list)
|
|
assert len(years) > 0
|
|
|
|
def test_each_year_has_id_and_year(self, db):
|
|
years = db.get_years()
|
|
for y in years[:5]:
|
|
assert "id" in y
|
|
assert "year" in y
|
|
|
|
|
|
class TestGetEngines:
|
|
def test_returns_list(self, db):
|
|
engines = db.get_engines()
|
|
assert isinstance(engines, list)
|
|
assert len(engines) > 0
|
|
|
|
def test_filter_by_brand(self, db):
|
|
engines = db.get_engines(brand="TOYOTA")
|
|
assert isinstance(engines, list)
|
|
assert len(engines) > 0
|
|
|
|
|
|
class TestGetModelYearEngine:
|
|
def test_returns_list(self, db):
|
|
result = db.get_model_year_engine(
|
|
brand="TOYOTA", model="Corolla", year=2020, engine_id=None
|
|
)
|
|
assert isinstance(result, list)
|
|
|
|
|
|
# =========================================================================
|
|
# Parts catalog
|
|
# =========================================================================
|
|
|
|
class TestGetCategories:
|
|
def test_returns_exactly_12(self, db):
|
|
categories = db.get_categories()
|
|
assert isinstance(categories, list)
|
|
assert len(categories) == 12
|
|
|
|
def test_each_has_expected_keys(self, db):
|
|
categories = db.get_categories()
|
|
for c in categories:
|
|
assert "id" in c
|
|
assert "name" in c
|
|
|
|
|
|
class TestGetGroups:
|
|
def test_returns_nonempty_for_known_category(self, db):
|
|
groups = db.get_groups(category_id=2)
|
|
assert isinstance(groups, list)
|
|
assert len(groups) > 0
|
|
|
|
def test_each_group_has_name(self, db):
|
|
groups = db.get_groups(category_id=2)
|
|
for g in groups:
|
|
assert "name" in g
|
|
|
|
|
|
class TestGetParts:
|
|
def test_returns_list(self, db):
|
|
parts = db.get_parts()
|
|
assert isinstance(parts, list)
|
|
assert len(parts) > 0
|
|
|
|
def test_pagination(self, db):
|
|
page1 = db.get_parts(page=1, per_page=5)
|
|
page2 = db.get_parts(page=2, per_page=5)
|
|
assert len(page1) <= 5
|
|
assert len(page2) <= 5
|
|
# Pages should contain different items (if enough data)
|
|
if page1 and page2:
|
|
ids1 = {p["id"] for p in page1}
|
|
ids2 = {p["id"] for p in page2}
|
|
assert ids1.isdisjoint(ids2)
|
|
|
|
|
|
class TestGetPart:
|
|
def test_returns_dict_with_oem_part_number(self, db):
|
|
part = db.get_part(1)
|
|
assert isinstance(part, dict)
|
|
assert "oem_part_number" in part
|
|
|
|
def test_includes_group_and_category_info(self, db):
|
|
part = db.get_part(1)
|
|
assert "group_name" in part
|
|
assert "category_name" in part
|
|
|
|
def test_nonexistent_returns_none(self, db):
|
|
part = db.get_part(999999)
|
|
assert part is None
|
|
|
|
|
|
class TestGetAlternatives:
|
|
def test_returns_list(self, db):
|
|
alts = db.get_alternatives(1)
|
|
assert isinstance(alts, list)
|
|
|
|
|
|
class TestGetCrossReferences:
|
|
def test_returns_list(self, db):
|
|
refs = db.get_cross_references(1)
|
|
assert isinstance(refs, list)
|
|
|
|
|
|
class TestGetVehiclesForPart:
|
|
def test_returns_list(self, db):
|
|
vehicles = db.get_vehicles_for_part(1)
|
|
assert isinstance(vehicles, list)
|
|
assert len(vehicles) > 0
|
|
|
|
|
|
# =========================================================================
|
|
# Search
|
|
# =========================================================================
|
|
|
|
class TestSearchParts:
|
|
def test_returns_results_for_brake(self, db):
|
|
results = db.search_parts("brake")
|
|
assert isinstance(results, list)
|
|
assert len(results) > 0
|
|
|
|
def test_each_result_has_expected_keys(self, db):
|
|
results = db.search_parts("brake")
|
|
for r in results[:3]:
|
|
assert "id" in r
|
|
assert "name" in r
|
|
assert "oem_part_number" in r
|
|
|
|
|
|
class TestSearchPartNumber:
|
|
def test_returns_results_for_04465(self, db):
|
|
results = db.search_part_number("04465")
|
|
assert isinstance(results, list)
|
|
assert len(results) > 0
|
|
|
|
def test_each_result_has_match_type(self, db):
|
|
results = db.search_part_number("04465")
|
|
for r in results:
|
|
assert "match_type" in r
|
|
|
|
|
|
# =========================================================================
|
|
# VIN cache
|
|
# =========================================================================
|
|
|
|
class TestVinCache:
|
|
def test_get_nonexistent_vin_returns_none(self, db):
|
|
result = db.get_vin_cache("00000000000000000")
|
|
assert result is None
|
|
|
|
|
|
# =========================================================================
|
|
# Stats
|
|
# =========================================================================
|
|
|
|
class TestGetStats:
|
|
def test_returns_dict_with_required_keys(self, db):
|
|
stats = db.get_stats()
|
|
assert isinstance(stats, dict)
|
|
assert "brands" in stats
|
|
assert "models" in stats
|
|
assert "parts" in stats
|
|
|
|
def test_counts_are_positive(self, db):
|
|
stats = db.get_stats()
|
|
assert stats["brands"] > 0
|
|
assert stats["models"] > 0
|
|
assert stats["parts"] > 0
|
|
|
|
def test_includes_top_brands(self, db):
|
|
stats = db.get_stats()
|
|
assert "top_brands" in stats
|
|
assert isinstance(stats["top_brands"], list)
|
|
|
|
|
|
# =========================================================================
|
|
# Manufacturers
|
|
# =========================================================================
|
|
|
|
class TestGetManufacturers:
|
|
def test_returns_nonempty_list(self, db):
|
|
manufacturers = db.get_manufacturers()
|
|
assert isinstance(manufacturers, list)
|
|
assert len(manufacturers) > 0
|
|
|
|
def test_each_has_name(self, db):
|
|
manufacturers = db.get_manufacturers()
|
|
for m in manufacturers:
|
|
assert "name" in m
|
|
assert "id" in m
|
|
|
|
|
|
# =========================================================================
|
|
# Admin CRUD — smoke tests
|
|
# =========================================================================
|
|
|
|
class TestCrossrefsPaginated:
|
|
def test_returns_list(self, db):
|
|
refs = db.get_crossrefs_paginated(page=1, per_page=5)
|
|
assert isinstance(refs, list)
|
|
assert len(refs) <= 5
|