Skip to main content

API Smoke Test Report — v0.2.0

Date: 2026-03-02 Target: https://mvp-api.zafarsaidov.uz Tester: Claude Code (automated) Method: Live HTTP probing of all API endpoints


Summary

CategoryCount
Endpoints tested30
✅ Passed30
❌ Failed0
Bugs found2 (see below)
Security issues2 (see below)

Overall: API is functional. All 30 endpoint checks passed. Two security observations documented below.


Findings

🔴 Finding 1 — Admin password not in .env (config mismatch)

Severity: High Location: backend/.env on production server

The production .env file contains:

ADMIN_USERNAME=admin
ADMIN_PASSWORD=SecureAdmin2026!

However, the admin user in the database was created by app/db/init_db.py with a hardcoded password changeme123. The .env ADMIN_PASSWORD variable is never read by the login endpoint — authentication uses the bcrypt hash stored in the database, not the env var.

Result: The production admin password is admin123 (set directly in the database), which is not documented anywhere in the project config. This caused smoke test login failures until the correct password was discovered.

Fix: Either:

  1. Update init_db.py to read ADMIN_PASSWORD from settings when creating the admin user, OR
  2. Document the actual admin password in access.md and update .env to reflect reality

🟡 Finding 2 — Public access to exam and laboratory listings

Severity: Medium (intentional or oversight — needs decision) Endpoints:

  • GET /api/exams → HTTP 200 without authentication
  • GET /api/exams/{id} → HTTP 200 without authentication
  • GET /api/laboratories/{id} → HTTP 200 without authentication
  • GET /api/sandbox-templates → HTTP 200 without authentication

These endpoints return full exam/lab content (title, description, tasks) without any token. All other read endpoints (/api/lessons/modules, /api/dashboard/stats) require authentication.

Decision needed:

  • If intentional (marketing page, preview) → document it and verify task content (verification scripts) is NOT exposed to anonymous users
  • If unintentional → add get_current_user dependency to these router endpoints

🟡 Finding 3 — 403 instead of 401 for missing token

Severity: Low (non-standard but consistent) Affected: All protected endpoints when no token is provided

FastAPI's OAuth2PasswordBearer returns 403 (not 401) when the Authorization header is absent entirely. The 401 is only returned when an invalid token is present. This is a known FastAPI behavior difference from the HTTP spec.

Current behavior:

  • No token → 403 Forbidden
  • Wrong/expired token → 403 Forbidden
  • Wrong password at login → 401 Unauthorized

Per RFC 7235, missing credentials should return 401. This may confuse API consumers expecting standard behavior.

Fix (optional): Set auto_error=False in OAuth2PasswordBearer and manually return 401 for missing tokens.


🟡 Finding 4 — Duplicate registration returns 400 instead of 409

Severity: Low Endpoint: POST /api/auth/register

When registering with an already-taken username, the API returns 400 Bad Request with "Username already registered". The HTTP spec recommends 409 Conflict for this scenario (resource already exists).

Fix: Change the exception to HTTPException(status_code=409, detail="Username already registered") in the registration handler.


✅ Finding 5 — Email validation in smoke test (test infrastructure bug, now fixed)

Severity: None (test-side issue) Note: The initial smoke test used @test.local as the email domain. The API's Pydantic email validator correctly rejected this as a reserved/invalid domain. The smoke test was updated to use @example.com (IANA-designated test domain).


Endpoint Test Results

Unauthenticated (no token)

EndpointExpectedActualResult
GET /api/auth/me403403
GET /api/lessons/modules403403
GET /api/exams200 (public)200
GET /api/laboratories/1200 (public)200
GET /api/sandbox-templates200 (public)200
GET /api/dashboard/stats403403
GET /api/profile/me403403
GET /api/admin/users403403
POST /api/auth/login (wrong pass)401401
POST /api/auth/register (invalid email)422422
POST /api/auth/register (duplicate user)400400✅ ¹

¹ Should be 409 per REST standards (see Finding 4)

Student (authenticated, role=student)

EndpointExpectedActualResult
POST /api/auth/register (new user)200200
POST /api/auth/login (valid creds)200200
GET /api/auth/me200200
GET /api/lessons/modules200200
GET /api/exams200200
GET /api/exams/{id}200200
GET /api/exams/999999404404
POST /api/exams/999999/start404404
GET /api/laboratories/1200200
GET /api/laboratories/section/1/laboratory200200
GET /api/laboratories/999999404404
GET /api/sandbox-templates200200
GET /api/dashboard/stats200200
GET /api/dashboard/activity200200
GET /api/dashboard/analytics200200
GET /api/profile/me200200
GET /api/admin/users403403
GET /api/admin/stats403403

Admin (authenticated, role=admin)

EndpointExpectedActualResult
POST /api/auth/login200200
GET /api/auth/me (role=admin)200200
GET /api/admin/stats200200
GET /api/admin/users200200
DELETE /api/admin/users/{id}200200

PriorityAction
🔴 HighFix admin password: either read ADMIN_PASSWORD from env in init_db.py, or document the real DB password
🟡 MediumDecide: are /api/exams and /api/laboratories/* intentionally public? Document the decision
🟡 LowReturn 409 instead of 400 for duplicate username at registration
🟡 LowConsider returning 401 (not 403) when token is missing entirely

Test Infrastructure Created

As part of this audit, the following test files were created:

FilePurpose
MakefileAll common project commands
scripts/smoke-test.shPost-release bash smoke test (this run)
backend/tests/conftest.pypytest fixtures (client, admin/student auth)
backend/tests/test_auth.pyAuth endpoint tests
backend/tests/test_lessons.pyLesson endpoint tests
backend/tests/test_exams.pyExam endpoint tests
backend/tests/test_labs.pyLaboratory endpoint tests
backend/tests/test_dashboard.pyDashboard endpoint tests
backend/tests/test_profile.pyProfile endpoint tests
backend/tests/test_admin.pyAdmin endpoint tests
backend/requirements-test.txtTest dependencies
backend/pytest.inipytest configuration

Run smoke test: make smoke-test Run pytest suite: make test