Lompat ke konten
dokumentasi.id
Commit 32b2830
v1
Star0

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

10

Pengujian dan Pipeline CI

Pipeline CI repository ini berada di satu workflow, .github/workflows/ci.yml, yang berjalan pada push ke master, push tag v*, dan setiap pull request. Workflow itu memecah pekerjaan menjadi tiga job, yaitu backend, frontend, dan image, dan job image menunggu kedua job lain selesai. .github/workflows/ci.yml:1-8

graph TD
  trg["push master / tag v* / pull_request"] --> be["job backend"]
  trg --> fe["job frontend"]
  be --> lint["ruff check + ruff format --check"]
  lint --> mypy["mypy ."]
  mypy --> mig["makemigrations --check --dry-run"]
  mig --> cek["manage.py check"]
  cek --> pt["pytest --create-db"]
  pt --> pa["pip-audit -r requirements.txt"]
  pa --> skema["diff /tmp/schema.yaml schema.yaml"]
  fe --> tipe["npm run tipe-api + git diff api-types.ts"]
  tipe --> tc["typecheck + build"]
  tc --> na["npm audit --omit=dev"]
  skema --> img["job image, hanya tag v*"]
  na --> img
  img --> build["docker build hydro-api + hydro-web"]
  build --> tv["Trivy --severity CRITICAL"]
  tv --> ghcr["docker push ke GHCR"]
  style be fill:#f59e0b,color:#000
  style fe fill:#f59e0b,color:#000
  style pt fill:#2d7d46,color:#fff
  style skema fill:#2d7d46,color:#fff
  style tv fill:#dc2626,color:#fff
  style ghcr fill:#336791,color:#fff
  style img fill:#f59e0b,color:#000
%% fig: Tahap pipeline CI
%% desc: Dua job paralel memeriksa mutu backend dan frontend, lalu job image membangun image, memindainya dengan Trivy, dan mengirimnya ke GHCR hanya pada tag.
Fig. 1: Tahap pipeline CIDua job paralel memeriksa mutu backend dan frontend, lalu job image membangun image, memindainya dengan Trivy, dan mengirimnya ke GHCR hanya pada tag.

Job backend: database, lint, dan uji

Job backend berjalan di ubuntu-24.04 dengan working-directory: backend. Job itu menyediakan service db berbasis postgres:18.1 pada port 5433:5432, lengkap dengan health check pg_isready -U hydro, interval 5 detik, dan 20 kali retry, sehingga langkah uji tidak dimulai sebelum database siap. .github/workflows/ci.yml:13-27

UrutanPerintahKegagalan yang dicegah
1ruff check .pelanggaran lint
2ruff format --check .format berkas yang tidak sesuai
3mypy .tipe yang tidak cocok
4python manage.py makemigrations --check --dry-runperubahan model tanpa migration
5python manage.py checkkonfigurasi Django yang salah
6pytest --create-dbuji yang gagal
7pip-audit -r requirements.txtdependensi rentan
8spectacular --file /tmp/schema.yaml --fail-on-warn lalu diffschema.yaml usang

Seluruh langkah itu ditulis berurutan dalam job backend. .github/workflows/ci.yml:30-45

Python dipasang versi 3.13 dengan cache pip yang kuncinya backend/requirements-dev.txt. .github/workflows/ci.yml:32-36 Sebelum uji, workflow memasang libpango-1.0-0, libpangoft2-1.0-0, dan libharfbuzz-subset0 lewat apt-get, kemudian memasang requirements-dev.txt. .github/workflows/ci.yml:37-38 Paket Pango itu yang membuat pembuatan PDF bisa diuji di runner; bila pustaka tidak ada, uji PDF melewati dirinya sendiri seperti dibahas di bagian akhir halaman ini.

