package rules import ( "encoding/json" "testing" ) func TestArchiversCanBeNull(t *testing.T) { tests := []struct { name string json string wantNil bool check func(t *testing.T, rule Rule) }{ { name: "mimetype rule with null extractors", json: `{"mimetype": "text/html", "extractors": null}`, wantNil: true, check: func(t *testing.T, rule Rule) { mr, ok := rule.(*MimetypeRule) if !ok { t.Fatalf("expected MimetypeRule, got %T", rule) } if mr.Archivers != nil { t.Errorf("Archivers = %v, want nil", mr.Archivers) } }, }, { name: "mimetype rule without extractors field", json: `{"mimetype": "text/html"}`, wantNil: true, check: func(t *testing.T, rule Rule) { mr, ok := rule.(*MimetypeRule) if !ok { t.Fatalf("expected MimetypeRule, got %T", rule) } if mr.Archivers != nil { t.Errorf("Archivers = %v, want nil", mr.Archivers) } }, }, { name: "hostname rule with null extractors", json: `{"hostname": "example.com", "extractors": null}`, wantNil: true, check: func(t *testing.T, rule Rule) { hr, ok := rule.(*HostnameRule) if !ok { t.Fatalf("expected HostnameRule, got %T", rule) } if hr.Archivers != nil { t.Errorf("Archivers = %v, want nil", hr.Archivers) } }, }, { name: "mimetype rule with extractors", json: `{"mimetype": "text/html", "extractors": [{"key": "obelisk"}]}`, wantNil: false, check: func(t *testing.T, rule Rule) { mr, ok := rule.(*MimetypeRule) if !ok { t.Fatalf("expected MimetypeRule, got %T", rule) } if mr.Archivers == nil { t.Error("Archivers is nil, want non-nil") } if len(mr.Archivers) != 1 || mr.Archivers[0].Key != "obelisk" { t.Errorf("Archivers = %v, want [{key: obelisk}]", mr.Archivers) } }, }, { name: "and rule with null extractors in child", json: `{"and": [{"mimetype": "text/html"}], "extractors": [{"key": "obelisk"}]}`, wantNil: false, check: func(t *testing.T, rule Rule) { ar, ok := rule.(*AndRule) if !ok { t.Fatalf("expected AndRule, got %T", rule) } if len(ar.Rules) != 1 { t.Errorf("Rules length = %d, want 1", len(ar.Rules)) } // Child rule should have nil extractors mr, ok := ar.Rules[0].(*MimetypeRule) if !ok { t.Fatalf("expected MimetypeRule child, got %T", ar.Rules[0]) } if mr.Archivers != nil { t.Errorf("Child rule Archivers = %v, want nil", mr.Archivers) } // Root rule should have extractors if ar.Archivers == nil { t.Error("Root rule Archivers is nil, want non-nil") } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rule, err := UnmarshalRule([]byte(tt.json)) if err != nil { t.Fatalf("UnmarshalRule() error = %v", err) } if rule == nil { t.Fatal("UnmarshalRule() returned nil rule") } if tt.check != nil { tt.check(t, rule) } }) } } func TestArchiversOmitEmpty(t *testing.T) { // Test that rules without extractors don't include the field in JSON tests := []struct { name string rule Rule check func(t *testing.T, jsonStr string) }{ { name: "mimetype rule without extractors", rule: &MimetypeRule{ Mimetype: "text/html", Archivers: nil, }, check: func(t *testing.T, jsonStr string) { // Should not contain "extractors" field when nil if jsonStr == "" { t.Error("JSON is empty") } // When extractors is nil and omitempty is set, it should be omitted // But Go's json package will include null if the field is explicitly set // Let's check that it's either omitted or null hasExtractors := contains(jsonStr, "extractors") if hasExtractors && !contains(jsonStr, `"extractors":null`) { t.Errorf("JSON contains extractors but not as null: %s", jsonStr) } }, }, { name: "hostname rule without extractors", rule: &HostnameRule{ Hostname: "example.com", Archivers: nil, }, check: func(t *testing.T, jsonStr string) { hasExtractors := contains(jsonStr, "extractors") if hasExtractors && !contains(jsonStr, `"extractors":null`) { t.Errorf("JSON contains extractors but not as null: %s", jsonStr) } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { jsonBytes, err := json.Marshal(tt.rule) if err != nil { t.Fatalf("Marshal() error = %v", err) } if tt.check != nil { tt.check(t, string(jsonBytes)) } }) } } func contains(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }