| 1 | package release |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "encoding/json" |
| 6 | "errors" |
| 7 | "testing" |
| 8 | "time" |
| 9 | ) |
| 10 | |
| 11 | func TestSystemKeyShape(t *testing.T) { |
| 12 | if got := SystemKey("antivirus"); got != "system/antivirus.json" { |
| 13 | t.Fatalf("SystemKey = %q", got) |
| 14 | } |
| 15 | } |
| 16 | |
| 17 | // Absent is a state, not an error — and it means NOT ENABLED. A deployment |
| 18 | // that has never enabled scanning does not scan. |
| 19 | func TestGetAntivirusAbsentIsNotEnabled(t *testing.T) { |
| 20 | ctx := context.Background() |
| 21 | s := NewDirStore(t.TempDir()) |
| 22 | |
| 23 | av, etag, err := GetAntivirus(ctx, s) |
| 24 | if err != nil || av != nil || etag != "" { |
| 25 | t.Fatalf("absent record = %+v, %q, %v", av, etag, err) |
| 26 | } |
| 27 | on, err := AntivirusEnabled(ctx, s) |
| 28 | if err != nil || on { |
| 29 | t.Fatalf("AntivirusEnabled with no record = %v, %v", on, err) |
| 30 | } |
| 31 | } |
| 32 | |
| 33 | // An unknown schema refuses rather than guessing. A future schema 2 could mean |
| 34 | // anything, and the thing this record drives is whether the deployment reads |
| 35 | // tenant object bytes. |
| 36 | func TestAntivirusSchemaRefusesUnknown(t *testing.T) { |
| 37 | ctx := context.Background() |
| 38 | s := NewDirStore(t.TempDir()) |
| 39 | |
| 40 | body, _ := json.Marshal(map[string]any{"schema": 2, "enabled": true}) |
| 41 | if _, err := s.Put(ctx, SystemKey(AntivirusName), body, ""); err != nil { |
| 42 | t.Fatal(err) |
| 43 | } |
| 44 | |
| 45 | if _, _, err := GetAntivirus(ctx, s); err == nil { |
| 46 | t.Fatal("an unknown schema was read as if it were understood") |
| 47 | } |
| 48 | // And the folded helper must not turn that refusal into "disabled" — a |
| 49 | // caller acting on false would silently stop scanning. |
| 50 | if _, err := AntivirusEnabled(ctx, s); err == nil { |
| 51 | t.Fatal("AntivirusEnabled swallowed a schema refusal as disabled") |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | // A read error is not "disabled" either, for the same reason. |
| 56 | func TestAntivirusEnabledDoesNotSwallowErrors(t *testing.T) { |
| 57 | ctx := context.Background() |
| 58 | s := NewDirStore(t.TempDir()) |
| 59 | |
| 60 | body, _ := json.Marshal(map[string]any{"schema": 1, "enabled": true}) |
| 61 | if _, err := s.Put(ctx, SystemKey(AntivirusName), []byte(string(body)[:len(body)-3]), ""); err != nil { |
| 62 | t.Fatal(err) |
| 63 | } |
| 64 | if _, err := AntivirusEnabled(ctx, s); err == nil { |
| 65 | t.Fatal("malformed JSON was read as disabled rather than as an error") |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | func TestPutAntivirusCreateOnlyThenReplace(t *testing.T) { |
| 70 | ctx := context.Background() |
| 71 | s := NewDirStore(t.TempDir()) |
| 72 | now := time.Date(2026, 9, 2, 8, 0, 0, 0, time.UTC) |
| 73 | |
| 74 | etag, err := PutAntivirus(ctx, s, &AntivirusSettings{ |
| 75 | Enabled: true, Engine: "clamav", At: now, By: "op@example.com", |
| 76 | }, "") |
| 77 | if err != nil { |
| 78 | t.Fatal(err) |
| 79 | } |
| 80 | |
| 81 | // Create-only against an existing key conflicts — this is what makes the |
| 82 | // boot seed idempotent across two consoles and a restart loop. |
| 83 | if _, err := PutAntivirus(ctx, s, &AntivirusSettings{Enabled: false}, ""); !errors.Is(err, ErrCASConflict) { |
| 84 | t.Fatalf("second create-only write = %v, want ErrCASConflict", err) |
| 85 | } |
| 86 | |
| 87 | if _, err := PutAntivirus(ctx, s, &AntivirusSettings{ |
| 88 | Enabled: false, Engine: "clamav", At: now, By: "op@example.com", |
| 89 | }, etag); err != nil { |
| 90 | t.Fatalf("replace with the current etag: %v", err) |
| 91 | } |
| 92 | |
| 93 | av, _, err := GetAntivirus(ctx, s) |
| 94 | if err != nil || av == nil || av.Enabled { |
| 95 | t.Fatalf("after disable: %+v, %v", av, err) |
| 96 | } |
| 97 | if av.Schema != AntivirusSchema { |
| 98 | t.Fatalf("schema not stamped on write: %+v", av) |
| 99 | } |
| 100 | } |
| 101 | |