From 16031fa72842f243001db310076ce7eabbc0b8b2 Mon Sep 17 00:00:00 2001 From: a2nr Date: Mon, 17 Aug 2026 16:22:49 +0700 Subject: [PATCH] fix(docs/api): hilangkan sync berulang di api_reference + UI API Reference di /docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - routes/docs.py: pakai current_app, bukan create_app() per request — tidak lagi memicu DB sync ulang di setiap hit /docs/api-reference (dead code route_modules/rule_map dihapus) - frontend: entry statis API Reference di sidebar /docs, fetch lazy /api/docs/api-reference, render method badge + auth + docstring - test: 4 test yatim di test_teacher_bootstrap diberi marker unit; Makefile test-stats kini menampilkan count per marker + orphan (harus 0) - docs_service: hapus dead code _read_md_cached, sederhanakan cache-check _parse_frontmatter (satu getmtime, kondisi jelas) - lesson_service: gabung pemanggilan bleach.clean dobel jadi satu dengan css_sanitizer opsional — hilangkan NoCssSanitizerWarning (7x) - conftest: fixture autouse _isolate_database lewati isolasi untuk test unit supaya make test-unit tetap jalan tanpa DB walau DATABASE_URL diset Verifikasi: unit 114 passed, full suite 239 passed (0 warnings) dengan Postgres test, svelte-check 0 error, vitest 114 passed, docs-validate ok. --- Makefile | 9 +- README.md | 10 +- frontend/src/lib/types/docs.ts | 13 ++ frontend/src/routes/docs/+page.svelte | 162 ++++++++++++++++++++++- routes/docs.py | 33 +---- services/docs_service.py | 38 +----- services/lesson_service.py | 25 ++-- services/tests/conftest.py | 10 +- services/tests/test_teacher_bootstrap.py | 5 + 9 files changed, 221 insertions(+), 84 deletions(-) diff --git a/Makefile b/Makefile index a7cd8ea..cdc2216 100644 --- a/Makefile +++ b/Makefile @@ -72,8 +72,13 @@ test-list: ## Show test counts by marker test-stats: @echo "=== Test counts by marker ===" - @$(PYTEST) --collect-only -q -m unit 2>/dev/null | grep -c "test" || echo "0" + @$(PYTEST) --collect-only -q -m unit 2>/dev/null | tail -1 @echo "^ unit tests" - @$(PYTEST) --collect-only -q -m integration 2>/dev/null | grep -c "test" || echo "0" + @$(PYTEST) --collect-only -q -m integration 2>/dev/null | tail -1 @echo "^ integration tests" + @$(PYTEST) --collect-only -q -m e2e 2>/dev/null | tail -1 + @echo "^ e2e tests" + @$(PYTEST) --collect-only -q -m "not unit and not integration and not e2e" 2>/dev/null | tail -1 + @echo "^ ORPHAN tests (tanpa marker) — harus 0, unit + integration == total" @$(PYTEST) --collect-only -q 2>/dev/null | tail -1 + @echo "^ total collected" diff --git a/README.md b/README.md index c474739..a70b9d2 100644 --- a/README.md +++ b/README.md @@ -493,14 +493,20 @@ student_id;token;nama_siswa;hello_world;variabel || `./elemes.sh test` | Full test suite (alias ke `test-all`) | || `./elemes.sh test-unit` | Unit test saja (cepat, no DB) | || `./elemes.sh test-integration` | Integration test (butuh PostgreSQL `elemes_test`) | -|| `./elemes.sh test-all` | Full test suite (CI gate) | +|| `./elemes.sh test-all` | Full test suite | || `./elemes.sh test-smoke` | Smoke test post-deploy (unit + sub-home subset) | || `./elemes.sh docs-validate` | Validasi frontmatter & broken link di `docs/*.md` | +> **Catatan testing (CI):** CI otomatis (GitHub Actions) **belum diaktifkan** — +> solo dev, test dijalankan manual sebelum push via +> `make test-unit && make test-integration` (atau `./elemes.sh test-all`). +> Rencana diaktifkan lagi saat ada kontributor lain; file `ci.yml` versi lama +> tetap tersimpan di git history (`git show a5aedf6:.github/workflows/ci.yml`). + ## Dokumentasi & Referensi API - **Docs Viewer**: Buka `http://localhost:3000/docs` untuk panduan teknis lengkap (arsitektur, backend, frontend, kuis, velxio, embed, dll) yang merender file `docs/*.md` secara dinamis. -- **API Reference**: `http://localhost:3000/docs/api-reference` menampilkan daftar semua endpoint Flask dengan docstring, metode, path, dan requirement auth. +- **API Reference**: Buka `/docs` lalu klik **API Reference** di sidebar (entry pertama) untuk melihat daftar semua endpoint Flask dengan docstring, metode, path, dan requirement auth. - **Troubleshooting**: Buka `http://localhost:3000/help` untuk tutorial siswa; tautan ke Docs Viewer ada di sana. ## Database & Penyimpanan (PostgreSQL) diff --git a/frontend/src/lib/types/docs.ts b/frontend/src/lib/types/docs.ts index f87da8b..25a6e6f 100644 --- a/frontend/src/lib/types/docs.ts +++ b/frontend/src/lib/types/docs.ts @@ -17,3 +17,16 @@ export interface DocMeta { order: number; category: string; } + +/** Satu entry dari GET /api/docs/api-reference. */ +export interface ApiReferenceEntry { + method: string[]; + path: string; + name: string; + auth: boolean; + doc: string; +} + +export interface ApiReferenceResponse { + endpoints: ApiReferenceEntry[]; +} diff --git a/frontend/src/routes/docs/+page.svelte b/frontend/src/routes/docs/+page.svelte index 665a26e..38bbad8 100644 --- a/frontend/src/routes/docs/+page.svelte +++ b/frontend/src/routes/docs/+page.svelte @@ -2,6 +2,7 @@ import { env } from '$env/dynamic/public'; import { renderMath } from '$lib/actions/renderMath'; import { tick } from 'svelte'; + import type { ApiReferenceEntry } from '$types/docs'; export let data: { docs: import('$types/docs').DocsIndexEntry[] @@ -12,11 +13,16 @@ let docContent: { title: string; html: string } | null = null; let docError = false; + let apiRefMode = false; + let apiRefEntries: ApiReferenceEntry[] | null = null; + let apiRefError = false; + $: filteredDocs = data.docs.filter(d => d.title.toLowerCase().includes(searchQuery.toLowerCase()) ); async function selectDoc(doc: import('$types/docs').DocsIndexEntry) { + apiRefMode = false; selectedDoc = doc; searchQuery = ''; docError = false; @@ -30,10 +36,29 @@ docError = true; } } + + async function selectApiReference() { + apiRefMode = true; + selectedDoc = data.docs[0]; + searchQuery = ''; + docError = false; + docContent = null; + + if (apiRefEntries) return; // sudah pernah dimuat — reuse + apiRefError = false; + try { + const res = await fetch('/api/docs/api-reference'); + if (!res.ok) throw new Error('Failed to load API reference'); + const payload = await res.json(); + apiRefEntries = payload.endpoints; + } catch { + apiRefError = true; + } + } - {selectedDoc?.title || 'Dokumentasi'} - Elemes LMS + {apiRefMode ? 'API Reference' : selectedDoc?.title || 'Dokumentasi'} - Elemes LMS
@@ -47,9 +72,18 @@ />