Files
bij/app/Commands/ApiStagingValidate.php
2026-04-21 05:59:39 +07:00

400 lines
18 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Commands;
use App\Services\Mobile\MobileJsonService;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use Config\Database;
use Throwable;
/**
* Validasi staging: koneksi DB + perilaku MobileJsonService vs ekspektasi CI3.
*
* Opsi:
* --with-uploads Uji upload kecil (save_pp) lalu hapus file & kembalikan photo DB jika perlu
*
* Env opsional (uji mutasi ringan):
* STAGING_VALIDATE_TOKEN token pegawai valid
* STAGING_VALIDATE_PASS password plaintext (untak uji save_password salah)
*/
class ApiStagingValidate extends BaseCommand
{
protected $group = 'Api';
protected $name = 'api:staging-validate';
protected $usage = 'api:staging-validate [--with-uploads]';
protected $description = 'Validasi staging API mobile terhadap DB (parity CI3)';
private int $passed = 0;
private int $failed = 0;
/** @var list<array{endpoint: string, ok: bool, note: string}> */
private array $results = [];
public function run(array $params)
{
$withUploads = CLI::getOption('with-uploads') !== null;
CLI::write('=== API staging validate ===', 'yellow');
CLI::write('Timezone: ' . date_default_timezone_get());
CLI::write('API_PARITY_LOG: ' . (filter_var(env('API_PARITY_LOG', false), FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'));
if (! $this->databaseReachable()) {
$this->record('database_ping', false, 'Koneksi database gagal — periksa .env (samakan dengan CI3).');
$this->finish($withUploads, false);
return EXIT_ERROR;
}
$this->record('database_ping', true, 'SELECT 1 OK');
$svc = new MobileJsonService();
$db = Database::connect();
$this->assertLoginInvalid($svc);
$this->assertLoginEmpty($svc);
$this->assertLoginWTokenEmpty($svc);
$this->assertLoginWTokenInvalid($svc);
$this->assertProfilInvalid($svc);
$this->assertPresensiTodayInvalid($svc);
$this->assertPresensiInvalid($svc);
$this->assertSaveMasukInvalidToken($svc);
$this->assertSavePulangInvalidToken($svc);
$this->assertSaveIstirahatInvalidToken($svc);
$this->assertSaveAktifitasInvalidToken($svc);
$this->assertSaveCutiInvalidToken($svc);
$this->assertBatalkanCutiInvalidToken($svc);
$this->assertBeritaInvalid($svc);
$this->assertCutiInvalid($svc);
$this->assertLemburInvalid($svc);
$this->assertLiburInvalid($svc);
$this->assertAktifitasInvalid($svc);
$this->assertDaftarTodayInvalid($svc);
$this->assertSavePpInvalidToken($svc);
$this->assertSavePasswordInvalidToken($svc);
$token = $this->fetchSampleToken($db);
if ($token === null) {
$this->record('authenticated_read_tests', true, 'Lewat: tidak ada pegawai dengan token di DB (uji invalid token sudah jalan).');
} else {
$this->runAuthenticatedSuite($svc, $token, $withUploads);
}
$this->assertUploadDirectories();
$allOk = $this->failed === 0;
$this->finish($withUploads, $allOk);
return $allOk ? EXIT_SUCCESS : EXIT_ERROR;
}
private function finish(bool $withUploads, bool $allOk): void
{
$path = WRITEPATH . 'staging' . DIRECTORY_SEPARATOR;
if (! is_dir($path)) {
mkdir($path, 0755, true);
}
$report = [
'generated_at' => date('c'),
'timezone' => date_default_timezone_get(),
'with_uploads' => $withUploads,
'passed' => $this->passed,
'failed' => $this->failed,
'all_ok' => $allOk,
'results' => $this->results,
];
file_put_contents($path . 'last-validation.json', json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
CLI::newLine();
CLI::write("Selesai. Passed: {$this->passed} Failed: {$this->failed}", $allOk ? 'green' : 'red');
CLI::write('Laporan mesin: writable/staging/last-validation.json', 'cyan');
}
private function databaseReachable(): bool
{
try {
Database::connect()->query('SELECT 1');
return true;
} catch (Throwable $e) {
return false;
}
}
private function record(string $endpoint, bool $ok, string $note): void
{
$this->results[] = ['endpoint' => $endpoint, 'ok' => $ok, 'note' => $note];
if ($ok) {
$this->passed++;
CLI::write("[PASS] {$endpoint}: {$note}", 'green');
} else {
$this->failed++;
CLI::write("[FAIL] {$endpoint}: {$note}", 'red');
}
}
private function assertTrue(string $endpoint, bool $cond, string $okMsg, string $failMsg): void
{
$this->record($endpoint, $cond, $cond ? $okMsg : $failMsg);
}
private function assertLoginInvalid(MobileJsonService $svc): void
{
$r = $svc->login('__ci4_staging_nope__', 'wrongpass');
$ok = ($r['status'] === 0 || $r['status'] === '0')
&& ($r['pesan'] ?? '') === 'Username atau Password tidak sesuai'
&& ! array_key_exists('token', $r);
$this->assertTrue('login_invalid', $ok, 'status 0, pesan default, tanpa token', json_encode($r, JSON_UNESCAPED_UNICODE));
}
private function assertLoginEmpty(MobileJsonService $svc): void
{
$r = $svc->login('', '');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('token', $r);
$this->assertTrue('login_empty', $ok, 'status 0 tanpa token', json_encode($r, JSON_UNESCAPED_UNICODE));
}
private function assertLoginWTokenEmpty(MobileJsonService $svc): void
{
$r = $svc->loginWToken('');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === ''
&& array_key_exists('status', $r) && array_key_exists('pesan', $r) && ! array_key_exists('token', $r);
$this->assertTrue('login_w_token_empty', $ok, 'status 0, pesan kosong, tanpa token', json_encode($r));
}
private function assertLoginWTokenInvalid(MobileJsonService $svc): void
{
$r = $svc->loginWToken('invalid_token_ci4_staging_xyz');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === '';
$this->assertTrue('login_w_token_invalid', $ok, 'status 0', json_encode($r));
}
private function assertProfilInvalid(MobileJsonService $svc): void
{
$r = $svc->profil('');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('pegawai', $r);
$this->assertTrue('profil_invalid_token', $ok, 'tanpa pegawai', json_encode($r));
}
private function assertPresensiTodayInvalid(MobileJsonService $svc): void
{
$r = $svc->presensiToday('');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('data', $r);
$this->assertTrue('presensi_today_invalid', $ok, 'tanpa data', json_encode($r));
}
private function assertPresensiInvalid(MobileJsonService $svc): void
{
$r = $svc->presensi('');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('data', $r);
$this->assertTrue('presensi_invalid', $ok, 'tanpa data', json_encode($r));
}
private function assertSaveMasukInvalidToken(MobileJsonService $svc): void
{
$r = $svc->saveMasuk('', 'p.png', '', '0', '0', '0');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === 'Tidak ada jadwal kerja';
$this->assertTrue('save_masuk_invalid', $ok, 'pesan jadwal', json_encode($r));
}
private function assertSavePulangInvalidToken(MobileJsonService $svc): void
{
$r = $svc->savePulang('', 'p.png', '', '0', '0', '0');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === 'Tidak ada jadwal kerja';
$this->assertTrue('save_pulang_invalid', $ok, 'pesan jadwal', json_encode($r));
}
private function assertSaveIstirahatInvalidToken(MobileJsonService $svc): void
{
$r = $svc->saveIstirahat('', '08:00', '');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === 'Tidak ada jadwal kerja';
$this->assertTrue('save_istirahat_invalid', $ok, 'pesan jadwal', json_encode($r));
}
private function assertSaveAktifitasInvalidToken(MobileJsonService $svc): void
{
$r = $svc->saveAktifitas('', 'x.png', '', '2020-01-01', 'd');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === '';
$this->assertTrue('save_aktifitas_invalid', $ok, 'status 0 pesan kosong', json_encode($r));
}
private function assertSaveCutiInvalidToken(MobileJsonService $svc): void
{
$r = $svc->saveCuti('', 'x.png', '', '2020-01-01', 'a', 't');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === '';
$this->assertTrue('save_cuti_invalid', $ok, 'status 0 pesan kosong', json_encode($r));
}
private function assertBatalkanCutiInvalidToken(MobileJsonService $svc): void
{
$r = $svc->batalkanCuti('', '1');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === '';
$this->assertTrue('batalkan_cuti_invalid', $ok, 'status 0', json_encode($r));
}
private function assertBeritaInvalid(MobileJsonService $svc): void
{
$r = $svc->berita('', '0', '5');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('data', $r);
$this->assertTrue('berita_invalid', $ok, 'tanpa data', json_encode($r));
}
private function assertCutiInvalid(MobileJsonService $svc): void
{
$r = $svc->cuti('', '0', '5');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('data', $r);
$this->assertTrue('cuti_invalid', $ok, 'tanpa data', json_encode($r));
}
private function assertLemburInvalid(MobileJsonService $svc): void
{
$r = $svc->lembur('', '0', '5');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('data', $r);
$this->assertTrue('lembur_invalid', $ok, 'tanpa data', json_encode($r));
}
private function assertLiburInvalid(MobileJsonService $svc): void
{
$r = $svc->libur('');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('data', $r);
$this->assertTrue('libur_invalid', $ok, 'tanpa data', json_encode($r));
}
private function assertAktifitasInvalid(MobileJsonService $svc): void
{
$r = $svc->aktifitas('', '0', '5');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('data', $r);
$this->assertTrue('aktifitas_invalid', $ok, 'tanpa data', json_encode($r));
}
private function assertDaftarTodayInvalid(MobileJsonService $svc): void
{
$r = $svc->daftarToday('');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('data', $r);
$this->assertTrue('daftar_today_invalid', $ok, 'tanpa data', json_encode($r));
}
private function assertSavePpInvalidToken(MobileJsonService $svc): void
{
$r = $svc->savePp('', 'x.png', '');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === 'Tidak ada jadwal kerja';
$this->assertTrue('save_pp_invalid', $ok, 'pesan sama CI3 untuk token salah', json_encode($r));
}
private function assertSavePasswordInvalidToken(MobileJsonService $svc): void
{
$r = $svc->savePassword('', 'a', 'b');
$ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === '-';
$this->assertTrue('save_password_invalid', $ok, 'pesan default -', json_encode($r));
}
private function fetchSampleToken($db): ?string
{
$row = $db->table('pegawai')->select('token')->where('token IS NOT NULL', null, false)->where('token !=', '')->limit(1)->get()->getRow();
return $row && ! empty($row->token) ? (string) $row->token : null;
}
private function runAuthenticatedSuite(MobileJsonService $svc, string $token, bool $withUploads): void
{
$lw = $svc->loginWToken($token);
$ok = ($lw['status'] === 1 || $lw['status'] === '1') && ($lw['pesan'] ?? '') === '' && ! array_key_exists('token', $lw);
$this->assertTrue('login_w_token_valid', $ok, 'status 1, pesan kosong, tanpa token di JSON', json_encode($lw));
$p = $svc->profil($token);
$ok = ($p['status'] === 1 || $p['status'] === '1') && isset($p['pegawai']) && is_object($p['pegawai']);
$this->assertTrue('profil_valid_token', $ok, 'status 1 + pegawai object', json_encode(['status' => $p['status'] ?? null]));
$pt = $svc->presensiToday($token);
$ok = ($pt['status'] === 1 || $pt['status'] === '1') && isset($pt['data']);
$this->assertTrue('presensi_today_valid', $ok, 'status 1 + data', isset($pt['data']) ? 'data ok' : 'no data');
$pr = $svc->presensi($token);
$ok = ! isset($pr['data']) || (isset($pr['data']) && is_array($pr['data']));
$ok = $ok && (($pr['status'] === 0 && ! isset($pr['data'])) || ($pr['status'] === 1 && isset($pr['data'])));
$this->assertTrue('presensi_valid_token', (bool) $ok, 'status sesuai ada/tidak data', json_encode(['status' => $pr['status'] ?? null, 'has_data' => isset($pr['data'])]));
$b = $svc->berita($token, '', '');
$ok = ($b['status'] === 1 || $b['status'] === '1') && isset($b['data']) && is_array($b['data']);
$this->assertTrue('berita_valid', $ok, 'status 1 + data array', json_encode(['count' => isset($b['data']) ? count($b['data']) : -1]));
$c = $svc->cuti($token, '', '');
$ok = ($c['status'] === 1 || $c['status'] === '1') && isset($c['data']) && is_array($c['data']);
$this->assertTrue('cuti_valid', $ok, 'status 1 + data', '');
$l = $svc->lembur($token, '', '');
$ok = ($l['status'] === 1 || $l['status'] === '1') && isset($l['data']) && is_array($l['data']);
$this->assertTrue('lembur_valid', $ok, 'status 1 + data', '');
$lib = $svc->libur($token);
$ok = ($lib['status'] === 1 || $lib['status'] === '1') && isset($lib['data']) && is_array($lib['data']);
$this->assertTrue('libur_valid', $ok, 'status 1 + data', '');
$a = $svc->aktifitas($token, '', '');
$ok = ($a['status'] === 1 || $a['status'] === '1') && isset($a['data']) && is_array($a['data']);
$this->assertTrue('aktifitas_valid', $ok, 'status 1 + data', '');
$d = $svc->daftarToday($token);
$ok = ($d['status'] === 0 && ! isset($d['data'])) || ($d['status'] === 1 && isset($d['data']) && is_array($d['data']));
$this->assertTrue('daftar_today_valid', $ok, 'status konsisten', json_encode(['status' => $d['status'] ?? null]));
// save_istirahat: mulai & selesai kosong — CI4 stabil (tanpa UPDATE)
$ist = $svc->saveIstirahat($token, '', '');
$ok = ($ist['status'] === 0 || $ist['status'] === '0') && ($ist['pesan'] ?? '') === 'Tidak ada jadwal kerja';
$this->assertTrue('save_istirahat_empty_both_ci4', $ok, 'deviasi terdokumen vs CI3 undefined', json_encode($ist));
$envPass = env('STAGING_VALIDATE_PASS', '');
if (is_string($envPass) && $envPass !== '') {
$sp = $svc->savePassword($token, 'definitely_wrong_old_password_xx', 'new');
$ok = ($sp['status'] === 0 || $sp['status'] === '0')
&& str_contains((string) ($sp['pesan'] ?? ''), 'Password lama tidak sesuai');
$this->assertTrue('save_password_wrong_old', $ok, 'pesan salah password lama', json_encode($sp));
}
if ($withUploads) {
$this->runTinyUploadPp($svc, $token);
}
}
private function runTinyUploadPp(MobileJsonService $svc, string $token): void
{
// 1x1 GIF transparan (sangat kecil)
$b64 = 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
$db = Database::connect();
$row = $db->table('pegawai')->select('id_pegawai, photo')->where('token', $token)->get()->getRow();
$oldPh = $row->photo ?? '';
$r = $svc->savePp($token, 'staging.gif', $b64);
$dir = FCPATH . 'assets' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'pengguna';
$ok = ($r['status'] === 1 || $r['status'] === '1');
if ($ok && $row) {
$newRow = $db->table('pegawai')->select('photo')->where('id_pegawai', $row->id_pegawai)->get()->getRow();
$fn = $newRow->photo ?? '';
$path = $dir . DIRECTORY_SEPARATOR . $fn;
$ok = $ok && $fn !== '' && is_file($path);
if (is_file($path)) {
@unlink($path);
}
$db->table('pegawai')->where('id_pegawai', $row->id_pegawai)->update(['photo' => $oldPh]);
}
$this->assertTrue('save_pp_upload_roundtrip', $ok, 'upload + file ada + revert photo DB', json_encode($r));
}
private function assertUploadDirectories(): void
{
$subs = ['dokcuti', 'aktifitas', 'absen' . DIRECTORY_SEPARATOR . 'masuk', 'absen' . DIRECTORY_SEPARATOR . 'pulang', 'pengguna'];
$ok = true;
foreach ($subs as $sub) {
$p = FCPATH . 'assets' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . $sub;
if (! is_dir($p)) {
mkdir($p, 0755, true);
}
$ok = $ok && is_dir($p) && is_writable($p);
}
$this->assertTrue('upload_directories', $ok, 'folder upload ada & writable', 'cek permission');
}
}