400 lines
18 KiB
PHP
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');
|
|
}
|
|
}
|