2026-08-08 11:57:08 +07:00
|
|
|
"""
|
|
|
|
|
Shared fixtures untuk suite services.
|
|
|
|
|
|
feat(teacher): manajemen akun guru canonical + first-run otomatis; cabut backend CSV (PG only)
- feat: ./elemes.sh teacher — upsert satu akun guru canonical (prompt nama default TEACHER_NAME, token tersembunyi via stdin, rotasi token saat guru sudah ada, idempotent); TEACHER_NAME/TEACHER_TOKEN di .env
- feat: first-run otomatis — db_init jalankan alembic upgrade head + bootstrap guru bila TEACHER_TOKEN terisi (run/runbuild/runclearbuild)
- refactor: backend CSV dicabut penuh — hapus mount tokens_siswa.csv, TOKENS_FILE, STORAGE_BACKEND=csv, csv_backend.py, csv_importer.py, generate_tokens.py, script migrate/verify/export CSV, command generatetoken/dbimport/dbverify/dbexport/synclessons; storage fail-loud postgresql-only
- fix: dbrestore gagal diam-diam saat restore ke DB berisi data (dump tanpa --clean) — dbbackup kini --clean --if-exists + dbrestore reset schema public; roundtrip terverifikasi
- test: suite kontrak PG-native + route auth/progress integrasi PG; 183 passed container, 96 passed host, frontend 91 passed
- docs: README, documentation.md, docs/01/02/11, load-test/README disinkronkan ke realita PostgreSQL-only; proposal.md dipertahankan (historis)
- chore: load-test token sintetis di-seed ke PG (content_parser), config.py bersih TOKENS_FILE, .dockerignore/.gitignore dibersihkan
2026-08-09 11:21:08 +07:00
|
|
|
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.
|
2026-08-08 11:57:08 +07:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import pathlib
|
|
|
|
|
|
|
|
|
|
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"
|
|
|
|
|
|
feat(teacher): manajemen akun guru canonical + first-run otomatis; cabut backend CSV (PG only)
- feat: ./elemes.sh teacher — upsert satu akun guru canonical (prompt nama default TEACHER_NAME, token tersembunyi via stdin, rotasi token saat guru sudah ada, idempotent); TEACHER_NAME/TEACHER_TOKEN di .env
- feat: first-run otomatis — db_init jalankan alembic upgrade head + bootstrap guru bila TEACHER_TOKEN terisi (run/runbuild/runclearbuild)
- refactor: backend CSV dicabut penuh — hapus mount tokens_siswa.csv, TOKENS_FILE, STORAGE_BACKEND=csv, csv_backend.py, csv_importer.py, generate_tokens.py, script migrate/verify/export CSV, command generatetoken/dbimport/dbverify/dbexport/synclessons; storage fail-loud postgresql-only
- fix: dbrestore gagal diam-diam saat restore ke DB berisi data (dump tanpa --clean) — dbbackup kini --clean --if-exists + dbrestore reset schema public; roundtrip terverifikasi
- test: suite kontrak PG-native + route auth/progress integrasi PG; 183 passed container, 96 passed host, frontend 91 passed
- docs: README, documentation.md, docs/01/02/11, load-test/README disinkronkan ke realita PostgreSQL-only; proposal.md dipertahankan (historis)
- chore: load-test token sintetis di-seed ke PG (content_parser), config.py bersih TOKENS_FILE, .dockerignore/.gitignore dibersihkan
2026-08-09 11:21:08 +07:00
|
|
|
pathlib.Path(_TEST_CONTENT).mkdir(parents=True, exist_ok=True)
|
2026-08-08 11:57:08 +07:00
|
|
|
|
|
|
|
|
# PAKSA path fixture (bukan setdefault) — env container (.env) membawa
|
feat(teacher): manajemen akun guru canonical + first-run otomatis; cabut backend CSV (PG only)
- feat: ./elemes.sh teacher — upsert satu akun guru canonical (prompt nama default TEACHER_NAME, token tersembunyi via stdin, rotasi token saat guru sudah ada, idempotent); TEACHER_NAME/TEACHER_TOKEN di .env
- feat: first-run otomatis — db_init jalankan alembic upgrade head + bootstrap guru bila TEACHER_TOKEN terisi (run/runbuild/runclearbuild)
- refactor: backend CSV dicabut penuh — hapus mount tokens_siswa.csv, TOKENS_FILE, STORAGE_BACKEND=csv, csv_backend.py, csv_importer.py, generate_tokens.py, script migrate/verify/export CSV, command generatetoken/dbimport/dbverify/dbexport/synclessons; storage fail-loud postgresql-only
- fix: dbrestore gagal diam-diam saat restore ke DB berisi data (dump tanpa --clean) — dbbackup kini --clean --if-exists + dbrestore reset schema public; roundtrip terverifikasi
- test: suite kontrak PG-native + route auth/progress integrasi PG; 183 passed container, 96 passed host, frontend 91 passed
- docs: README, documentation.md, docs/01/02/11, load-test/README disinkronkan ke realita PostgreSQL-only; proposal.md dipertahankan (historis)
- chore: load-test token sintetis di-seed ke PG (content_parser), config.py bersih TOKENS_FILE, .dockerignore/.gitignore dibersihkan
2026-08-09 11:21:08 +07:00
|
|
|
# CONTENT_DIR produksi ("content") yang membuat lesson tests membaca data asli.
|
2026-08-08 11:57:08 +07:00
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
|
feat(teacher): manajemen akun guru canonical + first-run otomatis; cabut backend CSV (PG only)
- feat: ./elemes.sh teacher — upsert satu akun guru canonical (prompt nama default TEACHER_NAME, token tersembunyi via stdin, rotasi token saat guru sudah ada, idempotent); TEACHER_NAME/TEACHER_TOKEN di .env
- feat: first-run otomatis — db_init jalankan alembic upgrade head + bootstrap guru bila TEACHER_TOKEN terisi (run/runbuild/runclearbuild)
- refactor: backend CSV dicabut penuh — hapus mount tokens_siswa.csv, TOKENS_FILE, STORAGE_BACKEND=csv, csv_backend.py, csv_importer.py, generate_tokens.py, script migrate/verify/export CSV, command generatetoken/dbimport/dbverify/dbexport/synclessons; storage fail-loud postgresql-only
- fix: dbrestore gagal diam-diam saat restore ke DB berisi data (dump tanpa --clean) — dbbackup kini --clean --if-exists + dbrestore reset schema public; roundtrip terverifikasi
- test: suite kontrak PG-native + route auth/progress integrasi PG; 183 passed container, 96 passed host, frontend 91 passed
- docs: README, documentation.md, docs/01/02/11, load-test/README disinkronkan ke realita PostgreSQL-only; proposal.md dipertahankan (historis)
- chore: load-test token sintetis di-seed ke PG (content_parser), config.py bersih TOKENS_FILE, .dockerignore/.gitignore dibersihkan
2026-08-09 11:21:08 +07:00
|
|
|
@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 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()
|
|
|
|
|
|
|
|
|
|
|
2026-08-08 11:57:08 +07:00
|
|
|
@pytest.fixture(autouse=True)
|
|
|
|
|
def _isolate_database():
|
|
|
|
|
"""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
|
feat(teacher): manajemen akun guru canonical + first-run otomatis; cabut backend CSV (PG only)
- feat: ./elemes.sh teacher — upsert satu akun guru canonical (prompt nama default TEACHER_NAME, token tersembunyi via stdin, rotasi token saat guru sudah ada, idempotent); TEACHER_NAME/TEACHER_TOKEN di .env
- feat: first-run otomatis — db_init jalankan alembic upgrade head + bootstrap guru bila TEACHER_TOKEN terisi (run/runbuild/runclearbuild)
- refactor: backend CSV dicabut penuh — hapus mount tokens_siswa.csv, TOKENS_FILE, STORAGE_BACKEND=csv, csv_backend.py, csv_importer.py, generate_tokens.py, script migrate/verify/export CSV, command generatetoken/dbimport/dbverify/dbexport/synclessons; storage fail-loud postgresql-only
- fix: dbrestore gagal diam-diam saat restore ke DB berisi data (dump tanpa --clean) — dbbackup kini --clean --if-exists + dbrestore reset schema public; roundtrip terverifikasi
- test: suite kontrak PG-native + route auth/progress integrasi PG; 183 passed container, 96 passed host, frontend 91 passed
- docs: README, documentation.md, docs/01/02/11, load-test/README disinkronkan ke realita PostgreSQL-only; proposal.md dipertahankan (historis)
- chore: load-test token sintetis di-seed ke PG (content_parser), config.py bersih TOKENS_FILE, .dockerignore/.gitignore dibersihkan
2026-08-09 11:21:08 +07:00
|
|
|
bertambah). Contract test (backend CSV) tidak terpengaruh — SessionLocal
|
2026-08-08 11:57:08 +07:00
|
|
|
None bila DATABASE_URL tidak diset.
|
|
|
|
|
"""
|
|
|
|
|
from sqlalchemy import text
|
|
|
|
|
|
|
|
|
|
from services.database import SessionLocal
|
|
|
|
|
|
|
|
|
|
if SessionLocal is None:
|
|
|
|
|
yield
|
|
|
|
|
return
|
|
|
|
|
db = SessionLocal()
|
|
|
|
|
try:
|
|
|
|
|
db.execute(
|
|
|
|
|
text(
|
|
|
|
|
"TRUNCATE student_progress, access_tokens, lessons, users "
|
|
|
|
|
"RESTART IDENTITY CASCADE"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
db.commit()
|
|
|
|
|
finally:
|
|
|
|
|
db.close()
|
|
|
|
|
yield
|