influxdb/http/check_test.go

1486 lines
38 KiB
Go

package http
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"path"
"testing"
"github.com/influxdata/flux/parser"
"github.com/influxdata/httprouter"
"github.com/influxdata/influxdb/v2"
pcontext "github.com/influxdata/influxdb/v2/context"
"github.com/influxdata/influxdb/v2/kit/platform"
"github.com/influxdata/influxdb/v2/kit/platform/errors"
kithttp "github.com/influxdata/influxdb/v2/kit/transport/http"
"github.com/influxdata/influxdb/v2/mock"
"github.com/influxdata/influxdb/v2/notification"
"github.com/influxdata/influxdb/v2/notification/check"
"github.com/influxdata/influxdb/v2/pkg/testttp"
"github.com/influxdata/influxdb/v2/query/fluxlang"
"github.com/influxdata/influxdb/v2/task/taskmodel"
influxTesting "github.com/influxdata/influxdb/v2/testing"
"go.uber.org/zap/zaptest"
)
// NewMockCheckBackend returns a CheckBackend with mock services.
func NewMockCheckBackend(t *testing.T) *CheckBackend {
return &CheckBackend{
log: zaptest.NewLogger(t),
CheckService: mock.NewCheckService(),
UserResourceMappingService: mock.NewUserResourceMappingService(),
LabelService: mock.NewLabelService(),
UserService: mock.NewUserService(),
OrganizationService: mock.NewOrganizationService(),
FluxLanguageService: fluxlang.DefaultService,
}
}
func TestService_handleGetChecks(t *testing.T) {
type fields struct {
CheckService influxdb.CheckService
LabelService influxdb.LabelService
}
type args struct {
queryParams map[string][]string
}
type wants struct {
statusCode int
contentType string
body string
}
fl1 := 100.32
fl2 := 200.64
fl4 := 100.1
fl5 := 3023.2
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "get all checks",
fields: fields{
&mock.CheckService{
FindChecksFn: func(ctx context.Context, filter influxdb.CheckFilter, opts ...influxdb.FindOptions) ([]influxdb.Check, int, error) {
return []influxdb.Check{
&check.Deadman{
Base: check.Base{
ID: influxTesting.MustIDBase16("0b501e7e557ab1ed"),
Name: "hello",
OrgID: influxTesting.MustIDBase16("50f7ba1150f7ba11"),
TaskID: 3,
},
Level: notification.Info,
},
&check.Threshold{
Base: check.Base{
ID: influxTesting.MustIDBase16("c0175f0077a77005"),
Name: "example",
OrgID: influxTesting.MustIDBase16("7e55e118dbabb1ed"),
TaskID: 3,
},
Thresholds: []check.ThresholdConfig{
&check.Greater{
Value: fl1,
ThresholdConfigBase: check.ThresholdConfigBase{Level: notification.Critical},
},
&check.Lesser{
Value: fl2,
ThresholdConfigBase: check.ThresholdConfigBase{Level: notification.Info},
},
&check.Range{Min: fl4, Max: fl5, Within: true},
},
},
}, 2, nil
},
},
&mock.LabelService{
FindResourceLabelsFn: func(ctx context.Context, f influxdb.LabelMappingFilter) ([]*influxdb.Label, error) {
labels := []*influxdb.Label{
{
ID: influxTesting.MustIDBase16("fc3dc670a4be9b9a"),
Name: "label",
Properties: map[string]string{
"color": "fff000",
},
},
}
return labels, nil
},
},
},
args: args{
map[string][]string{
"limit": {"1"},
},
},
wants: wants{
statusCode: http.StatusOK,
contentType: "application/json; charset=utf-8",
body: `
{
"links": {
"self": "/api/v2/checks?descending=false&limit=1&offset=0",
"next": "/api/v2/checks?descending=false&limit=1&offset=1"
},
"checks": [
{
"links": {
"self": "/api/v2/checks/0b501e7e557ab1ed",
"labels": "/api/v2/checks/0b501e7e557ab1ed/labels",
"query": "/api/v2/checks/0b501e7e557ab1ed/query",
"owners": "/api/v2/checks/0b501e7e557ab1ed/owners",
"members": "/api/v2/checks/0b501e7e557ab1ed/members"
},
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z",
"id": "0b501e7e557ab1ed",
"orgID": "50f7ba1150f7ba11",
"name": "hello",
"level": "INFO",
"query": {
"builderConfig": {
"aggregateWindow": {
"fillValues": false,
"period": ""
},
"buckets": [],
"functions": [],
"tags": []
},
"editMode": "",
"name": "",
"text": ""
},
"reportZero": false,
"statusMessageTemplate": "",
"tags": null,
"taskID": "0000000000000003",
"type": "deadman",
"labels": [
{
"id": "fc3dc670a4be9b9a",
"name": "label",
"properties": {
"color": "fff000"
}
}
],
"status": "active",
"latestCompleted": "0001-01-01T00:00:00Z",
"latestScheduled": "0001-01-01T00:00:00Z"
},
{
"links": {
"self": "/api/v2/checks/c0175f0077a77005",
"labels": "/api/v2/checks/c0175f0077a77005/labels",
"members": "/api/v2/checks/c0175f0077a77005/members",
"owners": "/api/v2/checks/c0175f0077a77005/owners",
"query": "/api/v2/checks/c0175f0077a77005/query"
},
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z",
"id": "c0175f0077a77005",
"orgID": "7e55e118dbabb1ed",
"name": "example",
"query": {
"builderConfig": {
"aggregateWindow": {
"fillValues": false,
"period": ""
},
"buckets": [],
"functions": [],
"tags": []
},
"editMode": "",
"name": "",
"text": ""
},
"statusMessageTemplate": "",
"tags": null,
"taskID": "0000000000000003",
"thresholds": [
{
"allValues": false,
"level": "CRIT",
"type": "greater",
"value": 100.32
},
{
"allValues": false,
"level": "INFO",
"type": "lesser",
"value": 200.64
},
{
"allValues": false,
"level": "UNKNOWN",
"max": 3023.2,
"min": 100.1,
"type": "range",
"within": true
}
],
"type": "threshold",
"labels": [
{
"id": "fc3dc670a4be9b9a",
"name": "label",
"properties": {
"color": "fff000"
}
}
],
"status": "active",
"latestCompleted": "0001-01-01T00:00:00Z",
"latestScheduled": "0001-01-01T00:00:00Z"
}
]
}
`,
},
},
{
name: "get all checks when there are none",
fields: fields{
&mock.CheckService{
FindChecksFn: func(ctx context.Context, filter influxdb.CheckFilter, opts ...influxdb.FindOptions) ([]influxdb.Check, int, error) {
return []influxdb.Check{}, 0, nil
},
},
&mock.LabelService{},
},
args: args{
map[string][]string{
"limit": {"1"},
},
},
wants: wants{
statusCode: http.StatusOK,
contentType: "application/json; charset=utf-8",
body: `
{
"links": {
"self": "/api/v2/checks?descending=false&limit=1&offset=0"
},
"checks": []
}`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
checkBackend := NewMockCheckBackend(t)
checkBackend.CheckService = tt.fields.CheckService
checkBackend.LabelService = tt.fields.LabelService
checkBackend.TaskService = &mock.TaskService{
FindTaskByIDFn: func(ctx context.Context, id platform.ID) (*taskmodel.Task, error) {
return &taskmodel.Task{Status: "active"}, nil
},
}
h := NewCheckHandler(zaptest.NewLogger(t), checkBackend)
r := httptest.NewRequest("GET", "http://any.url", nil)
qp := r.URL.Query()
for k, vs := range tt.args.queryParams {
for _, v := range vs {
qp.Add(k, v)
}
}
r.URL.RawQuery = qp.Encode()
r = r.WithContext(pcontext.SetAuthorizer(r.Context(), &influxdb.Session{UserID: influxTesting.MustIDBase16("6f626f7274697321")}))
w := httptest.NewRecorder()
h.handleGetChecks(w, r)
res := w.Result()
content := res.Header.Get("Content-Type")
body, _ := io.ReadAll(res.Body)
if res.StatusCode != tt.wants.statusCode {
t.Errorf("%q. handleGetChecks() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. handleGetChecks() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil || tt.wants.body != "" && !eq {
t.Errorf("%v", err)
t.Errorf("%q. handleGetChecks() = ***%v***", tt.name, diff)
}
})
}
}
func mustDuration(d string) *notification.Duration {
dur, err := parser.ParseDuration(d)
if err != nil {
panic(err)
}
return (*notification.Duration)(dur)
}
func TestService_handleGetCheckQuery(t *testing.T) {
type fields struct {
CheckService influxdb.CheckService
}
var l float64 = 10
var u float64 = 40
type args struct {
id string
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "get a check query by id",
fields: fields{
&mock.CheckService{
FindCheckByIDFn: func(ctx context.Context, id platform.ID) (influxdb.Check, error) {
if id == influxTesting.MustIDBase16("020f755c3c082000") {
return &check.Threshold{
Base: check.Base{
ID: influxTesting.MustIDBase16("020f755c3c082000"),
OrgID: influxTesting.MustIDBase16("020f755c3c082000"),
Name: "hello",
TaskID: 3,
Tags: []influxdb.Tag{
{Key: "aaa", Value: "vaaa"},
{Key: "bbb", Value: "vbbb"},
},
Every: mustDuration("1h"),
StatusMessageTemplate: "whoa! {check.yeah}",
Query: influxdb.DashboardQuery{
Text: `from(bucket: "foo") |> range(start: -1d, stop: now()) |> filter(fn: (r) => r._field == "usage_idle") |> aggregateWindow(every: 1m, fn: mean) |> yield()`,
},
},
Thresholds: []check.ThresholdConfig{
check.Greater{
ThresholdConfigBase: check.ThresholdConfigBase{
Level: notification.Ok,
},
Value: l,
},
check.Lesser{
ThresholdConfigBase: check.ThresholdConfigBase{
Level: notification.Info,
},
Value: u,
},
check.Range{
ThresholdConfigBase: check.ThresholdConfigBase{
Level: notification.Warn,
},
Min: l,
Max: u,
Within: true,
},
check.Range{
ThresholdConfigBase: check.ThresholdConfigBase{
Level: notification.Critical,
},
Min: l,
Max: u,
Within: true,
},
},
}, nil
}
return nil, fmt.Errorf("not found")
},
},
},
args: args{
id: "020f755c3c082000",
},
wants: wants{
statusCode: http.StatusOK,
contentType: "application/json; charset=utf-8",
body: "{\"flux\":" + formatFluxJson(t, `import "influxdata/influxdb/monitor"
import "influxdata/influxdb/v1"
data =
from(bucket: "foo")
|> range(start: -1h)
|> filter(fn: (r) => r._field == "usage_idle")
|> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
option task = {name: "hello", every: 1h}
check = {_check_id: "020f755c3c082000", _check_name: "hello", _type: "threshold", tags: {aaa: "vaaa", bbb: "vbbb"}}
ok = (r) => r["usage_idle"] > 10.0
info = (r) => r["usage_idle"] < 40.0
warn = (r) => r["usage_idle"] < 40.0 and r["usage_idle"] > 10.0
crit = (r) => r["usage_idle"] < 40.0 and r["usage_idle"] > 10.0
messageFn = (r) => "whoa! {check.yeah}"
data
|> v1["fieldsAsCols"]()
|> monitor["check"](
data: check,
messageFn: messageFn,
ok: ok,
info: info,
warn: warn,
crit: crit,
)
`) + "}\n",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
checkBackend := NewMockCheckBackend(t)
checkBackend.HTTPErrorHandler = kithttp.NewErrorHandler(zaptest.NewLogger(t))
checkBackend.CheckService = tt.fields.CheckService
checkBackend.TaskService = &mock.TaskService{
FindTaskByIDFn: func(ctx context.Context, id platform.ID) (*taskmodel.Task, error) {
return &taskmodel.Task{}, nil
},
}
testttp.
Get(t, path.Join(prefixChecks, tt.args.id, "/query")).
Do(NewCheckHandler(zaptest.NewLogger(t), checkBackend)).
ExpectStatus(tt.wants.statusCode).
Expect(func(resp *testttp.Resp) {
content := resp.Rec.Header().Get("Content-Type")
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. handleGetCheckQuery() = %v, want %v", tt.name, content, tt.wants.contentType)
}
}).
ExpectBody(func(body *bytes.Buffer) {
if eq, diff, err := jsonEqual(body.String(), tt.wants.body); err != nil || tt.wants.body != "" && !eq {
if err != nil {
t.Errorf("jsonEqual error: %v", err)
}
t.Errorf("%q. handleGetChecks() = ***%v***", tt.name, diff)
}
})
})
}
}
func formatFluxJson(t *testing.T, script string) string {
formatted := influxTesting.FormatFluxString(t, script)
enc, err := json.Marshal(formatted)
if err != nil {
t.Fatalf("error marshalling flux: %v", err)
}
var bb bytes.Buffer
json.HTMLEscape(&bb, enc)
std := bb.String()
return std
}
func TestService_handleGetCheck(t *testing.T) {
type fields struct {
CheckService influxdb.CheckService
}
type args struct {
id string
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "get a check by id",
fields: fields{
&mock.CheckService{
FindCheckByIDFn: func(ctx context.Context, id platform.ID) (influxdb.Check, error) {
if id == influxTesting.MustIDBase16("020f755c3c082000") {
return &check.Deadman{
Base: check.Base{
ID: influxTesting.MustIDBase16("020f755c3c082000"),
OrgID: influxTesting.MustIDBase16("020f755c3c082000"),
Name: "hello",
Every: mustDuration("3h"),
TaskID: 3,
},
Level: notification.Critical,
}, nil
}
return nil, fmt.Errorf("not found")
},
},
},
args: args{
id: "020f755c3c082000",
},
wants: wants{
statusCode: http.StatusOK,
contentType: "application/json; charset=utf-8",
body: `
{
"links": {
"self": "/api/v2/checks/020f755c3c082000",
"labels": "/api/v2/checks/020f755c3c082000/labels",
"members": "/api/v2/checks/020f755c3c082000/members",
"owners": "/api/v2/checks/020f755c3c082000/owners",
"query": "/api/v2/checks/020f755c3c082000/query"
},
"labels": [],
"level": "CRIT",
"every": "3h",
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z",
"id": "020f755c3c082000",
"query": {
"builderConfig": {
"aggregateWindow": {
"fillValues": false,
"period": ""
},
"buckets": [],
"functions": [],
"tags": []
},
"editMode": "",
"name": "",
"text": ""
},
"reportZero": false,
"status": "active",
"statusMessageTemplate": "",
"tags": null,
"taskID": "0000000000000003",
"type": "deadman",
"orgID": "020f755c3c082000",
"name": "hello",
"status": "active",
"latestCompleted": "0001-01-01T00:00:00Z",
"latestScheduled": "0001-01-01T00:00:00Z"
}
`,
},
},
{
name: "not found",
fields: fields{
&mock.CheckService{
FindCheckByIDFn: func(ctx context.Context, id platform.ID) (influxdb.Check, error) {
return nil, &errors.Error{
Code: errors.ENotFound,
Msg: "check not found",
}
},
},
},
args: args{
id: "020f755c3c082000",
},
wants: wants{
statusCode: http.StatusNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
checkBackend := NewMockCheckBackend(t)
checkBackend.HTTPErrorHandler = kithttp.NewErrorHandler(zaptest.NewLogger(t))
checkBackend.CheckService = tt.fields.CheckService
checkBackend.TaskService = &mock.TaskService{
FindTaskByIDFn: func(ctx context.Context, id platform.ID) (*taskmodel.Task, error) {
return &taskmodel.Task{Status: "active"}, nil
},
}
h := NewCheckHandler(zaptest.NewLogger(t), checkBackend)
r := httptest.NewRequest("GET", "http://any.url", nil)
r = r.WithContext(context.WithValue(
context.Background(),
httprouter.ParamsKey,
httprouter.Params{
{
Key: "id",
Value: tt.args.id,
},
}))
w := httptest.NewRecorder()
h.handleGetCheck(w, r)
res := w.Result()
content := res.Header.Get("Content-Type")
body, _ := io.ReadAll(res.Body)
t.Logf(res.Header.Get("X-Influx-Error"))
if res.StatusCode != tt.wants.statusCode {
t.Errorf("%q. handleGetCheck() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. handleGetCheck() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if tt.wants.body != "" {
if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil {
t.Errorf("%q, handleGetCheck(). error unmarshalling json %v", tt.name, err)
} else if !eq {
t.Errorf("%q. handleGetCheck() = ***%s***", tt.name, diff)
}
}
})
}
}
func TestService_handlePostCheck(t *testing.T) {
type fields struct {
CheckService influxdb.CheckService
OrganizationService influxdb.OrganizationService
}
type args struct {
userID platform.ID
check influxdb.Check
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "create a new check",
fields: fields{
CheckService: &mock.CheckService{
CreateCheckFn: func(ctx context.Context, c influxdb.CheckCreate, userID platform.ID) error {
c.SetID(influxTesting.MustIDBase16("020f755c3c082000"))
c.SetOwnerID(userID)
return nil
},
},
OrganizationService: &mock.OrganizationService{
FindOrganizationF: func(ctx context.Context, f influxdb.OrganizationFilter) (*influxdb.Organization, error) {
return &influxdb.Organization{ID: influxTesting.MustIDBase16("6f626f7274697320")}, nil
},
},
},
args: args{
userID: influxTesting.MustIDBase16("6f626f7274697321"),
check: &check.Deadman{
Base: check.Base{
Name: "hello",
OrgID: influxTesting.MustIDBase16("6f626f7274697320"),
OwnerID: influxTesting.MustIDBase16("6f626f7274697321"),
Description: "desc1",
StatusMessageTemplate: "msg1",
Every: mustDuration("5m"),
TaskID: 3,
Tags: []influxdb.Tag{
{Key: "k1", Value: "v1"},
{Key: "k2", Value: "v2"},
},
},
TimeSince: mustDuration("13s"),
ReportZero: true,
Level: notification.Warn,
},
},
wants: wants{
statusCode: http.StatusCreated,
contentType: "application/json; charset=utf-8",
body: `
{
"links": {
"self": "/api/v2/checks/020f755c3c082000",
"labels": "/api/v2/checks/020f755c3c082000/labels",
"members": "/api/v2/checks/020f755c3c082000/members",
"owners": "/api/v2/checks/020f755c3c082000/owners",
"query": "/api/v2/checks/020f755c3c082000/query"
},
"reportZero": true,
"statusMessageTemplate": "msg1",
"tags": [
{
"key": "k1",
"value": "v1"
},
{
"key": "k2",
"value": "v2"
}
],
"query": {
"builderConfig": {
"aggregateWindow": {
"fillValues": false,
"period": ""
},
"buckets": [],
"functions": [],
"tags": []
},
"editMode": "",
"name": "",
"text": ""
},
"taskID": "0000000000000003",
"type": "deadman",
"timeSince": "13s",
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z",
"id": "020f755c3c082000",
"orgID": "6f626f7274697320",
"name": "hello",
"ownerID": "6f626f7274697321",
"description": "desc1",
"every": "5m",
"level": "WARN",
"labels": [],
"status": "active",
"latestCompleted": "0001-01-01T00:00:00Z",
"latestScheduled": "0001-01-01T00:00:00Z"
}
`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
checkBackend := NewMockCheckBackend(t)
checkBackend.CheckService = tt.fields.CheckService
checkBackend.OrganizationService = tt.fields.OrganizationService
checkBackend.TaskService = &mock.TaskService{
FindTaskByIDFn: func(ctx context.Context, id platform.ID) (*taskmodel.Task, error) {
return &taskmodel.Task{Status: "active"}, nil
},
}
h := NewCheckHandler(zaptest.NewLogger(t), checkBackend)
b, err := json.Marshal(tt.args.check)
if err != nil {
t.Fatalf("failed to unmarshal check: %v", err)
}
r := httptest.NewRequest("GET", "http://any.url?org=30", bytes.NewReader(b))
w := httptest.NewRecorder()
r = r.WithContext(pcontext.SetAuthorizer(r.Context(), &influxdb.Session{UserID: tt.args.userID}))
h.handlePostCheck(w, r)
res := w.Result()
content := res.Header.Get("Content-Type")
body, _ := io.ReadAll(res.Body)
if res.StatusCode != tt.wants.statusCode {
t.Errorf("%q. handlePostCheck() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. handlePostCheck() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if tt.wants.body != "" {
if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil {
t.Errorf("%q, handlePostCheck(). error unmarshalling json %v", tt.name, err)
} else if !eq {
t.Errorf("%q. handlePostCheck() = ***%s***", tt.name, diff)
}
}
})
}
}
func TestService_handleDeleteCheck(t *testing.T) {
type fields struct {
CheckService influxdb.CheckService
}
type args struct {
id string
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "remove a check by id",
fields: fields{
&mock.CheckService{
DeleteCheckFn: func(ctx context.Context, id platform.ID) error {
if id == influxTesting.MustIDBase16("020f755c3c082000") {
return nil
}
return fmt.Errorf("wrong id")
},
},
},
args: args{
id: "020f755c3c082000",
},
wants: wants{
statusCode: http.StatusNoContent,
},
},
{
name: "check not found",
fields: fields{
&mock.CheckService{
DeleteCheckFn: func(ctx context.Context, id platform.ID) error {
return &errors.Error{
Code: errors.ENotFound,
Msg: "check not found",
}
},
},
},
args: args{
id: "020f755c3c082000",
},
wants: wants{
statusCode: http.StatusNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
checkBackend := NewMockCheckBackend(t)
checkBackend.HTTPErrorHandler = kithttp.NewErrorHandler(zaptest.NewLogger(t))
checkBackend.CheckService = tt.fields.CheckService
checkBackend.TaskService = &mock.TaskService{
FindTaskByIDFn: func(ctx context.Context, id platform.ID) (*taskmodel.Task, error) {
return &taskmodel.Task{}, nil
},
}
h := NewCheckHandler(zaptest.NewLogger(t), checkBackend)
r := httptest.NewRequest("GET", "http://any.url", nil)
r = r.WithContext(context.WithValue(
context.Background(),
httprouter.ParamsKey,
httprouter.Params{
{
Key: "id",
Value: tt.args.id,
},
}))
w := httptest.NewRecorder()
h.handleDeleteCheck(w, r)
res := w.Result()
content := res.Header.Get("Content-Type")
body, _ := io.ReadAll(res.Body)
if res.StatusCode != tt.wants.statusCode {
t.Errorf("%q. handleDeleteCheck() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. handleDeleteCheck() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if tt.wants.body != "" {
if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil {
t.Errorf("%q, handleDeleteCheck(). error unmarshalling json %v", tt.name, err)
} else if !eq {
t.Errorf("%q. handleDeleteCheck() = ***%s***", tt.name, diff)
}
}
})
}
}
func TestService_handlePatchCheck(t *testing.T) {
type fields struct {
CheckService influxdb.CheckService
}
type args struct {
id string
name string
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "update a check name",
fields: fields{
&mock.CheckService{
PatchCheckFn: func(ctx context.Context, id platform.ID, upd influxdb.CheckUpdate) (influxdb.Check, error) {
if id == influxTesting.MustIDBase16("020f755c3c082000") {
d := &check.Deadman{
Base: check.Base{
ID: influxTesting.MustIDBase16("020f755c3c082000"),
Name: "hello",
OrgID: influxTesting.MustIDBase16("020f755c3c082000"),
TaskID: 3,
},
Level: notification.Critical,
}
if upd.Name != nil {
d.Name = *upd.Name
}
return d, nil
}
return nil, fmt.Errorf("not found")
},
},
},
args: args{
id: "020f755c3c082000",
name: "example",
},
wants: wants{
statusCode: http.StatusOK,
contentType: "application/json; charset=utf-8",
body: `
{
"links": {
"self": "/api/v2/checks/020f755c3c082000",
"labels": "/api/v2/checks/020f755c3c082000/labels",
"members": "/api/v2/checks/020f755c3c082000/members",
"owners": "/api/v2/checks/020f755c3c082000/owners",
"query": "/api/v2/checks/020f755c3c082000/query"
},
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z",
"id": "020f755c3c082000",
"orgID": "020f755c3c082000",
"level": "CRIT",
"name": "example",
"query": {
"builderConfig": {
"aggregateWindow": {
"fillValues": false,
"period": ""
},
"buckets": [],
"functions": [],
"tags": []
},
"editMode": "",
"name": "",
"text": ""
},
"reportZero": false,
"status": "active",
"statusMessageTemplate": "",
"tags": null,
"taskID": "0000000000000003",
"type": "deadman",
"labels": [],
"latestCompleted": "0001-01-01T00:00:00Z",
"latestScheduled": "0001-01-01T00:00:00Z"
}
`,
},
},
{
name: "check not found",
fields: fields{
&mock.CheckService{
PatchCheckFn: func(ctx context.Context, id platform.ID, upd influxdb.CheckUpdate) (influxdb.Check, error) {
return nil, &errors.Error{
Code: errors.ENotFound,
Msg: "check not found",
}
},
},
},
args: args{
id: "020f755c3c082000",
name: "hello",
},
wants: wants{
statusCode: http.StatusNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
checkBackend := NewMockCheckBackend(t)
checkBackend.HTTPErrorHandler = kithttp.NewErrorHandler(zaptest.NewLogger(t))
checkBackend.CheckService = tt.fields.CheckService
checkBackend.TaskService = &mock.TaskService{
FindTaskByIDFn: func(ctx context.Context, id platform.ID) (*taskmodel.Task, error) {
return &taskmodel.Task{Status: "active"}, nil
},
}
h := NewCheckHandler(zaptest.NewLogger(t), checkBackend)
upd := influxdb.CheckUpdate{}
if tt.args.name != "" {
upd.Name = &tt.args.name
}
b, err := json.Marshal(upd)
if err != nil {
t.Fatalf("failed to unmarshal check update: %v", err)
}
r := httptest.NewRequest("GET", "http://any.url", bytes.NewReader(b))
r = r.WithContext(context.WithValue(
context.Background(),
httprouter.ParamsKey,
httprouter.Params{
{
Key: "id",
Value: tt.args.id,
},
}))
w := httptest.NewRecorder()
h.handlePatchCheck(w, r)
res := w.Result()
content := res.Header.Get("Content-Type")
body, _ := io.ReadAll(res.Body)
if res.StatusCode != tt.wants.statusCode {
t.Errorf("%q. handlePatchCheck() = %v, want %v %v", tt.name, res.StatusCode, tt.wants.statusCode, w.Header())
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. handlePatchCheck() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if tt.wants.body != "" {
if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil {
t.Errorf("%q, handlePatchCheck(). error unmarshalling json %v", tt.name, err)
} else if !eq {
t.Errorf("%q. handlePatchCheck() = ***%s***", tt.name, diff)
}
}
})
}
}
func TestService_handleUpdateCheck(t *testing.T) {
type fields struct {
CheckService influxdb.CheckService
}
type args struct {
id string
chk influxdb.Check
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "update a check name",
fields: fields{
CheckService: &mock.CheckService{
UpdateCheckFn: func(ctx context.Context, id platform.ID, chk influxdb.CheckCreate) (influxdb.Check, error) {
if id == influxTesting.MustIDBase16("020f755c3c082000") {
d := &check.Deadman{
Base: check.Base{
ID: influxTesting.MustIDBase16("020f755c3c082000"),
Name: "hello",
OrgID: influxTesting.MustIDBase16("020f755c3c082000"),
TaskID: 3,
Every: mustDuration("1m"),
},
}
d = chk.Check.(*check.Deadman)
d.SetID(influxTesting.MustIDBase16("020f755c3c082000"))
d.SetOrgID(influxTesting.MustIDBase16("020f755c3c082000"))
return d, nil
}
return nil, fmt.Errorf("not found")
},
},
},
args: args{
id: "020f755c3c082000",
chk: &check.Deadman{
Base: check.Base{
Name: "example",
TaskID: 3,
OwnerID: 42,
OrgID: influxTesting.MustIDBase16("020f755c3c082000"),
Every: mustDuration("1m"),
},
Level: notification.Critical,
},
},
wants: wants{
statusCode: http.StatusOK,
contentType: "application/json; charset=utf-8",
body: `
{
"links": {
"self": "/api/v2/checks/020f755c3c082000",
"labels": "/api/v2/checks/020f755c3c082000/labels",
"members": "/api/v2/checks/020f755c3c082000/members",
"owners": "/api/v2/checks/020f755c3c082000/owners",
"query": "/api/v2/checks/020f755c3c082000/query"
},
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z",
"id": "020f755c3c082000",
"every": "1m",
"orgID": "020f755c3c082000",
"ownerID": "000000000000002a",
"level": "CRIT",
"name": "example",
"query": {
"builderConfig": {
"aggregateWindow": {
"fillValues": false,
"period": ""
},
"buckets": [],
"functions": [],
"tags": []
},
"editMode": "",
"name": "",
"text": ""
},
"reportZero": false,
"status": "active",
"statusMessageTemplate": "",
"tags": null,
"taskID": "0000000000000003",
"type": "deadman",
"labels": [],
"latestCompleted": "0001-01-01T00:00:00Z",
"latestScheduled": "0001-01-01T00:00:00Z"
}
`,
},
},
{
name: "check not found",
fields: fields{
CheckService: &mock.CheckService{
UpdateCheckFn: func(ctx context.Context, id platform.ID, chk influxdb.CheckCreate) (influxdb.Check, error) {
return nil, &errors.Error{
Code: errors.ENotFound,
Msg: "check not found",
}
},
},
},
args: args{
id: "020f755c3c082000",
chk: &check.Deadman{
Base: check.Base{
Name: "example",
OwnerID: 42,
OrgID: influxTesting.MustIDBase16("020f755c3c082000"),
Every: mustDuration("1m"),
},
},
},
wants: wants{
statusCode: http.StatusNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
checkBackend := NewMockCheckBackend(t)
checkBackend.HTTPErrorHandler = kithttp.NewErrorHandler(zaptest.NewLogger(t))
checkBackend.CheckService = tt.fields.CheckService
checkBackend.TaskService = &mock.TaskService{
FindTaskByIDFn: func(ctx context.Context, id platform.ID) (*taskmodel.Task, error) {
return &taskmodel.Task{Status: "active"}, nil
},
}
h := NewCheckHandler(zaptest.NewLogger(t), checkBackend)
b, err := json.Marshal(tt.args.chk)
if err != nil {
t.Fatalf("failed to unmarshal check update: %v", err)
}
r := httptest.NewRequest("GET", "http://any.url", bytes.NewReader(b))
r = r.WithContext(context.WithValue(
context.Background(),
httprouter.ParamsKey,
httprouter.Params{
{
Key: "id",
Value: tt.args.id,
},
}))
w := httptest.NewRecorder()
h.handlePutCheck(w, r)
res := w.Result()
content := res.Header.Get("Content-Type")
body, _ := io.ReadAll(res.Body)
if res.StatusCode != tt.wants.statusCode {
t.Errorf("%q. handlePutCheck() = %v, want %v %v %v", tt.name, res.StatusCode, tt.wants.statusCode, w.Header(), string(body))
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. handlePutCheck() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if tt.wants.body != "" {
if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil {
t.Errorf("%q, handlePutCheck(). error unmarshalling json %v", tt.name, err)
} else if !eq {
t.Errorf("%q. handlePutCheck() = ***%s***", tt.name, diff)
}
}
})
}
}
func TestService_handlePostCheckMember(t *testing.T) {
type fields struct {
UserService influxdb.UserService
}
type args struct {
checkID string
user *influxdb.User
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "add a check member",
fields: fields{
UserService: &mock.UserService{
FindUserByIDFn: func(ctx context.Context, id platform.ID) (*influxdb.User, error) {
return &influxdb.User{
ID: id,
Name: "name",
Status: influxdb.Active,
}, nil
},
},
},
args: args{
checkID: "020f755c3c082000",
user: &influxdb.User{
ID: influxTesting.MustIDBase16("6f626f7274697320"),
},
},
wants: wants{
statusCode: http.StatusCreated,
contentType: "application/json; charset=utf-8",
body: `
{
"links": {
"logs": "/api/v2/users/6f626f7274697320/logs",
"self": "/api/v2/users/6f626f7274697320"
},
"role": "member",
"id": "6f626f7274697320",
"name": "name",
"status": "active"
}
`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
checkBackend := NewMockCheckBackend(t)
checkBackend.UserService = tt.fields.UserService
checkBackend.TaskService = &mock.TaskService{
FindTaskByIDFn: func(ctx context.Context, id platform.ID) (*taskmodel.Task, error) {
return &taskmodel.Task{}, nil
},
}
h := NewCheckHandler(zaptest.NewLogger(t), checkBackend)
b, err := json.Marshal(tt.args.user)
if err != nil {
t.Fatalf("failed to marshal user: %v", err)
}
path := fmt.Sprintf("/api/v2/checks/%s/members", tt.args.checkID)
r := httptest.NewRequest("POST", path, bytes.NewReader(b))
w := httptest.NewRecorder()
h.ServeHTTP(w, r)
res := w.Result()
content := res.Header.Get("Content-Type")
body, _ := io.ReadAll(res.Body)
if res.StatusCode != tt.wants.statusCode {
t.Errorf("%q. handlePostCheckMember() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. handlePostCheckMember() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil {
t.Errorf("%q, handlePostCheckMember(). error unmarshalling json %v", tt.name, err)
} else if tt.wants.body != "" && !eq {
t.Errorf("%q. handlePostCheckMember() = ***%s***", tt.name, diff)
}
})
}
}
func TestService_handlePostCheckOwner(t *testing.T) {
type fields struct {
UserService influxdb.UserService
}
type args struct {
checkID string
user *influxdb.User
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "add a check owner",
fields: fields{
UserService: &mock.UserService{
FindUserByIDFn: func(ctx context.Context, id platform.ID) (*influxdb.User, error) {
return &influxdb.User{
ID: id,
Name: "name",
Status: influxdb.Active,
}, nil
},
},
},
args: args{
checkID: "020f755c3c082000",
user: &influxdb.User{
ID: influxTesting.MustIDBase16("6f626f7274697320"),
},
},
wants: wants{
statusCode: http.StatusCreated,
contentType: "application/json; charset=utf-8",
body: `
{
"links": {
"logs": "/api/v2/users/6f626f7274697320/logs",
"self": "/api/v2/users/6f626f7274697320"
},
"role": "owner",
"id": "6f626f7274697320",
"name": "name",
"status": "active"
}
`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
checkBackend := NewMockCheckBackend(t)
checkBackend.UserService = tt.fields.UserService
h := NewCheckHandler(zaptest.NewLogger(t), checkBackend)
b, err := json.Marshal(tt.args.user)
if err != nil {
t.Fatalf("failed to marshal user: %v", err)
}
path := fmt.Sprintf("/api/v2/checks/%s/owners", tt.args.checkID)
r := httptest.NewRequest("POST", path, bytes.NewReader(b))
w := httptest.NewRecorder()
h.ServeHTTP(w, r)
res := w.Result()
content := res.Header.Get("Content-Type")
body, _ := io.ReadAll(res.Body)
if res.StatusCode != tt.wants.statusCode {
t.Errorf("%q. handlePostCheckOwner() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. handlePostCheckOwner() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil {
t.Errorf("%q, handlePostCheckOwner(). error unmarshalling json %v", tt.name, err)
} else if tt.wants.body != "" && !eq {
t.Errorf("%q. handlePostCheckOwner() = ***%s***", tt.name, diff)
}
})
}
}