#!/usr/bin/env bash
# run-tests.sh — test suite untuk paket otomasi-dengan-cron-dan-systemd.
#
# Suite menyiapkan folder kerja terisolasi (mktemp) beserta data
# contohnya, menyalin skrip dari src/ dan berkas dari config/, lalu
# menjalankan rangkaian pengujian dengan assertion sederhana:
#   - idempotensi tugas.sh (dua eksekusi, hasil akhir identik),
#   - --dry-run tidak mengubah mirror maupun log,
#   - baris crontab lolos validator sintaks 5-field dan benar-benar
#     bisa dijalankan (jalur disesuaikan ke folder kerja),
#   - unit systemd lolos systemd-analyze verify,
#   - flock menolak eksekusi ganda lewat baris crontab dan runner.sh,
#   - cek-jadwal.sh mengklasifikasi tiga kondisi log dengan benar.
#
# Jalankan dari mana saja: bash tests/run-tests.sh

set -u

SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJ_DIR=$(dirname "$SCRIPT_DIR")

PASS=0
FAIL=0

# assert_eq <harapan> <nyata> <pesan>: bandingkan dua nilai.
assert_eq() {
    if [ "$1" = "$2" ]; then
        PASS=$((PASS + 1))
        printf 'OK    %s\n' "$3"
    else
        FAIL=$((FAIL + 1))
        printf 'GAGAL %s: harapan %s, nyata %s\n' "$3" "$1" "$2"
    fi
}

# --- siapkan folder kerja terisolasi ---------------------------------------
WORK=$(mktemp -d)
cp "$PROJ_DIR/src/tugas.sh" "$PROJ_DIR/src/cek-jadwal.sh" \
    "$PROJ_DIR/src/runner.sh" "$WORK/"
cd "$WORK" || exit 1

# Data contoh (fixture) untuk tugas sinkronisasi.
mkdir -p sumber/laporan
printf 'daftar tugas minggu ini\n' > sumber/tugas.txt
printf 'laporan penjualan kuartal pertama\n' > sumber/laporan/penjualan.txt

# --- uji 1: eksekusi pertama sukses ---------------------------------------
STAMP=20260115T080000 bash tugas.sh > /dev/null 2>&1
assert_eq 0 "$?" "eksekusi pertama keluar 0"
diff -r sumber mirror > /dev/null 2>&1
assert_eq 0 "$?" "mirror identik dengan sumber"

# --- uji 2: idempotensi — eksekusi ulang tidak mengubah hasil -------------
cp -a mirror potret-1
STAMP=20260115T080500 bash tugas.sh > /dev/null 2>&1
assert_eq 0 "$?" "eksekusi kedua keluar 0"
diff -r potret-1 mirror > /dev/null 2>&1
assert_eq 0 "$?" "eksekusi ulang tidak mengubah mirror"
pindah2=$(sed -n '2p' log/tugas.log | grep -c 'pindah=0')
assert_eq 1 "$pindah2" "eksekusi kedua tidak memindah berkas (idempoten)"

# --- uji 3: dry-run tidak mengubah mirror maupun log -----------------------
printf 'catatan baru\n' > sumber/catatan-baru.txt
n_log=$(wc -l < log/tugas.log)
STAMP=20260115T081000 bash tugas.sh --dry-run > /dev/null 2>&1
assert_eq 0 "$?" "dry-run keluar 0"
[ ! -f mirror/catatan-baru.txt ]
assert_eq 0 "$?" "dry-run tidak menyalin berkas baru"
n_log2=$(wc -l < log/tugas.log)
assert_eq "$n_log" "$n_log2" "dry-run tidak menulis log"

# --- uji 4: eksekusi berikutnya membawa berkas baru ------------------------
STAMP=20260115T081500 bash tugas.sh > /dev/null 2>&1
assert_eq 0 "$?" "eksekusi ketiga keluar 0"
diff -r sumber mirror > /dev/null 2>&1
assert_eq 0 "$?" "mirror mengikuti sumber terbaru"

# --- uji 5: opsi salah dan sumber hilang ----------------------------------
bash tugas.sh --salah-opsi > /dev/null 2>&1
assert_eq 1 "$?" "opsi tidak dikenal keluar 1"
TUGAS_SRC="$WORK/tidak-ada" STAMP=20260116T080000 bash tugas.sh > /dev/null 2>&1
assert_eq 2 "$?" "sumber hilang keluar 2"
gagal=$(tail -n 1 log/tugas.log | grep -c 'GAGAL|keluar=2')
assert_eq 1 "$gagal" "kegagalan sumber tercatat di log"