Langkah terakhir job backend membandingkan skema yang dihasilkan dengan berkas yang tersimpan. Perintah spectacular --file /tmp/schema.yaml --fail-on-warn menulis skema baru, lalu diff /tmp/schema.yaml schema.yaml menggagalkan pipeline bila keduanya berbeda. .github/workflows/ci.yml:46-49 Bentuk endpoint yang menjadi sumber skema itu dijelaskan di Endpoint API dan Skema OpenAPI.

Berkas requirements-dev.txt memuat pytest==8.*, pytest-django==4.14.0, ruff==0.17.3, mypy==2.4.1, django-stubs==6.1.2, djangorestframework-stubs==3.18.1, pip-audit, dan types-openpyxl, sehingga versi alat di lokal dan di CI berasal dari satu daftar. backend/requirements-dev.txt:1-10

Aturan lint dan pemeriksaan tipe

ruff dikonfigurasi dengan line-length = 100, target-version = "py313", dan pengecualian direktori migrations serta .venv. Aturan yang dipilih adalah E, F, I, B, UP, S, dan DJ, dengan S101, DJ001, dan DJ008 diabaikan; berkas di tests/* mendapat pengecualian tambahan S105, S106, dan S107 karena memakai sandi uji. backend/pyproject.toml:1-12

mypy berjalan pada python_version = "3.13" dengan plugin mypy_django_plugin.main dan mypy_drf_plugin.main. Direktori migrations, .venv, dan tests dikecualikan, dan kode galat attr-defined serta union-attr dimatikan karena kolom hasil annotate() dan objek openpyxl tidak terbaca oleh stub. backend/pyproject.toml:14-23

Konfigurasi pytest dan fixture peran

pytest.ini menetapkan DJANGO_SETTINGS_MODULE = config.settings, pola berkas test_*.py, dan addopts = -q --reuse-db. backend/pytest.ini:1-4 CI menambahkan --create-db pada pemanggilan pytest, jadi database uji selalu dibangun ulang di runner meskipun konfigurasi lokal memakai --reuse-db.

conftest.py menyediakan satu pintu masuk pembuatan pengguna. Fungsi buat_pengguna memanggil Peran.objects.get_or_create karena tes transactional mengosongkan tabel termasuk data awal dari migration, lalu menyimpan sandi sandi-uji-12345; bila peran yang diminta adalah PEMILIK, fungsi itu juga membuat Authenticator bertipe TOTP. backend/tests/conftest.py:17-28

FixturePeran atau isi
adminpengguna dengan ADMIN_PENJUALAN
gudangpengguna dengan STAF_GUDANG
pemilikpengguna dengan PEMILIK dan TOTP
klien_admin, klien_gudang, klien_pemilikAPIClient hasil force_login
pelanggan, produk, botol, dusdata dasar transaksi dan stok

Ketiga fixture peran dibangun di atas buat_pengguna. backend/tests/conftest.py:31-43 Klien uji dibuat oleh klien_untuk, yang memanggil force_login dengan django.contrib.auth.backends.ModelBackend, lalu dibungkus menjadi klien_admin, klien_gudang, dan klien_pemilik. backend/tests/conftest.py:46-64 Data pendukung lainnya adalah pelanggan bernama Toko Uji, helper stok_masuk yang memanggil catat_barang_masuk dengan kedaluwarsa bawaan 100 hari, produk berkode AMK-600, serta satuan botol dan dus. backend/tests/conftest.py:67-107

Matriks izin sebagai uji

Berkas test_izin.py memuat daftar MATRIKS berisi pasangan metode, url, dan himpunan peran yang boleh. Komentarnya menjelaskan trik yang dipakai: id 1 tidak ada, sehingga permintaan yang lolos izin berakhir 404 dan yang ditolak berakhir 403. backend/tests/test_izin.py:9-67

Endpointadmin_penjualanstaf_gudangpemilik
get /api/produk/bolehbolehboleh
post /api/produk/ditolakditolakboleh
get /api/barang-masuk/ditolakbolehboleh
post /api/opname/1/setujui/ditolakditolakboleh
get /api/transaksi/bolehditolakboleh
get /api/laporan/penjualan/ditolakditolakboleh
get /api/dasbor/bolehbolehboleh

Fixture klien_peran membuat satu klien untuk setiap peran, lalu test_matriks_izin_setiap_peran dijalankan lewat pytest.mark.parametrize dengan id "{metode} {url}". Untuk peran yang berhak, assertion hanya menuntut kode bukan 403; untuk peran lain, kode wajib 403. backend/tests/test_izin.py:70-82

test_tabel_izin_sesuai_plan mengunci isi IZIN_PERAN: admin_penjualan memegang lihat_produk dan kelola_transaksi, staf_gudang memegang lihat_produk dan kelola_stok, dan pemilik memegang seluruh SEMUA_IZIN. backend/tests/test_izin.py:85-88 Rincian peran dan izinnya dijelaskan di Autentikasi, 2FA, dan Izin Peran.

Uji berikutnya menelusuri seluruh pola url lewat _semua_pola, melewati api/_allauth dan himpunan PUBLIK berisi api/kesehatan/ dan api/auth/sesi/, lalu memastikan setiap endpoint hanya mengembalikan kode dalam {401, 405} saat diakses tanpa login. Uji itu juga menuntut jumlah endpoint yang diperiksa melebihi 25. backend/tests/test_izin.py:91-120

Dua uji menutup celah deklarasi. test_view_baru_tanpa_deklarasi_izin_ditolak memanggil PunyaIzin().has_permission pada view yang tidak punya atribut izin dan menuntut hasil False. backend/tests/test_izin.py:123-131 Uji terakhir memastikan pengguna tanpa peran mendapat 403 dan pemilik yang belum mengaktifkan 2FA mendapat 403 dengan kode bernilai wajib_2fa. backend/tests/test_izin.py:134-149

Uji konkurensi

Berkas test_konkurensi.py diberi penanda modul pytest.mark.django_db(transaction=True) agar data yang ditulis satu utas terlihat oleh utas lain. backend/tests/test_konkurensi.py:14-16 Helper _siapkan membuat pengguna, produk, satu batch berisi satu botol, dan pelanggan. backend/tests/test_konkurensi.py:19-25

Helper _jalankan_bersamaan adalah inti pengujian ini. Fungsi itu membuat threading.Barrier(n), menjalankan n utas yang menunggu di gerbang sebelum bekerja, menangkap AturanBisnisError dan menyimpan exc.kode sebagai hasil, menutup koneksi di blok finally, lalu menunggu setiap utas dengan join(30). backend/tests/test_konkurensi.py:28-46

sequenceDiagram
  participant T as test_stok_terakhir_tidak_terjual_dua_kali
  participant B as threading.Barrier
  participant U as utas buat_transaksi
  participant DB as PostgreSQL
  T->>B: _jalankan_bersamaan(8, kerja)
  T->>U: start 8 thread
  U->>B: gerbang.wait()
  B-->>U: lepas serentak
  U->>DB: buat_transaksi(kunci_idempoten=uuid4)
  DB-->>U: AturanBisnisError stok_tidak_cukup atau nomor nota
  U->>DB: connection.close()
  T->>T: assert hasil.count(stok_tidak_cukup) == 7
%% fig: Uji konkurensi batch terakhir
%% desc: Delapan utas menunggu di Barrier lalu berebut membeli batch terakhir; tujuh gagal dengan kode stok_tidak_cukup dan hanya satu nota tersimpan.
Fig. 2: Uji konkurensi batch terakhirDelapan utas menunggu di Barrier lalu berebut membeli batch terakhir; tujuh gagal dengan kode stok_tidak_cukup dan hanya satu nota tersimpan.

Tiga skenario diuji. test_stok_terakhir_tidak_terjual_dua_kali menjalankan 8 utas pada batch berisi 1 botol dan menuntut hasil berisi 7 kode stok_tidak_cukup, satu Transaksi tersimpan, dan sisa batch menjadi 0. backend/tests/test_konkurensi.py:49-68

test_kunci_idempoten_sama_bersamaan_hanya_satu_nota memakai satu kunci yang sama untuk 6 utas, dengan sisa batch dinaikkan ke 50. Uji itu menuntut semua hasil identik, hanya satu Transaksi, dan sisa menjadi 49. backend/tests/test_konkurensi.py:71-92 Skenario ketiga mengirim 6 utas dengan kunci_idempoten berbeda dan menuntut 6 nomor nota yang semuanya unik. backend/tests/test_konkurensi.py:95-113 Perilaku buat_transaksi yang dijaga uji ini, termasuk pengembalian nota lama saat IntegrityError, dijelaskan di Alur Transaksi, Pembayaran, Retur, dan Piutang.

Uji laporan dan PDF

Uji PDF dibuat toleran terhadap mesin tanpa Pango. test_nota_dan_laporan_pdf mencoba import weasyprint; bila muncul OSError, uji itu memanggil pytest.skip dengan pesan "Pustaka Pango tidak tersedia di mesin ini". backend/tests/test_laporan.py:125-129 Di CI, langkah apt-get tadi memasok pustaka tersebut sehingga uji berjalan penuh. Isi laporan dan ekspornya dibahas di Dasbor, Laporan, dan Ekspor Berkas.

⚠️ pytest.ini memakai --reuse-db, sedangkan CI memakai pytest --create-db. Jalankan pytest --create-db sebelum mengandalkan hasil lokal, karena database uji yang dipakai ulang bisa menyembunyikan migration yang belum diterapkan.

Job frontend

Job frontend berjalan di ubuntu-24.04 dengan working-directory: frontend, memasang Node versi 26 dan cache npm yang kuncinya frontend/package-lock.json, lalu menjalankan npm ci, langkah kesesuaian tipe API, npm run typecheck, npm run build, dan npm audit --omit=dev --audit-level=critical. .github/workflows/ci.yml:51-70

Langkah kesesuaian tipe API menjalankan npm run tipe-api lalu git diff --exit-code src/lib/api-types.ts. Bila hasil pembangkitan berbeda dari berkas yang di-commit, pipeline gagal. .github/workflows/ci.yml:64-67 Kedua skrip itu didefinisikan di package.json: tipe-api memanggil openapi-typescript ../backend/schema.yaml -o src/lib/api-types.ts, dan typecheck memanggil tsc --noEmit. frontend/package.json:9-10 Pemeriksaan ini mengikat frontend ke skema backend yang sama dengan yang diuji diff di job backend.

Job image dan pemindaian Trivy

Job image hanya berjalan bila github.ref diawali refs/tags/v, menunggu needs: [backend, frontend], dan meminta izin contents: read serta packages: write. .github/workflows/ci.yml:72-78

Langkah Build image mengambil versi dari GITHUB_REF_NAME#v, membangun image hydro-api dari direktori backend dan hydro-web dari deploy/web.Dockerfile, lalu menyimpan VERSI ke GITHUB_ENV. Langkah berikutnya menjalankan aquasec/trivy:0.67.2 untuk kedua image dengan --exit-code 1 --severity CRITICAL --ignore-unfixed. .github/workflows/ci.yml:86-97 Langkah terakhir mendorong kedua image ke ghcr.io. .github/workflows/ci.yml:98-101

⚠️ Pemindaian Trivy dan push ke ghcr.io hanya berjalan saat tag v* dibuat. pull request tidak pernah melewati kedua langkah itu, jadi kerentanan CRITICAL baru terlihat ketika tag sudah dipotong. Opsi --ignore-unfixed juga membuat kerentanan yang belum punya perbaikan tidak menggagalkan build. Rangkaian deploy yang memakai image hasil job ini dibahas di Deploy, Backup, dan Operasi VPS.