elemes/services/tests/conftest.py

135 lines
4.2 KiB
Python

"""
Shared fixtures untuk suite services.
CONTENT_DIR di-set di import-time conftest, SEBELUM `app`/`lesson_service`
di-import, supaya test menunjuk data uji terpisah — bukan content produksi.
Token & progress siswa kini dikelola sepenuhnya di PostgreSQL (backend CSV
sudah dicabut); konstanta token uji di bawah tetap dipakai untuk seeding
via repositories di test integrasi.
"""
import os
import pathlib
import shutil
import pytest
_TEST_ROOT = "/tmp/lms-contract"
_TEST_CONTENT = f"{_TEST_ROOT}/content"
TEACHER_TOKEN = "TOKEN_GURU_001"
STUDENT_TOKEN = "TOKEN_SISWA_001"
STUDENT2_TOKEN = "TOKEN_SISWA_002"
pathlib.Path(_TEST_CONTENT).mkdir(parents=True, exist_ok=True)
# PAKSA path fixture (bukan setdefault) — env container (.env) membawa
# CONTENT_DIR produksi ("content") yang membuat lesson tests membaca data asli.
os.environ["CONTENT_DIR"] = _TEST_CONTENT
os.environ["ORIGIN"] = "*"
@pytest.fixture()
def app():
from app import create_app
application = create_app()
application.config["TESTING"] = True
return application
@pytest.fixture()
def client(app):
return app.test_client()
@pytest.fixture()
def seed_demo_users():
"""Reset + seed data uji route auth/progress: 1 guru, 2 siswa, 1 lesson.
Opt-in: modul yang butuh data ini meminta fixture via wrapper autouse.
"""
from sqlalchemy import text
from services import repositories as repo
from services.database import SessionLocal
from services.models import Lesson
if SessionLocal is None:
pytest.skip("butuh PostgreSQL nyata")
db = SessionLocal()
try:
db.execute(
text(
"TRUNCATE quiz_attempts, student_progress, access_tokens, lessons, users "
"RESTART IDENTITY CASCADE"
)
)
teacher = repo.create_user(db, display_name="Pak Guru", role="teacher")
repo.create_access_token(db, user_id=teacher.id, raw_token=TEACHER_TOKEN)
budi = repo.create_user(db, display_name="Budi Santoso", role="student")
repo.create_access_token(db, user_id=budi.id, raw_token=STUDENT_TOKEN)
siti = repo.create_user(db, display_name="Siti Aminah", role="student")
repo.create_access_token(db, user_id=siti.id, raw_token=STUDENT2_TOKEN)
db.add(Lesson(slug="hello_world", title="Hello World", order_index=0))
db.commit()
finally:
db.close()
@pytest.fixture(autouse=True)
def _isolate_filesystem():
"""Bersihkan CONTENT_DIR/ASSETS_DIR test sebelum tiap test — filesystem
harus se-isolated database, supaya suite idempotent lintas run.
Fixture ini autouse dan berjalan untuk SEMUA test (termasuk unit test tanpa
DB). Unit test yang tidak menyentuh filesystem tidak terpengaruh; test
integrasi yang menulis ke CONTENT_DIR kini selalu dapat state yang bersih.
"""
content_dir = pathlib.Path(_TEST_CONTENT)
assets_dir = content_dir.parent / "assets"
for d in (content_dir, assets_dir):
if d.exists():
shutil.rmtree(d)
d.mkdir(parents=True, exist_ok=True)
yield
@pytest.fixture(autouse=True)
def _isolate_database(request):
"""Isolasi integration test: truncate semua tabel sebelum SETIAP test.
Test importer/lesson-registry/progress berbagi DATABASE_URL yang sama;
tanpa reset, data sisa antar test saling mengotori (mis. total lessons
bertambah). Contract test (backend CSV) tidak terpengaruh — SessionLocal
None bila DATABASE_URL tidak diset.
Test bertanda `unit` tidak pernah menyentuh DB — lewati isolasi sepenuhnya
supaya `make test-unit` tetap jalan cepat & tanpa DB walau DATABASE_URL
diset di environment (atau server PostgreSQL sedang mati).
"""
if request.node.get_closest_marker("unit") is not None:
yield
return
from sqlalchemy import text
from services.database import SessionLocal
if SessionLocal is None:
yield
return
db = SessionLocal()
try:
db.execute(
text(
"TRUNCATE quiz_attempts, student_progress, access_tokens, lessons, users "
"RESTART IDENTITY CASCADE"
)
)
db.commit()
finally:
db.close()
yield