# --- uji 6: crontab lolos validator sintaks 5-field ------------------------
cat > validasi-crontab.awk <<'AWK'
# validasi-crontab.awk — validator sintaks 5-field untuk berkas crontab.

function cek_field(v, lo, hi,    daftar, n, i, bagian, pos, langkah, rentang) {
    n = split(v, daftar, ",")
    for (i = 1; i <= n; i++) {
        bagian = daftar[i]
        pos = index(bagian, "/")
        if (pos > 0) {
            langkah = substr(bagian, pos + 1)
            if (langkah !~ /^[0-9]+$/ || langkah + 0 < 1)
                return 0
            bagian = substr(bagian, 1, pos - 1)
        }
        if (bagian == "*")
            continue
        if (bagian ~ /^[0-9]+$/) {
            if (bagian + 0 < lo || bagian + 0 > hi)
                return 0
        } else if (bagian ~ /^[0-9]+-[0-9]+$/) {
            split(bagian, rentang, "-")
            if (rentang[1] + 0 < lo || rentang[2] + 0 > hi)
                return 0
            if (rentang[1] + 0 > rentang[2] + 0)
                return 0
        } else {
            return 0
        }
    }
    return 1
}

/^[ \t]*#/ || /^[ \t]*$/ { next }
/^[A-Za-z_][A-Za-z0-9_]*=/ { printf "BARIS %d: pengaturan %s\n", NR, $1; next }
{
    jumlah++
    if (NF < 6) {
        printf "BARIS %d SALAH: perlu 5 field jadwal + perintah\n", NR
        salah++
        next
    }
    if (!cek_field($1, 0, 59)) { printf "BARIS %d SALAH: menit   %s\n", NR, $1; salah++; next }
    if (!cek_field($2, 0, 23)) { printf "BARIS %d SALAH: jam     %s\n", NR, $2; salah++; next }
    if (!cek_field($3, 1, 31)) { printf "BARIS %d SALAH: tanggal %s\n", NR, $3; salah++; next }
    if (!cek_field($4, 1, 12)) { printf "BARIS %d SALAH: bulan   %s\n", NR, $4; salah++; next }
    if (!cek_field($5, 0, 7))  { printf "BARIS %d SALAH: hari    %s\n", NR, $5; salah++; next }
    printf "BARIS %d OK: %s %s %s %s %s\n", NR, $1, $2, $3, $4, $5
}
END {
    printf "%d baris jadwal diperiksa, %d SALAH\n", jumlah + 0, salah + 0
    if (salah > 0)
        exit 1
}
AWK
awk -f validasi-crontab.awk "$PROJ_DIR/config/tugas.crontab" > hasil-vaildasi.txt
assert_eq 0 "$?" "config/tugas.crontab lolos validator 5-field"
ringkasan=$(tail -n 1 hasil-vaildasi.txt)
assert_eq "1 baris jadwal diperiksa, 0 SALAH" "$ringkasan" "validator menemukan 1 jadwal valid"

cat > salah-jadwal.crontab <<'CRON'
# salah-jadwal.crontab — fixture berisi jadwal yang salah.
SHELL=/bin/bash

0 8 * * *
0 25 * * * /home/budi/otomasi/tugas.sh
61 * * * * /home/budi/otomasi/tugas.sh
CRON
awk -f validasi-crontab.awk salah-jadwal.crontab > hasil-salah.txt
assert_eq 1 "$?" "validator menolak jadwal yang salah (keluar 1)"
n_salah=$(grep -c 'SALAH:' hasil-salah.txt)
assert_eq 3 "$n_salah" "tiga baris salah terdeteksi"

# --- uji 7: baris crontab benar-benar bisa dijalankan ----------------------
# Ambil baris jadwal pertama, ganti jalur instalasi ke folder kerja,
# lalu jalankan persis seperti yang akan dilakukan cron.
baris=$(awk '!/^[ \t]*#/ && !/^[ \t]*$/ && !/^[A-Za-z_][A-Za-z0-9_]*=/ { print; exit }' \
    "$PROJ_DIR/config/tugas.crontab")
