package main import ( "bytes" "strings" "testing" "time" ) func scanReplyFixture() apiScanReply { at := time.Date(2026, 9, 1, 10, 0, 0, 0, time.UTC) return apiScanReply{ App: "titogo", Backend: "aws", Engine: "clamav", Signature: "ClamAV 1.4.1/27500/" + time.Now().Format(time.ANSIC), MaxSignatureAgeSeconds: int64((168 * time.Hour) / time.Second), Scanning: &apiScanBlock{ Requested: &apiScanRequested{At: at, By: "m@example.com"}, Enabled: &apiScanEnabled{Engine: "clamav", At: at, By: "op@example.com"}, }, } } // The audit line names BOTH numbers. A bucket of over-size objects reporting // "1000 scanned" and nothing else would be true and misleading — nobody looked // at the other two hundred, and the sentence that hides that is the one an // auditor is entitled to catch. func TestPrintScanStatusNamesUnscannableCount(t *testing.T) { res := scanReplyFixture() res.Coverage = &apiScanCoverage{LastComplete: &apiSweep{ At: time.Now(), Objects: 1200, Scanned: 1000, Unscanned: 200, Skipped: 195, Failed: 5, }} var b bytes.Buffer printScanStatus(&b, res) out := b.String() if !strings.Contains(out, "1000 of 1200 objects scanned") { t.Fatalf("coverage line missing the two headline numbers:\n%s", out) } if !strings.Contains(out, "200 not scanned") { t.Fatalf("the unscanned count is not stated:\n%s", out) } if !strings.Contains(out, "195 too large or refused") { t.Fatalf("the breakdown of what was not scanned is missing:\n%s", out) } } // An encrypted store never gets the coverage sentence — not even a zeroed one. // A scanner cannot read ciphertext, so any number here would be a // true-looking claim about bytes nobody examined. func TestPrintScanStatusRefusesCoverageLineWhenEncrypted(t *testing.T) { res := scanReplyFixture() res.Scanning.Requested.Encrypted = true // Even with a rollup present — which should never happen, but the print // layer must not depend on the server having got it right. res.Coverage = &apiScanCoverage{LastComplete: &apiSweep{Objects: 10, Scanned: 10}} var b bytes.Buffer printScanStatus(&b, res) out := b.String() if strings.Contains(out, "objects scanned") { t.Fatalf("an encrypted store was given a coverage claim:\n%s", out) } if !strings.Contains(out, "client-encrypted") || !strings.Contains(out, "NO coverage") { t.Fatalf("the output does not say why there is no coverage:\n%s", out) } } // A sweep spanning ticks says so, and says which numbers are stale, rather // than implying the partial count is the answer. func TestPrintScanStatusShowsProgressMidSweep(t *testing.T) { res := scanReplyFixture() res.Coverage = &apiScanCoverage{ Current: &apiSweep{StartedAt: time.Now().Add(-2 * time.Hour), Objects: 300}, LastComplete: &apiSweep{At: time.Now().Add(-24 * time.Hour), Objects: 1200, Scanned: 1200}, } var b bytes.Buffer printScanStatus(&b, res) out := b.String() if !strings.Contains(out, "sweep in progress") { t.Fatalf("an in-flight sweep is not mentioned:\n%s", out) } if !strings.Contains(out, "last COMPLETE sweep") { t.Fatalf("the output does not say which numbers are stale:\n%s", out) } if !strings.Contains(out, "1200 of 1200 objects scanned") { t.Fatalf("the completed coverage is not shown:\n%s", out) } } // A first sweep that has not finished claims nothing at all. Partial counts // with no complete sweep behind them are not coverage. func TestPrintScanStatusClaimsNothingBeforeFirstCompleteSweep(t *testing.T) { res := scanReplyFixture() res.Coverage = &apiScanCoverage{Current: &apiSweep{StartedAt: time.Now(), Objects: 40}} var b bytes.Buffer printScanStatus(&b, res) out := b.String() if strings.Contains(out, "objects scanned") { t.Fatalf("a partial first sweep was printed as coverage:\n%s", out) } if !strings.Contains(out, "no coverage can be claimed") { t.Fatalf("the output does not refuse the claim outright:\n%s", out) } } // Old signatures are called out. A clean verdict from three-month-old // definitions is a weaker claim than a fresh one. func TestPrintScanStatusWarnsOnStaleSignatures(t *testing.T) { res := scanReplyFixture() old := time.Now().Add(-90 * 24 * time.Hour).Format(time.ANSIC) res.Signature = "ClamAV 1.4.1/27000/" + old res.Coverage = &apiScanCoverage{LastComplete: &apiSweep{At: time.Now(), Objects: 1, Scanned: 1}} var b bytes.Buffer printScanStatus(&b, res) out := b.String() if !strings.Contains(out, "days old") { t.Fatalf("stale signatures were not called out:\n%s", out) } } // A signature line we cannot parse produces NO age claim. Inventing one would // undermine the very line it was meant to strengthen. func TestSignatureAgeRefusesToGuess(t *testing.T) { for _, v := range []string{"ClamAV 1.4.1", "", "something/else", "ClamAV/27000/not a date"} { if _, ok := signatureAge(v, time.Now()); ok { t.Fatalf("signatureAge guessed an age from %q", v) } } good := "ClamAV 1.4.1/27500/" + time.Now().Add(-48*time.Hour).Format(time.ANSIC) age, ok := signatureAge(good, time.Now()) if !ok || age < 47*time.Hour || age > 49*time.Hour { t.Fatalf("signatureAge(%q) = %v, %v", good, age, ok) } } // An unreachable engine is reported as such, not silently omitted. "No new // verdicts are being reached" is the fact an operator needs. func TestPrintScanStatusReportsUnreachableEngine(t *testing.T) { res := scanReplyFixture() res.Signature = "" res.EngineError = "dial unix /run/clamav/clamd.ctl: connection refused" var b bytes.Buffer printScanStatus(&b, res) out := b.String() if !strings.Contains(out, "not answering") { t.Fatalf("an unreachable engine was not reported:\n%s", out) } } // A store nobody has requested scanning for says exactly that, and names the // verb that would start it. func TestPrintScanStatusUnrequested(t *testing.T) { res := apiScanReply{App: "titogo", Backend: "aws"} var b bytes.Buffer printScanStatus(&b, res) out := b.String() if !strings.Contains(out, "not requested") { t.Fatalf("output = %q", out) } if !strings.Contains(out, "carlos store scan request --app titogo") { t.Fatalf("the output does not name the verb that starts it:\n%s", out) } } // Enabled but not enforcing is stated plainly. An operator reading "engine: // clamav" and assuming infected objects are blocked would be wrong, and the // difference is the whole of what enforcement buys. func TestPrintScanStatusDistinguishesEnabledFromEnforcing(t *testing.T) { res := scanReplyFixture() res.Coverage = &apiScanCoverage{LastComplete: &apiSweep{At: time.Now(), Objects: 2, Scanned: 2}} var b bytes.Buffer printScanStatus(&b, res) if !strings.Contains(b.String(), "not enforcing") { t.Fatalf("an enabled-but-not-enforcing store did not say so:\n%s", b.String()) } res.Scanning.Enforce = &apiScanEnforce{Key: "carlos-scan", Value: "infected"} var b2 bytes.Buffer printScanStatus(&b2, res) if !strings.Contains(b2.String(), "cannot read an object tagged carlos-scan=infected") { t.Fatalf("an enforcing store did not say what is blocked:\n%s", b2.String()) } }