*/ 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'); } }