@@ -823,6 +823,204 @@ func TestMuteStageWithSilences(t *testing.T) {
823823 }
824824}
825825
826+ func TestMuteStageWithSendResolved (t * testing.T ) {
827+ // Create silences
828+ silences , err := silence .New (silence.Options {Retention : time .Hour })
829+ require .NoError (t , err )
830+
831+ sil := & silencepb.Silence {
832+ EndsAt : utcNow ().Add (time .Hour ),
833+ Matchers : []* silencepb.Matcher {{Name : "mute" , Pattern : "me" }},
834+ }
835+ require .NoError (t , silences .Set (sil ))
836+
837+ reg := prometheus .NewRegistry ()
838+ marker := types .NewMarker (reg )
839+ silencer := silence .NewSilencer (silences , marker , promslog .NewNopLogger ())
840+
841+ // Create notification log
842+ nflog , err := nflog .New (nflog.Options {
843+ Retention : time .Hour ,
844+ Logger : promslog .NewNopLogger (),
845+ Metrics : reg ,
846+ })
847+ require .NoError (t , err )
848+
849+ // Create receivers with different send_resolved settings
850+ receiversWithSendResolved := map [string ][]Integration {
851+ "with_resolved" : {
852+ NewIntegration (& testNotifier {}, sendResolved (true ), "webhook" , 0 , "with_resolved" ),
853+ },
854+ "without_resolved" : {
855+ NewIntegration (& testNotifier {}, sendResolved (false ), "webhook" , 0 , "without_resolved" ),
856+ },
857+ "mixed" : {
858+ NewIntegration (& testNotifier {}, sendResolved (true ), "webhook" , 0 , "mixed" ),
859+ NewIntegration (& testNotifier {}, sendResolved (false ), "email" , 1 , "mixed" ),
860+ },
861+ }
862+
863+ metrics := NewMetrics (reg , featurecontrol.NoopFlags {})
864+ stage := NewMuteStageWithSendResolved (silencer , nflog , receiversWithSendResolved , metrics )
865+
866+ // Test 1: Silenced firing alerts are filtered out
867+ t .Run ("silenced firing alerts are filtered" , func (t * testing.T ) {
868+ firingAlert := & types.Alert {
869+ Alert : model.Alert {
870+ Labels : model.LabelSet {"mute" : "me" , "alertname" : "test" },
871+ EndsAt : time.Time {}, // Firing alert has zero EndsAt
872+ StartsAt : time .Now ().Add (- time .Hour ),
873+ },
874+ }
875+
876+ ctx := context .Background ()
877+ ctx = WithReceiverName (ctx , "with_resolved" )
878+ ctx = WithGroupKey (ctx , "test-group" )
879+
880+ _ , alerts , err := stage .Exec (ctx , promslog .NewNopLogger (), firingAlert )
881+ require .NoError (t , err )
882+ require .Empty (t , alerts , "firing silenced alerts should be filtered" )
883+ })
884+
885+ // Test 2: Silenced resolved alerts without prior notification are filtered
886+ t .Run ("silenced resolved alerts without prior notification are filtered" , func (t * testing.T ) {
887+ resolvedAlert := & types.Alert {
888+ Alert : model.Alert {
889+ Labels : model.LabelSet {"mute" : "me" , "alertname" : "test2" },
890+ StartsAt : time .Now ().Add (- 2 * time .Hour ),
891+ EndsAt : time .Now ().Add (- time .Hour ),
892+ },
893+ }
894+
895+ ctx := context .Background ()
896+ ctx = WithReceiverName (ctx , "with_resolved" )
897+ ctx = WithGroupKey (ctx , "test-group-2" )
898+
899+ _ , alerts , err := stage .Exec (ctx , promslog .NewNopLogger (), resolvedAlert )
900+ require .NoError (t , err )
901+ require .Empty (t , alerts , "resolved silenced alerts without prior notification should be filtered" )
902+ })
903+
904+ // Test 3: Silenced resolved alerts with prior notification and send_resolved=true are allowed
905+ t .Run ("silenced resolved alerts with prior notification and send_resolved are allowed" , func (t * testing.T ) {
906+ resolvedAlert := & types.Alert {
907+ Alert : model.Alert {
908+ Labels : model.LabelSet {"mute" : "me" , "alertname" : "test3" },
909+ StartsAt : time .Now ().Add (- 2 * time .Hour ),
910+ EndsAt : time .Now ().Add (- time .Hour ),
911+ },
912+ }
913+
914+ groupKey := "test-group-3"
915+ receiverName := "with_resolved"
916+
917+ // Log a previous notification for this alert
918+ recv := & nflogpb.Receiver {
919+ GroupName : receiverName ,
920+ Integration : "webhook" ,
921+ Idx : 0 ,
922+ }
923+ err := nflog .Log (recv , groupKey , []uint64 {1234 }, []uint64 {}, time .Hour )
924+ require .NoError (t , err )
925+
926+ ctx := context .Background ()
927+ ctx = WithReceiverName (ctx , receiverName )
928+ ctx = WithGroupKey (ctx , groupKey )
929+
930+ _ , alerts , err := stage .Exec (ctx , promslog .NewNopLogger (), resolvedAlert )
931+ require .NoError (t , err )
932+ require .Len (t , alerts , 1 , "resolved silenced alerts with prior notification should be allowed when send_resolved=true" )
933+ })
934+
935+ // Test 4: Silenced resolved alerts with send_resolved=false are filtered even with prior notification
936+ t .Run ("silenced resolved alerts without send_resolved are filtered" , func (t * testing.T ) {
937+ resolvedAlert := & types.Alert {
938+ Alert : model.Alert {
939+ Labels : model.LabelSet {"mute" : "me" , "alertname" : "test4" },
940+ StartsAt : time .Now ().Add (- 2 * time .Hour ),
941+ EndsAt : time .Now ().Add (- time .Hour ),
942+ },
943+ }
944+
945+ groupKey := "test-group-4"
946+ receiverName := "without_resolved"
947+
948+ // Log a previous notification for this alert
949+ recv := & nflogpb.Receiver {
950+ GroupName : receiverName ,
951+ Integration : "webhook" ,
952+ Idx : 0 ,
953+ }
954+ err := nflog .Log (recv , groupKey , []uint64 {5678 }, []uint64 {}, time .Hour )
955+ require .NoError (t , err )
956+
957+ ctx := context .Background ()
958+ ctx = WithReceiverName (ctx , receiverName )
959+ ctx = WithGroupKey (ctx , groupKey )
960+
961+ _ , alerts , err := stage .Exec (ctx , promslog .NewNopLogger (), resolvedAlert )
962+ require .NoError (t , err )
963+ require .Empty (t , alerts , "resolved silenced alerts should be filtered when send_resolved=false" )
964+ })
965+
966+ // Test 5: Non-silenced alerts pass through
967+ t .Run ("non-silenced alerts pass through" , func (t * testing.T ) {
968+ firingAlert := & types.Alert {
969+ Alert : model.Alert {
970+ Labels : model.LabelSet {"not" : "muted" , "alertname" : "test5" },
971+ EndsAt : time.Time {},
972+ StartsAt : time .Now ().Add (- time .Hour ),
973+ },
974+ }
975+ resolvedAlert := & types.Alert {
976+ Alert : model.Alert {
977+ Labels : model.LabelSet {"not" : "muted" , "alertname" : "test6" },
978+ StartsAt : time .Now ().Add (- 2 * time .Hour ),
979+ EndsAt : time .Now ().Add (- time .Hour ),
980+ },
981+ }
982+
983+ ctx := context .Background ()
984+ ctx = WithReceiverName (ctx , "with_resolved" )
985+ ctx = WithGroupKey (ctx , "test-group-5" )
986+
987+ _ , alerts , err := stage .Exec (ctx , promslog .NewNopLogger (), firingAlert , resolvedAlert )
988+ require .NoError (t , err )
989+ require .Len (t , alerts , 2 , "non-silenced alerts should pass through" )
990+ })
991+
992+ // Test 6: Mixed receiver with at least one send_resolved=true integration
993+ t .Run ("mixed receiver with send_resolved allows resolved silenced alerts" , func (t * testing.T ) {
994+ resolvedAlert := & types.Alert {
995+ Alert : model.Alert {
996+ Labels : model.LabelSet {"mute" : "me" , "alertname" : "test7" },
997+ StartsAt : time .Now ().Add (- 2 * time .Hour ),
998+ EndsAt : time .Now ().Add (- time .Hour ),
999+ },
1000+ }
1001+
1002+ groupKey := "test-group-7"
1003+ receiverName := "mixed"
1004+
1005+ // Log a previous notification for the webhook integration (send_resolved=true)
1006+ recv := & nflogpb.Receiver {
1007+ GroupName : receiverName ,
1008+ Integration : "webhook" ,
1009+ Idx : 0 ,
1010+ }
1011+ err := nflog .Log (recv , groupKey , []uint64 {9999 }, []uint64 {}, time .Hour )
1012+ require .NoError (t , err )
1013+
1014+ ctx := context .Background ()
1015+ ctx = WithReceiverName (ctx , receiverName )
1016+ ctx = WithGroupKey (ctx , groupKey )
1017+
1018+ _ , alerts , err := stage .Exec (ctx , promslog .NewNopLogger (), resolvedAlert )
1019+ require .NoError (t , err )
1020+ require .Len (t , alerts , 1 , "resolved silenced alerts should be allowed when at least one integration has send_resolved=true" )
1021+ })
1022+ }
1023+
8261024func TestTimeMuteStage (t * testing.T ) {
8271025 sydney , err := time .LoadLocation ("Australia/Sydney" )
8281026 if err != nil {
@@ -1080,3 +1278,9 @@ func BenchmarkHashAlert(b *testing.B) {
10801278 hashAlert (alert )
10811279 }
10821280}
1281+
1282+ type testNotifier struct {}
1283+
1284+ func (n * testNotifier ) Notify (ctx context.Context , alerts ... * types.Alert ) (bool , error ) {
1285+ return true , nil
1286+ }
0 commit comments