go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/cron/dispatcher_test.go (about) 1 // Copyright 2021 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 cron 16 17 import ( 18 "context" 19 "errors" 20 "net/http/httptest" 21 "testing" 22 "time" 23 24 "go.chromium.org/luci/common/logging/gologger" 25 "go.chromium.org/luci/common/retry/transient" 26 "go.chromium.org/luci/common/tsmon" 27 "go.chromium.org/luci/common/tsmon/distribution" 28 "go.chromium.org/luci/common/tsmon/store" 29 "go.chromium.org/luci/common/tsmon/target" 30 "go.chromium.org/luci/common/tsmon/types" 31 32 "go.chromium.org/luci/server/router" 33 34 . "github.com/smartystreets/goconvey/convey" 35 ) 36 37 func TestDispatcher(t *testing.T) { 38 t.Parallel() 39 40 Convey("With dispatcher", t, func() { 41 ctx := context.Background() 42 ctx = gologger.StdConfig.Use(ctx) 43 ctx, _, _ = tsmon.WithFakes(ctx) 44 tsmon.GetState(ctx).SetStore(store.NewInMemory(&target.Task{})) 45 46 metric := func(m types.Metric, fieldVals ...any) any { 47 return tsmon.GetState(ctx).Store().Get(ctx, m, time.Time{}, fieldVals) 48 } 49 50 metricDist := func(m types.Metric, fieldVals ...any) (count int64) { 51 val := metric(m, fieldVals...) 52 if val != nil { 53 So(val, ShouldHaveSameTypeAs, &distribution.Distribution{}) 54 count = val.(*distribution.Distribution).Count() 55 } 56 return 57 } 58 59 d := &Dispatcher{DisableAuth: true} 60 61 srv := router.New() 62 d.InstallCronRoutes(srv, "/crons") 63 64 call := func(path string) int { 65 req := httptest.NewRequest("GET", path, nil).WithContext(ctx) 66 rec := httptest.NewRecorder() 67 srv.ServeHTTP(rec, req) 68 return rec.Result().StatusCode 69 } 70 71 Convey("Handler IDs", func() { 72 d.RegisterHandler("h1", func(ctx context.Context) error { return nil }) 73 d.RegisterHandler("h2", func(ctx context.Context) error { return nil }) 74 So(d.handlerIDs(), ShouldResemble, []string{"h1", "h2"}) 75 }) 76 77 Convey("Works", func() { 78 called := false 79 d.RegisterHandler("ok", func(ctx context.Context) error { 80 called = true 81 return nil 82 }) 83 So(call("/crons/ok"), ShouldEqual, 200) 84 So(called, ShouldBeTrue) 85 So(metric(callsCounter, "ok", "OK"), ShouldEqual, 1) 86 So(metricDist(callsDurationMS, "ok", "OK"), ShouldEqual, 1) 87 }) 88 89 Convey("Fatal error", func() { 90 d.RegisterHandler("boom", func(ctx context.Context) error { 91 return errors.New("boom") 92 }) 93 So(call("/crons/boom"), ShouldEqual, 202) 94 So(metric(callsCounter, "boom", "fatal"), ShouldEqual, 1) 95 So(metricDist(callsDurationMS, "boom", "fatal"), ShouldEqual, 1) 96 }) 97 98 Convey("Transient error", func() { 99 d.RegisterHandler("smaller-boom", func(ctx context.Context) error { 100 return transient.Tag.Apply(errors.New("smaller boom")) 101 }) 102 So(call("/crons/smaller-boom"), ShouldEqual, 500) 103 So(metric(callsCounter, "smaller-boom", "transient"), ShouldEqual, 1) 104 So(metricDist(callsDurationMS, "smaller-boom", "transient"), ShouldEqual, 1) 105 }) 106 107 Convey("Unknown handler", func() { 108 So(call("/crons/unknown"), ShouldEqual, 202) 109 So(metric(callsCounter, "unknown", "no_handler"), ShouldEqual, 1) 110 }) 111 112 Convey("Panic", func() { 113 d.RegisterHandler("panic", func(ctx context.Context) error { 114 panic("boom") 115 }) 116 So(func() { call("/crons/panic") }, ShouldPanic) 117 So(metric(callsCounter, "panic", "panic"), ShouldEqual, 1) 118 So(metricDist(callsDurationMS, "panic", "panic"), ShouldEqual, 1) 119 }) 120 }) 121 }