- Add SQLITE_DB_PATH env var to db_engine.py for test isolation - Create tests/conftest.py with in-memory SQLite fixture and sample data helpers - Add tests/test_dependencies.py: unit tests for valid_token, mround, param_char, get_req_url - Add tests/test_card_pricing.py: tests for Player.change_on_sell/buy and get_all_pos - Add tests/test_api_packs.py: integration tests for GET/POST/DELETE /api/v2/packs - Add requirements-test.txt with pytest and httpx - Add test job to CI workflow (build now requires tests to pass first) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
131 lines
4.7 KiB
Python
131 lines
4.7 KiB
Python
"""
|
|
Integration tests for the /api/v2/packs endpoints.
|
|
|
|
Uses FastAPI's TestClient (starlette) with an isolated SQLite test database.
|
|
The db_transaction fixture in conftest.py rolls back all changes after each test.
|
|
"""
|
|
|
|
import pytest
|
|
from starlette.testclient import TestClient
|
|
|
|
from app.main import app
|
|
|
|
TEST_TOKEN = "test_token_12345"
|
|
AUTH_HEADERS = {"Authorization": f"Bearer {TEST_TOKEN}"}
|
|
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
"""Return a TestClient for the FastAPI app."""
|
|
return TestClient(app, raise_server_exceptions=True)
|
|
|
|
|
|
class TestGetPacks:
|
|
"""Tests for GET /api/v2/packs."""
|
|
|
|
def test_empty_database_returns_404(self, client):
|
|
"""With no packs in the DB, the endpoint should return 404."""
|
|
resp = client.get("/api/v2/packs")
|
|
assert resp.status_code == 404
|
|
|
|
def test_returns_pack_list(self, client, sample_pack):
|
|
"""With at least one pack, GET /packs returns count and list."""
|
|
resp = client.get("/api/v2/packs")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "count" in data
|
|
assert data["count"] >= 1
|
|
assert "packs" in data
|
|
assert len(data["packs"]) >= 1
|
|
|
|
def test_filter_by_team_id(self, client, sample_pack, sample_team):
|
|
"""team_id filter should return only packs for that team."""
|
|
resp = client.get(f"/api/v2/packs?team_id={sample_team.id}")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
for pack in data["packs"]:
|
|
assert pack["team"]["id"] == sample_team.id
|
|
|
|
def test_filter_by_invalid_team_returns_404(self, client, sample_pack):
|
|
"""Filtering by a non-existent team_id should return 404."""
|
|
resp = client.get("/api/v2/packs?team_id=999999")
|
|
assert resp.status_code == 404
|
|
|
|
def test_filter_opened_false(self, client, sample_pack):
|
|
"""opened=false should return only packs with no open_time."""
|
|
resp = client.get("/api/v2/packs?opened=false")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
for pack in data["packs"]:
|
|
assert pack["open_time"] is None
|
|
|
|
|
|
class TestGetOnePack:
|
|
"""Tests for GET /api/v2/packs/{pack_id}."""
|
|
|
|
def test_get_existing_pack(self, client, sample_pack):
|
|
"""GET with a valid pack_id should return that pack's data."""
|
|
resp = client.get(f"/api/v2/packs/{sample_pack.id}")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["id"] == sample_pack.id
|
|
|
|
def test_get_nonexistent_pack_returns_404(self, client):
|
|
"""GET with an unknown pack_id should return 404."""
|
|
resp = client.get("/api/v2/packs/999999")
|
|
assert resp.status_code == 404
|
|
|
|
|
|
class TestPostPack:
|
|
"""Tests for POST /api/v2/packs — requires auth token."""
|
|
|
|
def test_post_without_token_returns_401(
|
|
self, client, sample_team, sample_pack_type
|
|
):
|
|
"""POST without Authorization header should return 422 (missing field)."""
|
|
payload = {
|
|
"packs": [{"team_id": sample_team.id, "pack_type_id": sample_pack_type.id}]
|
|
}
|
|
resp = client.post("/api/v2/packs", json=payload)
|
|
# FastAPI returns 422 when required OAuth2 token is missing
|
|
assert resp.status_code == 422
|
|
|
|
def test_post_with_invalid_token_returns_401(
|
|
self, client, sample_team, sample_pack_type
|
|
):
|
|
"""POST with a wrong token should return 401."""
|
|
payload = {
|
|
"packs": [{"team_id": sample_team.id, "pack_type_id": sample_pack_type.id}]
|
|
}
|
|
resp = client.post(
|
|
"/api/v2/packs",
|
|
json=payload,
|
|
headers={"Authorization": "Bearer wrong_token"},
|
|
)
|
|
assert resp.status_code == 401
|
|
|
|
def test_post_with_valid_token_creates_packs(
|
|
self, client, sample_team, sample_pack_type
|
|
):
|
|
"""POST with valid token and payload should create the packs (returns 200)."""
|
|
payload = {
|
|
"packs": [{"team_id": sample_team.id, "pack_type_id": sample_pack_type.id}]
|
|
}
|
|
resp = client.post("/api/v2/packs", json=payload, headers=AUTH_HEADERS)
|
|
# The router raises HTTPException(200) on success
|
|
assert resp.status_code == 200
|
|
|
|
|
|
class TestDeletePack:
|
|
"""Tests for DELETE /api/v2/packs/{pack_id} — requires auth token."""
|
|
|
|
def test_delete_nonexistent_pack_returns_404(self, client):
|
|
"""Deleting a pack that doesn't exist should return 404."""
|
|
resp = client.delete("/api/v2/packs/999999", headers=AUTH_HEADERS)
|
|
assert resp.status_code == 404
|
|
|
|
def test_delete_without_token_returns_401_or_422(self, client, sample_pack):
|
|
"""DELETE without a token should fail auth."""
|
|
resp = client.delete(f"/api/v2/packs/{sample_pack.id}")
|
|
assert resp.status_code in (401, 422)
|