package rules import ( "encoding/json" "testing" ) func TestUnmarshalRule(t *testing.T) { tests := []struct { name string json string wantErr bool check func(t *testing.T, rule Rule) }{ { name: "mimetype rule", json: `{"mimetype": "text/html", "extractors": [{"key": "obelisk"}]}`, wantErr: false, check: func(t *testing.T, rule Rule) { mr, ok := rule.(*MimetypeRule) if !ok { t.Fatalf("expected MimetypeRule, got %T", rule) } if mr.Mimetype != "text/html" { t.Errorf("Mimetype = %q, want %q", mr.Mimetype, "text/html") } if len(mr.Archivers) != 1 || mr.Archivers[0].Key != "obelisk" { t.Errorf("Archivers = %v, want [{key: obelisk}]", mr.Archivers) } }, }, { name: "hostname rule", json: `{"hostname": "example.com", "extractors": [{"key": "direct_download"}]}`, wantErr: false, check: func(t *testing.T, rule Rule) { hr, ok := rule.(*HostnameRule) if !ok { t.Fatalf("expected HostnameRule, got %T", rule) } if hr.Hostname != "example.com" { t.Errorf("Hostname = %q, want %q", hr.Hostname, "example.com") } }, }, { name: "and rule", json: `{"and": [{"mimetype": "text/html"}, {"hostname": "example.com"}], "extractors": [{"key": "obelisk"}]}`, wantErr: 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) != 2 { t.Errorf("Rules length = %d, want 2", len(ar.Rules)) } }, }, { name: "or rule", json: `{"or": [{"mimetype": "text/html"}, {"hostname": "example.com"}], "extractors": [{"key": "obelisk"}]}`, wantErr: false, check: func(t *testing.T, rule Rule) { or, ok := rule.(*OrRule) if !ok { t.Fatalf("expected OrRule, got %T", rule) } if len(or.Rules) != 2 { t.Errorf("Rules length = %d, want 2", len(or.Rules)) } }, }, { name: "nested and/or rules", json: `{"and": [{"or": [{"mimetype": "text/html"}, {"mimetype": "application/json"}]}, {"hostname": "example.com"}], "extractors": [{"key": "obelisk"}]}`, wantErr: 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) != 2 { t.Errorf("Rules length = %d, want 2", len(ar.Rules)) } or, ok := ar.Rules[0].(*OrRule) if !ok { t.Fatalf("expected first child to be OrRule, got %T", ar.Rules[0]) } if len(or.Rules) != 2 { t.Errorf("OrRule children length = %d, want 2", len(or.Rules)) } }, }, { name: "invalid rule - no type field", json: `{"extractors": [{"key": "obelisk"}]}`, wantErr: true, }, { name: "invalid json", json: `{invalid json}`, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rule, err := UnmarshalRule([]byte(tt.json)) if (err != nil) != tt.wantErr { t.Errorf("UnmarshalRule() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr { return } if rule == nil { t.Fatal("UnmarshalRule() returned nil rule") } if tt.check != nil { tt.check(t, rule) } }) } } func TestUnmarshalRuleConfig(t *testing.T) { tests := []struct { name string json string wantErr bool check func(t *testing.T, config *RuleConfig) }{ { name: "valid config", json: `{"rules": [{"mimetype": "text/html", "extractors": [{"key": "obelisk"}]}], "default_extractors": [{"key": "direct_download"}]}`, wantErr: false, check: func(t *testing.T, config *RuleConfig) { if len(config.Rules) != 1 { t.Errorf("Rules length = %d, want 1", len(config.Rules)) } if len(config.DefaultArchivers) != 1 || config.DefaultArchivers[0].Key != "direct_download" { t.Errorf("DefaultArchivers = %v, want [{key: direct_download}]", config.DefaultArchivers) } }, }, { name: "empty rules", json: `{"rules": [], "default_extractors": [{"key": "obelisk"}]}`, wantErr: false, check: func(t *testing.T, config *RuleConfig) { if len(config.Rules) != 0 { t.Errorf("Rules length = %d, want 0", len(config.Rules)) } }, }, { name: "invalid json", json: `{invalid json}`, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config, err := UnmarshalRuleConfig([]byte(tt.json)) if (err != nil) != tt.wantErr { t.Errorf("UnmarshalRuleConfig() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr { return } if config == nil { t.Fatal("UnmarshalRuleConfig() returned nil config") } if tt.check != nil { tt.check(t, config) } }) } } func TestUnmarshalRule_RoundTrip(t *testing.T) { // Test that we can marshal and unmarshal rules correctly original := &MimetypeRule{ Mimetype: "application/*", Archivers: []ArchiverConfig{ {Key: "direct_download", Config: map[string]any{"timeout": "5m"}}, }, } // Marshal data, err := json.Marshal(original) if err != nil { t.Fatalf("Marshal() error = %v", err) } // Unmarshal unmarshaled, err := UnmarshalRule(data) if err != nil { t.Fatalf("UnmarshalRule() error = %v", err) } // Check mr, ok := unmarshaled.(*MimetypeRule) if !ok { t.Fatalf("expected MimetypeRule, got %T", unmarshaled) } if mr.Mimetype != original.Mimetype { t.Errorf("Mimetype = %q, want %q", mr.Mimetype, original.Mimetype) } if len(mr.Archivers) != len(original.Archivers) { t.Errorf("Archivers length = %d, want %d", len(mr.Archivers), len(original.Archivers)) } }