blob: 9a93e96f694110dab99e81b41e867a4d4d39584b [file] [log] [blame]
// Copyright 2021 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cron
import (
"context"
"errors"
"net/http/httptest"
"sort"
"testing"
"time"
"go.chromium.org/luci/common/logging/gologger"
"go.chromium.org/luci/common/retry/transient"
"go.chromium.org/luci/common/tsmon"
"go.chromium.org/luci/common/tsmon/distribution"
"go.chromium.org/luci/common/tsmon/store"
"go.chromium.org/luci/common/tsmon/target"
"go.chromium.org/luci/common/tsmon/types"
"go.chromium.org/luci/server/router"
. "github.com/smartystreets/goconvey/convey"
)
func TestDispatcher(t *testing.T) {
t.Parallel()
Convey("With dispatcher", t, func() {
ctx := context.Background()
ctx = gologger.StdConfig.Use(ctx)
ctx, _, _ = tsmon.WithFakes(ctx)
tsmon.GetState(ctx).SetStore(store.NewInMemory(&target.Task{}))
metric := func(m types.Metric, fieldVals ...interface{}) interface{} {
return tsmon.GetState(ctx).Store().Get(ctx, m, time.Time{}, fieldVals)
}
metricDist := func(m types.Metric, fieldVals ...interface{}) (count int64) {
val := metric(m, fieldVals...)
if val != nil {
So(val, ShouldHaveSameTypeAs, &distribution.Distribution{})
count = val.(*distribution.Distribution).Count()
}
return
}
d := &Dispatcher{DisableAuth: true}
srv := router.NewWithRootContext(ctx)
d.InstallCronRoutes(srv, "/crons")
call := func(path string) int {
req := httptest.NewRequest("GET", path, nil)
rec := httptest.NewRecorder()
srv.ServeHTTP(rec, req)
return rec.Result().StatusCode
}
Convey("Handler IDs", func() {
d.RegisterHandler("h1", func(ctx context.Context) error { return nil })
d.RegisterHandler("h2", func(ctx context.Context) error { return nil })
ids := d.handlerIDs()
sort.Strings(ids)
So(ids, ShouldResemble, []string{"h1", "h2"})
})
Convey("Works", func() {
called := false
d.RegisterHandler("ok", func(ctx context.Context) error {
called = true
return nil
})
So(call("/crons/ok"), ShouldEqual, 200)
So(called, ShouldBeTrue)
So(metric(callsCounter, "ok", "OK"), ShouldEqual, 1)
So(metricDist(callsDurationMS, "ok", "OK"), ShouldEqual, 1)
})
Convey("Fatal error", func() {
d.RegisterHandler("boom", func(ctx context.Context) error {
return errors.New("boom")
})
So(call("/crons/boom"), ShouldEqual, 202)
So(metric(callsCounter, "boom", "fatal"), ShouldEqual, 1)
So(metricDist(callsDurationMS, "boom", "fatal"), ShouldEqual, 1)
})
Convey("Transient error", func() {
d.RegisterHandler("smaller-boom", func(ctx context.Context) error {
return transient.Tag.Apply(errors.New("smaller boom"))
})
So(call("/crons/smaller-boom"), ShouldEqual, 500)
So(metric(callsCounter, "smaller-boom", "transient"), ShouldEqual, 1)
So(metricDist(callsDurationMS, "smaller-boom", "transient"), ShouldEqual, 1)
})
Convey("Unknown handler", func() {
So(call("/crons/unknown"), ShouldEqual, 202)
So(metric(callsCounter, "unknown", "no_handler"), ShouldEqual, 1)
})
Convey("Panic", func() {
d.RegisterHandler("panic", func(ctx context.Context) error {
panic("boom")
})
So(func() { call("/crons/panic") }, ShouldPanic)
So(metric(callsCounter, "panic", "panic"), ShouldEqual, 1)
So(metricDist(callsDurationMS, "panic", "panic"), ShouldEqual, 1)
})
})
}