@@ -2,6 +2,7 @@ package custom_detectors
22
33import (
44 "context"
5+ "strings"
56 "testing"
67
78 "github.com/google/go-cmp/cmp"
@@ -559,6 +560,153 @@ func TestDetectorValidations(t *testing.T) {
559560 }
560561}
561562
563+ func TestNewWebhookCustomRegex_Validation (t * testing.T ) {
564+ t .Parallel ()
565+
566+ // A known-good baseline; each test case mutates exactly one thing to trigger a specific validator.
567+ base := func () * custom_detectorspb.CustomRegex {
568+ return & custom_detectorspb.CustomRegex {
569+ Name : "ok" ,
570+ Keywords : []string {"kw" },
571+ Regex : map [string ]string {
572+ "main" : `\btoken_[a-z]+\b` ,
573+ },
574+ PrimaryRegexName : "main" ,
575+ ExcludeRegexesCapture : []string {
576+ `^skip_.*$` ,
577+ },
578+ ExcludeRegexesMatch : []string {
579+ `^ignore_.*$` ,
580+ },
581+ Verify : []* custom_detectorspb.VerifierConfig {
582+ {
583+ Endpoint : "https://example.com/verify" ,
584+ Unsafe : false ,
585+ Headers : []string {"Authorization: Bearer x" },
586+ },
587+ },
588+ }
589+ }
590+
591+ tests := []struct {
592+ name string
593+ mutate func (* custom_detectorspb.CustomRegex )
594+ wantErr bool
595+ wantErrSubstr string // substring expected in error
596+ }{
597+ {
598+ name : "Validate everything ok" ,
599+ mutate : func (pb * custom_detectorspb.CustomRegex ) {},
600+ },
601+ {
602+ name : "ValidateKeywords: no keywords" ,
603+ mutate : func (pb * custom_detectorspb.CustomRegex ) {
604+ pb .Keywords = nil
605+ },
606+ wantErr : true ,
607+ wantErrSubstr : "no keywords" ,
608+ },
609+ {
610+ name : "ValidateKeywords: empty keyword" ,
611+ mutate : func (pb * custom_detectorspb.CustomRegex ) {
612+ pb .Keywords = []string {"" }
613+ },
614+ wantErr : true ,
615+ wantErrSubstr : "empty keyword" ,
616+ },
617+ {
618+ name : "ValidateRegex: no regex" ,
619+ mutate : func (pb * custom_detectorspb.CustomRegex ) {
620+ pb .Regex = nil
621+ },
622+ wantErr : true ,
623+ wantErrSubstr : "no regex" ,
624+ },
625+ {
626+ name : "ValidateRegex: invalid regex in map" ,
627+ mutate : func (pb * custom_detectorspb.CustomRegex ) {
628+ pb .Regex = map [string ]string {"main" : "(" } // invalid
629+ },
630+ wantErr : true ,
631+ wantErrSubstr : "regex 'main':" ,
632+ },
633+ {
634+ name : "ValidateRegexSlice: invalid exclude_regexes_capture" ,
635+ mutate : func (pb * custom_detectorspb.CustomRegex ) {
636+ pb .ExcludeRegexesCapture = []string {"(" } // invalid
637+ },
638+ wantErr : true ,
639+ wantErrSubstr : "regex '1':" ,
640+ },
641+ {
642+ name : "ValidateRegexSlice: invalid exclude_regexes_match" ,
643+ mutate : func (pb * custom_detectorspb.CustomRegex ) {
644+ pb .ExcludeRegexesMatch = []string {"(" } // invalid
645+ },
646+ wantErr : true ,
647+ wantErrSubstr : "regex '1':" ,
648+ },
649+ {
650+ name : "ValidatePrimaryRegexName: unknown primary regex name" ,
651+ mutate : func (pb * custom_detectorspb.CustomRegex ) {
652+ pb .PrimaryRegexName = "does-not-exist"
653+ },
654+ wantErr : true ,
655+ wantErrSubstr : `unknown primary regex name: "does-not-exist"` ,
656+ },
657+ {
658+ name : "ValidateVerifyEndpoint: empty endpoint" ,
659+ mutate : func (pb * custom_detectorspb.CustomRegex ) {
660+ pb .Verify = []* custom_detectorspb.VerifierConfig {
661+ {Endpoint : "" , Unsafe : false , Headers : []string {"A: b" }},
662+ }
663+ },
664+ wantErr : true ,
665+ wantErrSubstr : "no endpoint" ,
666+ },
667+ {
668+ name : "ValidateVerifyEndpoint: http endpoint without unsafe=true" ,
669+ mutate : func (pb * custom_detectorspb.CustomRegex ) {
670+ pb .Verify = []* custom_detectorspb.VerifierConfig {
671+ {Endpoint : "http://example.com/verify" , Unsafe : false , Headers : []string {"A: b" }},
672+ }
673+ },
674+ wantErr : true ,
675+ wantErrSubstr : "http endpoint must have unsafe=true" ,
676+ },
677+ {
678+ name : "ValidateVerifyHeaders: header missing colon" ,
679+ mutate : func (pb * custom_detectorspb.CustomRegex ) {
680+ pb .Verify = []* custom_detectorspb.VerifierConfig {
681+ {Endpoint : "https://example.com/verify" , Unsafe : false , Headers : []string {"Authorization Bearer x" }},
682+ }
683+ },
684+ wantErr : true ,
685+ wantErrSubstr : `must contain a colon` ,
686+ },
687+ }
688+
689+ for _ , tt := range tests {
690+ t .Run (tt .name , func (t * testing.T ) {
691+ t .Parallel ()
692+
693+ pb := base ()
694+ tt .mutate (pb )
695+
696+ got , err := NewWebhookCustomRegex (pb )
697+ if (err != nil ) != tt .wantErr {
698+ t .Fatalf ("expected error=%v, got error=%v (result=%#v)" , tt .wantErr , err != nil , got )
699+ }
700+ if tt .wantErr && got != nil {
701+ t .Fatalf ("expected nil result on error, got=%#v" , got )
702+ }
703+ if tt .wantErr && ! strings .Contains (err .Error (), tt .wantErrSubstr ) {
704+ t .Fatalf ("error mismatch:\n got: %q\n want substring: %q" , err .Error (), tt .wantErrSubstr )
705+ }
706+ })
707+ }
708+ }
709+
562710func BenchmarkProductIndices (b * testing.B ) {
563711 for i := 0 ; i < b .N ; i ++ {
564712 _ = productIndices (3 , 2 , 6 )
0 commit comments