@@ -618,6 +618,141 @@ func TestWatch(t *testing.T) {
618618 }
619619}
620620
621+ func TestUpdateFlagSetIdScoping (t * testing.T ) {
622+ t .Parallel ()
623+
624+ const src = "src1"
625+ sources := []string {src }
626+
627+ type updateStep struct {
628+ flags []model.Flag
629+ metadata model.Metadata
630+ }
631+
632+ tests := []struct {
633+ name string
634+ updates []updateStep
635+ wantPresent []string // "flagSetId/key" entries expected in the store
636+ wantAbsent []string // "flagSetId/key" entries expected to be gone
637+ }{
638+ {
639+ name : "per-flagSetId update preserves flags from other flagSetIds" ,
640+ updates : []updateStep {
641+ {flags : []model.Flag {{Key : "flagA1" }, {Key : "flagA2" }}, metadata : model.Metadata {"flagSetId" : "A" }},
642+ {flags : []model.Flag {{Key : "flagB1" }}, metadata : model.Metadata {"flagSetId" : "B" }},
643+ {flags : []model.Flag {{Key : "flagA1" }}, metadata : model.Metadata {"flagSetId" : "A" }},
644+ },
645+ wantPresent : []string {"A/flagA1" , "B/flagB1" },
646+ wantAbsent : []string {"A/flagA2" },
647+ },
648+ {
649+ name : "out-of-scope flag-level override persists when not in batch" ,
650+ updates : []updateStep {
651+ {flags : []model.Flag {{Key : "kept" }, {Key : "override" , Metadata : model.Metadata {"flagSetId" : "Y" }}}, metadata : model.Metadata {"flagSetId" : "X" }},
652+ {flags : []model.Flag {{Key : "kept" }}, metadata : model.Metadata {"flagSetId" : "X" }},
653+ },
654+ wantPresent : []string {"X/kept" , "Y/override" },
655+ },
656+ {
657+ name : "stale flag deleted when its flagSetId is in scope" ,
658+ updates : []updateStep {
659+ {flags : []model.Flag {{Key : "inX" }, {Key : "inY" , Metadata : model.Metadata {"flagSetId" : "Y" }}}, metadata : model.Metadata {"flagSetId" : "X" }},
660+ {flags : []model.Flag {{Key : "inY" , Metadata : model.Metadata {"flagSetId" : "Y" }}}, metadata : model.Metadata {"flagSetId" : "X" }},
661+ },
662+ wantPresent : []string {"Y/inY" },
663+ wantAbsent : []string {"X/inX" },
664+ },
665+ {
666+ name : "no flagSetId in metadata falls back to source-scoped deletion" ,
667+ updates : []updateStep {
668+ {flags : []model.Flag {{Key : "flagA" }}, metadata : model.Metadata {"flagSetId" : "A" }},
669+ {flags : []model.Flag {{Key : "flagB" }}, metadata : model.Metadata {"flagSetId" : "B" }},
670+ {flags : []model.Flag {}, metadata : nil },
671+ },
672+ wantAbsent : []string {"A/flagA" , "B/flagB" },
673+ },
674+ {
675+ name : "empty update with flagSetId clears only that set" ,
676+ updates : []updateStep {
677+ {flags : []model.Flag {{Key : "flagA" }}, metadata : model.Metadata {"flagSetId" : "A" }},
678+ {flags : []model.Flag {{Key : "flagB" }}, metadata : model.Metadata {"flagSetId" : "B" }},
679+ {flags : []model.Flag {}, metadata : model.Metadata {"flagSetId" : "A" }},
680+ },
681+ wantPresent : []string {"B/flagB" },
682+ wantAbsent : []string {"A/flagA" },
683+ },
684+ }
685+
686+ for _ , tt := range tests {
687+ tt := tt
688+ t .Run (tt .name , func (t * testing.T ) {
689+ t .Parallel ()
690+ s , err := NewStore (logger .NewLogger (nil , false ), sources )
691+ require .NoError (t , err )
692+
693+ for _ , step := range tt .updates {
694+ s .Update (src , step .flags , step .metadata )
695+ }
696+
697+ allFlags , _ , _ := s .GetAll (context .Background (), nil )
698+ flagKeys := make (map [string ]struct {}, len (allFlags ))
699+ for _ , f := range allFlags {
700+ flagKeys [f .FlagSetId + "/" + f .Key ] = struct {}{}
701+ }
702+
703+ for _ , key := range tt .wantPresent {
704+ assert .Contains (t , flagKeys , key )
705+ }
706+ for _ , key := range tt .wantAbsent {
707+ assert .NotContains (t , flagKeys , key )
708+ }
709+ })
710+ }
711+ }
712+
713+ func TestToLogStringCompound (t * testing.T ) {
714+ t .Parallel ()
715+
716+ tests := []struct {
717+ name string
718+ selector * Selector
719+ want string
720+ }{
721+ {
722+ name : "nil selector" ,
723+ selector : nil ,
724+ want : "<none>" ,
725+ },
726+ {
727+ name : "empty selector" ,
728+ selector : & Selector {indexMap : map [string ]string {}},
729+ want : "<none>" ,
730+ },
731+ {
732+ name : "single key" ,
733+ selector : & Selector {indexMap : map [string ]string {"source" : "mySource" }},
734+ want : "'source=mySource'" ,
735+ },
736+ {
737+ name : "compound selector" ,
738+ selector : & Selector {indexMap : map [string ]string {"flagSetId" : "abc" , "source" : "mySource" }},
739+ want : "'flagSetId=abc,source=mySource'" ,
740+ },
741+ {
742+ name : "three keys sorted" ,
743+ selector : & Selector {indexMap : map [string ]string {"source" : "s" , "key" : "k" , "flagSetId" : "f" }},
744+ want : "'flagSetId=f,key=k,source=s'" ,
745+ },
746+ }
747+
748+ for _ , tt := range tests {
749+ t .Run (tt .name , func (t * testing.T ) {
750+ got := tt .selector .ToLogString ()
751+ assert .Equal (t , tt .want , got )
752+ })
753+ }
754+ }
755+
621756func TestQueryMetadata (t * testing.T ) {
622757
623758 sourceA := "sourceA"
0 commit comments