read -r _m _h _dm _mo _dw cmd <<< "$baris"
cmd=${cmd//\/home\/budi\/otomasi/$WORK}
mkdir -p log
STAMP=20260117T080000 bash -c "$cmd"
assert_eq 0 "$?" "baris crontab (jalur disesuaikan) sukses dijalankan"
n_ok=$(grep -c '^20260117T080000|OK' log/tugas.log)
assert_eq 1 "$n_ok" "eksekusi via baris crontab tercatat di log"
[ -s log/tugas-output.log ]
assert_eq 0 "$?" "output tugas tertangkap redirect crontab"

# --- uji 8: flock menolak eksekusi ganda ------------------------------------
flock "$WORK/.tugas.lock" sleep 1 &
sleep 0.3
n_sebelum=$(wc -l < log/tugas.log)
STAMP=20260117T080500 bash -c "$cmd" > /dev/null 2>&1
assert_eq 1 "$?" "baris crontab ditolak flock saat kunci sibuk"
n_sesudah=$(wc -l < log/tugas.log)
assert_eq "$n_sebelum" "$n_sesudah" "eksekusi yang ditolak tidak menulis log"
wait

# --- uji 9: unit systemd lolos verify ----------------------------------------
systemd-analyze verify "$PROJ_DIR/config/tugas-sinkron.service" \
    "$PROJ_DIR/config/tugas-sinkron.timer" > /dev/null 2>&1
assert_eq 0 "$?" "unit service+timer lolos systemd-analyze verify"

# --- uji 10: runner menjalankan tugas seperti scheduler ----------------------
STAMP=20260118T080000 bash runner.sh "$WORK" > /dev/null 2>&1
assert_eq 0 "$?" "runner menjalankan tugas seperti scheduler"
n_ok=$(grep -c '^20260118T080000|OK' log/tugas.log)
assert_eq 1 "$n_ok" "eksekusi via runner tercatat di log"

# --- uji 11: runner ditolak saat kunci sibuk (dobel) ------------------------
flock "$WORK/.tugas.lock" sleep 1 &
sleep 0.3
n_sebelum=$(wc -l < log/tugas.log)
STAMP=20260118T080500 bash runner.sh "$WORK" > /dev/null 2>&1
assert_eq 1 "$?" "runner ditolak flock saat tugas lain berjalan"
n_sesudah=$(wc -l < log/tugas.log)
assert_eq "$n_sebelum" "$n_sesudah" "runner yang ditolak tidak menulis log"
wait

# --- uji 12: cek-jadwal mengklasifikasi tiga kondisi log --------------------
cat > log-sehat.log <<'FIX'
20260113T080000|OK|keluar=0|durasi=0s|total=3|pindah=0|hapus=0
20260114T080000|OK|keluar=0|durasi=0s|total=3|pindah=1|hapus=0
20260115T080000|OK|keluar=0|durasi=0s|total=3|pindah=0|hapus=0
20260116T080000|OK|keluar=0|durasi=0s|total=3|pindah=2|hapus=0
FIX
cat > log-terlambat.log <<'FIX'
20260112T080000|OK|keluar=0|durasi=0s|total=3|pindah=0|hapus=0
20260113T080000|OK|keluar=0|durasi=0s|total=3|pindah=2|hapus=1
FIX
cat > log-gagal.log <<'FIX'
20260114T080000|OK|keluar=0|durasi=0s|total=3|pindah=0|hapus=0
20260115T080000|GAGAL|keluar=3|durasi=0s|rsync gagal
20260116T080000|GAGAL|keluar=3|durasi=0s|rsync gagal
FIX

NOW=20260116T120000 bash cek-jadwal.sh log-sehat.log > laporan-sehat.txt
assert_eq 0 "$?" "log sehat diklasifikasi SEHAT (keluar 0)"
n=$(grep -c 'sukses terakhir 4 jam lalu' laporan-sehat.txt)
assert_eq 1 "$n" "umur sukses dihitung: 4 jam"

NOW=20260116T120000 bash cek-jadwal.sh log-terlambat.log > laporan-terlambat.txt
assert_eq 1 "$?" "log berhenti diklasifikasi TERLAMBAT (keluar 1)"
n=$(grep -c '76 jam tanpa sukses' laporan-terlambat.txt)
assert_eq 1 "$n" "umur tanpa sukses dihitung: 76 jam"

NOW=20260116T120000 bash cek-jadwal.sh log-gagal.log > laporan-gagal.txt
assert_eq 2 "$?" "log gagal berturut diklasifikasi GAGAL (keluar 2)"
n=$(grep -c '2 kali gagal berturut-turut' laporan-gagal.txt)
assert_eq 1 "$n" "gagal berturut terdeteksi: 2 kali"

bash cek-jadwal.sh log-tidak-ada.log > /dev/null 2>&1
assert_eq 3 "$?" "berkas log yang hilang dilaporkan (keluar 3)"

# --- ringkasan -----------------------------------------------------------------
if [ "$FAIL" -eq 0 ]; then
    printf 'PASSED: %d/%d test lulus\n' "$PASS" "$PASS"
else
    printf 'FAILED: %d/%d test lulus\n' "$PASS" "$((PASS + FAIL))"
fi
cd / && rm -rf "$WORK"
[ "$FAIL" -eq 0 ]
