Lewati ke konten

Shield Corev1.2.x

Testing & CI

Shield Core punya test suite sendiri. Halaman ini membahas apa yang perlu Anda uji di package adapter Anda, dan alat-alat yang repository ini menyediakan.

Terminal window
composer test # Pest
composer analyse # PHPStan level 8
composer format-test # Pint, mode periksa
composer mutate # mutation testing

Tiga perkakas lain di luar Composer:

Terminal window
php tools/scanner-simulator/simulate.php # pemutaran ulang corpus scanner
php tools/scanner-simulator/benchmark.php # latensi jalur allow
php tools/replay.php # deteksi dari log kejadian nyata

Shield dipanggil di setiap request. Kalau adapter Anda menambah 5 milidetik, itu 5 milidetik di setiap halaman.

Terminal window
php tools/scanner-simulator/benchmark.php

Targetnya jalur allow di bawah 2 milidetik. Jalankan setelah setiap perubahan yang menyentuh kode request.

replay.php untuk verifikasi yang lebih jujur

Section titled “replay.php untuk verifikasi yang lebih jujur”

Mengambil log kejadian nyata dan menjalankan ulang request tersebut lewat engine. Ini cara paling bagus untuk mengukur apakah perubahan Anda memperbaiki atau merusak keputusan lama.

Semua waktu di Shield datang dari ClockInterface. Ini satu-satunya cara menguji pelusan dan kedaluwarsa tanpa menunggu.

use Ganadev\Shield\Core\Clock\ClockInterface;
final class FrozenClock implements ClockInterface
{
public function __construct(
private \DateTimeImmutable $now = new \DateTimeImmutable('2026-01-01 00:00:00'),
) {}
public function now(): \DateTimeImmutable
{
return $this->now;
}
public function advance(string $modifier): void
{
$this->now = $this->now->modify($modifier);
}
}

Dengan begitu Anda bisa menguji pelusan offense tanpa tidur 24 jam:

$clock = new FrozenClock;
$engine = $this->engine($clock);
// Ban dengan offense_count 3
$engine->inspect($context, $counters);
// Belum ada jeda — offense tetap 3
expect($this->offenseCount())->toBe(3);
// Setelah 24 jam tanpa aktivitas
$clock->advance('+25 hours');
$engine->inspect($context, $counters);
// Sekarang 2
expect($this->offenseCount())->toBe(2);

Lima skenario ini menutup bagian yang paling sering rusak:

test('encoding berlapis diselesaikan', function () {
$result = $this->inspect('/%2e%65nv');
expect($result->shouldBlock())->toBeTrue();
});

Satu kasus ini menutup banyak payload.

test('allowlist tidak menutupi rule critical', function () {
config(['shield.allowlist.paths' => ['/']]);
$result = $this->inspect('/.env');
expect($result->shouldBlock())->toBeTrue();
});

Ini regresi yang mahal kalau sampai lolos.

test('mode observe tidak pernah memblokir', function () {
config(['shield.mode' => 'observe']);
$result = $this->inspect('/.env');
expect($result->shouldBlock())->toBeFalse();
expect($result->verdict->decision)->toBe(Decision::Observe);
expect($result->verdict->intended)->toBe(Decision::BlockRequest);
expect($result->verdict->reason)
->toStartWith('observe_mode:');
});

Perhatikan dua lapis yang dibandingkan berbeda. Kalau test Anda hanya memeriksa shouldBlock(), ia tidak akan menangkap mode yang keliru.

test('repository gagal tidak menjatuhkan request', function () {
$this->bans->shouldReceive('findActiveByIp')
->andThrow(new RuntimeException('database down'));
$result = $this->inspect('/');
expect($result->infrastructureDegraded)->toBeTrue();
expect($result->shouldBlock())->toBeFalse();
});
test('isi body tidak masuk log', function () {
$this->inspect('/api', 'POST', body: 'password=rahasia123');
expect($this->recordedEvents())->not->toContain('rahasia123');
});

Ini yang membuat Shield layak dipakai di produksi.

Skenario Yang diuji
Cookie tepercaya valid Tidak kena challenge lagi.
Cookie tepercaya dari domain lain Ditolak.
fail_mode = closed dengan DB mati Request diblokir dengan alasan fail_closed.
Path di skip_paths Perilaku nol, signature tetap jalan.
mode = challenge dengan critical Tetap BLOCK_REQUEST.
IP sudah di-ban lalu lolos challenge Ban dilepas, challengePassedAt terisi.
Melepas ban lalu offense lagi offenseCount naik, bukan reset.
Bot terverifikasi Skor perilaku nol.
Klaim bot gagal diverifikasi unverified_crawler_claim muncul.
URI melewati max_uri_length Dipotong, bukan error.
Terminal window
composer analyse # wajib. PHPStan level 8 menangkap kesalahan tipe kontrak.
composer test # wajib.
composer format-test

Mutation testing (composer mutate) butuh xdebug atau pcov, jadi biasanya dijalankan terpisah, tidak di setiap commit.

Untuk adapter, tambahkan:

Terminal window
php tools/scanner-simulator/benchmark.php # harus di bawah 2 ms

Kalau latensi naik, commit-nya belum selesai.

Jalankan test yang sama dengan dua mode berbeda:

it('tidak pernah memblokir dalam observe mode', function () {
config(['shield.mode' => 'observe']);
// semua kasus harus ALLOW atau OBSERVE
})->with(['observe', 'challenge', 'enforce']);

Kalau ada kasus yang hanya lulus di satu mode, ada bug di mode yang lain. Perbedaan mode seharusnya hanya soal respons, bukan soal data.

Kalau sebuah test gagal, verdict->reason biasanya langsung menjelaskan kenapa:

Alasan Artinya
critical_signature_sensitive.env Rule cocok. Bukan bug scoring.
score_ban Skor melewati ambang. Periksa score->total.
active_ban_challenge Ban aktif menaikkan jadi challenge.
trusted_cookie Cookie yang membuat request lolos.
observe_mode: would_have_been_... Mode observe menahan sesuatu.
fail_closed Infrastruktur bermasalah.

fail_closed satu-satunya yang biasanya berarti ada masalah lingkungan, bukan logika.

  • Perilaku default framework — itu tugas framework.
  • Tampilan HTML challenge — itu presentasi.
  • Driver pihak ketiga — provider itu yang menguji dirinya sendiri.

Yang perlu diuji package Anda: mapping dari dunia framework Anda ke dunia Shield, dan mapping kembali ke respons. Titik bifurasi itulah tempat bug tinggal.

Powered by PT Ganadev Multi Solusi