@@ -454,3 +454,125 @@ LEGACY
454454 [ " $elapsed " -lt 30 ]
455455 [[ " ${lines[0]} " = " {" ]]
456456}
457+
458+ # --- Test 28: --json-report list reports[] globally sorted by started_epoch ---
459+ # Regression guard for issue #483 comment 2: pre-fix code emitted TSV-index
460+ # entries before legacy entries regardless of date, producing a misordered
461+ # reports[] (e.g. Mar 29 legacy after Apr 18 TSV). The fix buffers both
462+ # passes and sorts by started_epoch descending.
463+ @test " --json-report list reports[] sorted by started_epoch descending" {
464+ local sessdir=" $LMD_INSTALL /sess"
465+ # Inject a legacy session with an OLDER date than any TSV scan we'll run.
466+ local oldsid=" 200101-0100.88881"
467+ cat > " $sessdir /session.$oldsid " << 'LEGACY '
468+ HOST: testhost.example.com
469+ SCAN ID: 200101-0100.88881
470+ STARTED: Jan 01 2020 01:00:00 +0000
471+ COMPLETED: Jan 01 2020 01:00:05 +0000
472+ ELAPSED: 5s [find: 1s]
473+ PATH: /home/legacyuser
474+ TOTAL FILES: 10
475+ TOTAL HITS: 0
476+ TOTAL CLEANED: 0
477+
478+ FILE HIT LIST:
479+ LEGACY
480+ # Run a fresh scan — produces a TSV-indexed entry with a recent epoch.
481+ cp " $SAMPLES_DIR /eicar.com" " $TEST_DIR /"
482+ maldet -a " $TEST_DIR " || true
483+
484+ run maldet --json-report list
485+ rm -f " $sessdir /session.$oldsid "
486+ assert_success
487+ # Extract started_epoch values in emission order and verify non-increasing.
488+ # python3 is available on debian12 + rocky9; skip on images without it.
489+ if ! command -v python3 > /dev/null 2>&1 ; then
490+ skip " python3 not available in this image"
491+ fi
492+ local ordered
493+ ordered=$( echo " $output " | python3 -c '
494+ import sys, json
495+ d = json.load(sys.stdin)
496+ eps = [r["started_epoch"] for r in d["reports"]]
497+ print("OK" if eps == sorted(eps, reverse=True) else "FAIL: " + repr(eps))
498+ ' )
499+ [[ " $ordered " == " OK" ]]
500+ }
501+
502+ # --- Test 29: --json-report list reports[] include started_epoch integer ---
503+ # Regression guard for issue #483 comment 1: machine consumers need an
504+ # unambiguous absolute timestamp. Asserts the field is present, is an
505+ # integer (not a quoted string), and matches date -d on the started string.
506+ @test " --json-report list reports[] include started_epoch matching started" {
507+ cp " $SAMPLES_DIR /eicar.com" " $TEST_DIR /"
508+ maldet -a " $TEST_DIR " || true
509+ run maldet --json-report list
510+ assert_success
511+ assert_output --partial ' "started_epoch"'
512+ # Integer — no quotes around the value. Matches lines like:
513+ # "started_epoch": 1744116606,
514+ [[ " $output " =~ \" started_epoch\" :[[:space:]]+[0-9]+ ]]
515+ # If python3 available, cross-check epoch equals date -d on started.
516+ if command -v python3 > /dev/null 2>&1 ; then
517+ local check
518+ check=$( echo " $output " | python3 -c '
519+ import sys, json, subprocess
520+ d = json.load(sys.stdin)
521+ bad = []
522+ for r in d["reports"]:
523+ s, e = r.get("started"), r.get("started_epoch")
524+ if s is None or e is None or e == 0:
525+ continue
526+ p = subprocess.run(["date", "-d", s, "+%s"], capture_output=True, text=True)
527+ if p.returncode != 0:
528+ continue
529+ want = int(p.stdout.strip())
530+ if want != e:
531+ bad.append((r["scan_id"], s, e, want))
532+ print("OK" if not bad else "FAIL: " + repr(bad))
533+ ' )
534+ [[ " $check " == " OK" ]]
535+ fi
536+ }
537+
538+ # --- Test 30: --json-report list legacy entries carry started_epoch ---
539+ # Regression guard for issue #483: pass 2 (legacy session files not in
540+ # session.index) must derive started_epoch from scan_start_hr, not silently
541+ # omit the field. Also asserts the legacy "source" tag is still emitted.
542+ @test " --json-report list legacy session reports[] include started_epoch" {
543+ local sessdir=" $LMD_INSTALL /sess"
544+ local sid=" 991231-2359.99990"
545+ cat > " $sessdir /session.$sid " << 'LEGACY '
546+ HOST: testhost.example.com
547+ SCAN ID: 991231-2359.99990
548+ STARTED: Dec 31 2099 23:59:59 +0000
549+ COMPLETED: Jan 01 2100 00:00:05 +0000
550+ ELAPSED: 6s [find: 1s]
551+ PATH: /home/testuser
552+ TOTAL FILES: 42
553+ TOTAL HITS: 0
554+ TOTAL CLEANED: 0
555+
556+ FILE HIT LIST:
557+ LEGACY
558+ run maldet --json-report list
559+ rm -f " $sessdir /session.$sid "
560+ assert_success
561+ assert_output --partial ' "991231-2359.99990"'
562+ assert_output --partial ' "source": "legacy"'
563+ # Epoch of Dec 31 2099 23:59:59 UTC = 4102444799. Non-zero value proves
564+ # pass 2 derivation ran. Loose assertion (just >= year-2099 epoch) to
565+ # avoid timezone-induced brittleness on test runners.
566+ if command -v python3 > /dev/null 2>&1 ; then
567+ local got
568+ got=$( echo " $output " | python3 -c '
569+ import sys, json
570+ d = json.load(sys.stdin)
571+ e = next((r["started_epoch"] for r in d["reports"]
572+ if r["scan_id"] == "991231-2359.99990"), None)
573+ print(e if e is not None else "MISSING")
574+ ' )
575+ [[ " $got " != " MISSING" ]]
576+ [[ " $got " -gt 4000000000 ]]
577+ fi
578+ }
0 commit comments