go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/luci_notify/internal/alerts/alerts_test.go (about)

     1  // Copyright 2024 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package alerts
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  	"time"
    21  
    22  	"cloud.google.com/go/spanner"
    23  
    24  	"go.chromium.org/luci/server/span"
    25  
    26  	"go.chromium.org/luci/luci_notify/internal/testutil"
    27  
    28  	. "github.com/smartystreets/goconvey/convey"
    29  	. "go.chromium.org/luci/common/testing/assertions"
    30  )
    31  
    32  func TestValidation(t *testing.T) {
    33  	Convey("Validate", t, func() {
    34  		Convey("valid", func() {
    35  			err := Validate(NewAlertBuilder().Build())
    36  			So(err, ShouldBeNil)
    37  		})
    38  		Convey("bug", func() {
    39  			Convey("must not be negative", func() {
    40  				err := Validate(NewAlertBuilder().WithBug(-1).Build())
    41  				So(err, ShouldErrLike, "bug: must be zero or positive")
    42  			})
    43  			Convey("zero is valid", func() {
    44  				err := Validate(NewAlertBuilder().WithBug(0).Build())
    45  				So(err, ShouldBeNil)
    46  			})
    47  		})
    48  		Convey("SilenceUntil", func() {
    49  			Convey("must not be negative", func() {
    50  				err := Validate(NewAlertBuilder().WithSilenceUntil(-1).Build())
    51  				So(err, ShouldErrLike, "silence_until: must be zero or positive")
    52  			})
    53  			Convey("zero is valid", func() {
    54  				err := Validate(NewAlertBuilder().WithSilenceUntil(0).Build())
    55  				So(err, ShouldBeNil)
    56  			})
    57  		})
    58  	})
    59  }
    60  
    61  func TestStatusTable(t *testing.T) {
    62  	Convey("Put", t, func() {
    63  		ctx := testutil.SpannerTestContext(t)
    64  		alert := NewAlertBuilder().WithBug(10).Build()
    65  
    66  		m, err := Put(alert)
    67  		So(err, ShouldBeNil)
    68  		ts, err := span.Apply(ctx, []*spanner.Mutation{m})
    69  		alert.ModifyTime = ts.UTC()
    70  
    71  		So(err, ShouldBeNil)
    72  		fetched, err := ReadBatch(span.Single(ctx), []string{alert.AlertKey})
    73  		So(err, ShouldBeNil)
    74  		So(fetched, ShouldEqual, []*Alert{alert})
    75  	})
    76  	Convey("Put deletes when all info is zero", t, func() {
    77  		ctx := testutil.SpannerTestContext(t)
    78  		alert := NewAlertBuilder().CreateInDB(ctx)
    79  
    80  		m, err := Put(alert)
    81  		So(err, ShouldBeNil)
    82  		ts, err := span.Apply(ctx, []*spanner.Mutation{m})
    83  		alert.ModifyTime = ts.UTC()
    84  
    85  		So(err, ShouldBeNil)
    86  		// When we read a non-existent entry, a fake entry will be returned, so the only
    87  		// way to tell it was actually deleted is a zero timestamp.
    88  		fetched, err := ReadBatch(span.Single(ctx), []string{alert.AlertKey})
    89  		So(err, ShouldBeNil)
    90  		So(len(fetched), ShouldEqual, 1)
    91  		So(fetched[0].ModifyTime, ShouldEqual, time.Time{})
    92  	})
    93  
    94  	Convey("ReadBatch", t, func() {
    95  		Convey("Single", func() {
    96  			ctx := testutil.SpannerTestContext(t)
    97  			alert := NewAlertBuilder().CreateInDB(ctx)
    98  
    99  			fetched, err := ReadBatch(span.Single(ctx), []string{alert.AlertKey})
   100  
   101  			So(err, ShouldBeNil)
   102  			So(fetched, ShouldEqual, []*Alert{alert})
   103  		})
   104  
   105  		Convey("NotPresent returns empty fields", func() {
   106  			ctx := testutil.SpannerTestContext(t)
   107  			_ = NewAlertBuilder().CreateInDB(ctx)
   108  
   109  			fetched, err := ReadBatch(span.Single(ctx), []string{"not-present"})
   110  
   111  			So(err, ShouldBeNil)
   112  			So(fetched, ShouldEqual, []*Alert{{
   113  				AlertKey:     "not-present",
   114  				Bug:          0,
   115  				SilenceUntil: 0,
   116  				ModifyTime:   time.Time{},
   117  			}})
   118  		})
   119  		Convey("Multiple returned in order", func() {
   120  			ctx := testutil.SpannerTestContext(t)
   121  			alert1 := NewAlertBuilder().WithAlertKey("alert1").WithBug(1).CreateInDB(ctx)
   122  			alert2 := NewAlertBuilder().WithAlertKey("alert2").WithBug(2).CreateInDB(ctx)
   123  
   124  			forwards, err := ReadBatch(span.Single(ctx), []string{"alert1", "alert2"})
   125  			backwards, err2 := ReadBatch(span.Single(ctx), []string{"alert2", "alert1"})
   126  
   127  			So(err, ShouldBeNil)
   128  			So(err2, ShouldBeNil)
   129  			So(forwards, ShouldEqual, []*Alert{alert1, alert2})
   130  			So(backwards, ShouldEqual, []*Alert{alert2, alert1})
   131  		})
   132  	})
   133  }
   134  
   135  type AlertBuilder struct {
   136  	alert Alert
   137  }
   138  
   139  func NewAlertBuilder() *AlertBuilder {
   140  	return &AlertBuilder{alert: Alert{
   141  		AlertKey:     "test-alert",
   142  		Bug:          0,
   143  		SilenceUntil: 0,
   144  		ModifyTime:   spanner.CommitTimestamp,
   145  	}}
   146  }
   147  
   148  func (b *AlertBuilder) WithAlertKey(alertKey string) *AlertBuilder {
   149  	b.alert.AlertKey = alertKey
   150  	return b
   151  }
   152  
   153  func (b *AlertBuilder) WithBug(bug int64) *AlertBuilder {
   154  	b.alert.Bug = bug
   155  	return b
   156  }
   157  
   158  func (b *AlertBuilder) WithSilenceUntil(silenceUntil int64) *AlertBuilder {
   159  	b.alert.SilenceUntil = silenceUntil
   160  	return b
   161  }
   162  
   163  func (b *AlertBuilder) WithModifyTime(modifyTime time.Time) *AlertBuilder {
   164  	b.alert.ModifyTime = modifyTime
   165  	return b
   166  }
   167  
   168  func (b *AlertBuilder) Build() *Alert {
   169  	s := b.alert
   170  	return &s
   171  }
   172  
   173  func (b *AlertBuilder) CreateInDB(ctx context.Context) *Alert {
   174  	s := b.Build()
   175  	row := map[string]any{
   176  		"AlertKey":     s.AlertKey,
   177  		"Bug":          s.Bug,
   178  		"SilenceUntil": s.SilenceUntil,
   179  		"ModifyTime":   s.ModifyTime,
   180  	}
   181  	m := spanner.InsertOrUpdateMap("Alerts", row)
   182  	ts, err := span.Apply(ctx, []*spanner.Mutation{m})
   183  	So(err, ShouldBeNil)
   184  	if s.ModifyTime == spanner.CommitTimestamp {
   185  		s.ModifyTime = ts.UTC()
   186  	}
   187  	return s
   188  }