influxdb/chronograf/server/org_config_test.go

1077 lines
26 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package server
import (
"bytes"
"context"
"encoding/json"
"io/ioutil"
"net/http/httptest"
"testing"
"github.com/influxdata/influxdb/v2/chronograf"
"github.com/influxdata/influxdb/v2/chronograf/mocks"
"github.com/influxdata/influxdb/v2/chronograf/organizations"
)
func TestOrganizationConfig(t *testing.T) {
type args struct {
organizationID string
}
type fields struct {
organizationConfigStore chronograf.OrganizationConfigStore
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
args args
fields fields
wants wants
}{
{
name: "Get organization configuration",
args: args{
organizationID: "default",
},
fields: fields{
organizationConfigStore: &mocks.OrganizationConfigStore{
FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
switch orgID {
case "default":
return &chronograf.OrganizationConfig{
OrganizationID: "default",
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "time",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "hidden",
},
},
},
{
Name: "severity",
Position: 1,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "label",
Value: "icon",
},
{
Type: "label",
Value: "text",
},
},
},
{
Name: "timestamp",
Position: 2,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "message",
Position: 3,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "facility",
Position: 4,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "procid",
Position: 5,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "displayName",
Value: "Proc ID",
},
},
},
{
Name: "appname",
Position: 6,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "displayName",
Value: "Application",
},
},
},
{
Name: "host",
Position: 7,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
},
},
}, nil
default:
return nil, chronograf.ErrOrganizationConfigNotFound
}
},
},
},
wants: wants{
statusCode: 200,
contentType: "application/json",
body: `{"links":{"self":"/chronograf/v1/org_config","logViewer":"/chronograf/v1/org_config/logviewer"},"organization":"default","logViewer":{"columns":[{"name":"time","position":0,"encodings":[{"type":"visibility","value":"hidden"}]},{"name":"severity","position":1,"encodings":[{"type":"visibility","value":"visible"},{"type":"label","value":"icon"},{"type":"label","value":"text"}]},{"name":"timestamp","position":2,"encodings":[{"type":"visibility","value":"visible"}]},{"name":"message","position":3,"encodings":[{"type":"visibility","value":"visible"}]},{"name":"facility","position":4,"encodings":[{"type":"visibility","value":"visible"}]},{"name":"procid","position":5,"encodings":[{"type":"visibility","value":"visible"},{"type":"displayName","value":"Proc ID"}]},{"name":"appname","position":6,"encodings":[{"type":"visibility","value":"visible"},{"type":"displayName","value":"Application"}]},{"name":"host","position":7,"encodings":[{"type":"visibility","value":"visible"}]}]}}`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Service{
Store: &mocks.Store{
OrganizationConfigStore: tt.fields.organizationConfigStore,
},
Logger: &chronograf.NoopLogger{},
}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "http://any.url", nil)
ctx := context.WithValue(r.Context(), organizations.ContextKey, tt.args.organizationID)
r = r.WithContext(ctx)
s.OrganizationConfig(w, r)
resp := w.Result()
content := resp.Header.Get("Content-Type")
body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != tt.wants.statusCode {
t.Errorf("%q. OrganizationConfig() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode)
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. OrganizationConfig() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
t.Errorf("%q. OrganizationConfig() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body)
}
})
}
}
func TestLogViewerOrganizationConfig(t *testing.T) {
type args struct {
organizationID string
}
type fields struct {
organizationConfigStore chronograf.OrganizationConfigStore
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
args args
fields fields
wants wants
}{
{
name: "Get log viewer configuration",
args: args{
organizationID: "default",
},
fields: fields{
organizationConfigStore: &mocks.OrganizationConfigStore{
FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
switch orgID {
case "default":
return &chronograf.OrganizationConfig{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "severity",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "color",
Value: "emergency",
Name: "ruby",
},
{
Type: "color",
Value: "info",
Name: "rainforest",
},
{
Type: "displayName",
Value: "Log Severity",
},
},
},
},
},
}, nil
default:
return nil, chronograf.ErrOrganizationConfigNotFound
}
},
},
},
wants: wants{
statusCode: 200,
contentType: "application/json",
body: `{"links":{"self":"/chronograf/v1/org_config/logviewer"},"columns":[{"name":"severity","position":0,"encodings":[{"type":"color","value":"emergency","name":"ruby"},{"type":"color","value":"info","name":"rainforest"},{"type":"displayName","value":"Log Severity"}]}]}`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Service{
Store: &mocks.Store{
OrganizationConfigStore: tt.fields.organizationConfigStore,
},
Logger: &chronograf.NoopLogger{},
}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "http://any.url", nil)
ctx := context.WithValue(r.Context(), organizations.ContextKey, tt.args.organizationID)
r = r.WithContext(ctx)
s.OrganizationLogViewerConfig(w, r)
resp := w.Result()
content := resp.Header.Get("Content-Type")
body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != tt.wants.statusCode {
t.Errorf("%q. LogViewerOrganizationConfig() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode)
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. LogViewerOrganizationConfig() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
t.Errorf("%q. LogViewerOrganizationConfig() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body)
}
})
}
}
func TestReplaceLogViewerOrganizationConfig(t *testing.T) {
type fields struct {
organizationConfigStore chronograf.OrganizationConfigStore
}
type args struct {
payload interface{} // expects JSON serializable struct
organizationID string
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "Set log viewer configuration",
fields: fields{
organizationConfigStore: &mocks.OrganizationConfigStore{
FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
switch orgID {
case "1337":
return &chronograf.OrganizationConfig{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "severity",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "color",
Value: "info",
Name: "rainforest",
},
{
Type: "visibility",
Value: "visible",
},
{
Type: "label",
Value: "icon",
},
},
},
},
},
}, nil
default:
return nil, chronograf.ErrOrganizationConfigNotFound
}
},
PutF: func(ctx context.Context, target *chronograf.OrganizationConfig) error {
return nil
},
},
},
args: args{
payload: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "severity",
Position: 1,
Encodings: []chronograf.ColumnEncoding{
{
Type: "color",
Value: "info",
Name: "pineapple",
},
{
Type: "color",
Value: "emergency",
Name: "ruby",
},
{
Type: "visibility",
Value: "visible",
},
{
Type: "label",
Value: "icon",
},
},
},
{
Name: "messages",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "displayName",
Value: "Log Messages",
},
{
Type: "visibility",
Value: "visible",
},
},
},
},
},
organizationID: "1337",
},
wants: wants{
statusCode: 200,
contentType: "application/json",
body: `{"links":{"self":"/chronograf/v1/org_config/logviewer"},"columns":[{"name":"severity","position":1,"encodings":[{"type":"color","value":"info","name":"pineapple"},{"type":"color","value":"emergency","name":"ruby"},{"type":"visibility","value":"visible"},{"type":"label","value":"icon"}]},{"name":"messages","position":0,"encodings":[{"type":"displayName","value":"Log Messages"},{"type":"visibility","value":"visible"}]}]}`,
},
},
{
name: "Set invalid log viewer configuration empty",
fields: fields{
organizationConfigStore: &mocks.OrganizationConfigStore{
FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
switch orgID {
case "1337":
return &chronograf.OrganizationConfig{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "severity",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "color",
Value: "info",
Name: "rainforest",
},
{
Type: "label",
Value: "icon",
},
{
Type: "visibility",
Value: "visible",
},
},
},
},
},
}, nil
default:
return nil, chronograf.ErrOrganizationConfigNotFound
}
},
PutF: func(ctx context.Context, target *chronograf.OrganizationConfig) error {
return nil
},
},
},
args: args{
payload: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{},
},
organizationID: "1337",
},
wants: wants{
statusCode: 400,
contentType: "application/json",
body: `{"code":400,"message":"invalid log viewer config: must have at least 1 column"}`,
},
},
{
name: "Set invalid log viewer configuration - duplicate column name",
fields: fields{
organizationConfigStore: &mocks.OrganizationConfigStore{
FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
switch orgID {
case "1337":
return &chronograf.OrganizationConfig{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "procid",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "hidden",
},
},
},
},
},
}, nil
default:
return nil, chronograf.ErrOrganizationConfigNotFound
}
},
PutF: func(ctx context.Context, target *chronograf.OrganizationConfig) error {
return nil
},
},
},
args: args{
payload: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "procid",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "hidden",
},
},
},
{
Name: "procid",
Position: 1,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "hidden",
},
},
},
},
},
organizationID: "1337",
},
wants: wants{
statusCode: 400,
contentType: "application/json",
body: `{"code":400,"message":"invalid log viewer config: Duplicate column name procid"}`,
},
},
{
name: "Set invalid log viewer configuration - multiple columns with same position value",
fields: fields{
organizationConfigStore: &mocks.OrganizationConfigStore{
FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
switch orgID {
case "1337":
return &chronograf.OrganizationConfig{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "procid",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "hidden",
},
},
},
},
},
}, nil
default:
return nil, chronograf.ErrOrganizationConfigNotFound
}
},
PutF: func(ctx context.Context, target *chronograf.OrganizationConfig) error {
return nil
},
},
},
args: args{
payload: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "procid",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "hidden",
},
},
},
{
Name: "timestamp",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "hidden",
},
},
},
},
},
organizationID: "1337",
},
wants: wants{
statusCode: 400,
contentType: "application/json",
body: `{"code":400,"message":"invalid log viewer config: Multiple columns with same position value"}`,
},
},
{
name: "Set invalid log viewer configuration no visibility",
fields: fields{
organizationConfigStore: &mocks.OrganizationConfigStore{
FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
switch orgID {
case "1337":
return &chronograf.OrganizationConfig{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "severity",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "color",
Value: "info",
Name: "rainforest",
},
{
Type: "label",
Value: "icon",
},
},
},
},
},
}, nil
default:
return nil, chronograf.ErrOrganizationConfigNotFound
}
},
PutF: func(ctx context.Context, target *chronograf.OrganizationConfig) error {
return nil
},
},
},
args: args{
payload: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "severity",
Position: 1,
Encodings: []chronograf.ColumnEncoding{
{
Type: "color",
Value: "info",
Name: "pineapple",
},
{
Type: "color",
Value: "emergency",
Name: "ruby",
},
{
Type: "label",
Value: "icon",
},
},
},
},
},
organizationID: "1337",
},
wants: wants{
statusCode: 400,
contentType: "application/json",
body: `{"code":400,"message":"invalid log viewer config: missing visibility encoding in column severity"}`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Service{
Store: &mocks.Store{
OrganizationConfigStore: tt.fields.organizationConfigStore,
},
Logger: &chronograf.NoopLogger{},
}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "http://any.url", nil)
ctx := context.WithValue(r.Context(), organizations.ContextKey, tt.args.organizationID)
r = r.WithContext(ctx)
buf, _ := json.Marshal(tt.args.payload)
r.Body = ioutil.NopCloser(bytes.NewReader(buf))
s.ReplaceOrganizationLogViewerConfig(w, r)
resp := w.Result()
content := resp.Header.Get("Content-Type")
body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != tt.wants.statusCode {
t.Errorf("%q. ReplaceLogViewerOrganizationConfig() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode)
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. ReplaceLogViewerOrganizationConfig() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
t.Errorf("%q. ReplaceLogViewerOrganizationConfig() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body)
}
})
}
}
func Test_validLogViewerConfig(t *testing.T) {
type args struct {
LogViewer chronograf.LogViewerConfig
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "cannot have 0 columns",
args: args{
LogViewer: chronograf.LogViewerConfig{
Columns: nil,
},
},
wantErr: true,
},
{
name: "can have 1 column",
args: args{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "timestamp",
Position: 2,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
},
},
},
wantErr: false,
},
{
name: "can have more than 1 column",
args: args{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "timestamp",
Position: 2,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "message",
Position: 3,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "facility",
Position: 4,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
},
},
},
wantErr: false,
},
{
name: "cannot have multiple columns with the same name value",
args: args{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "timestamp",
Position: 2,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "timestamp",
Position: 3,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
},
},
},
wantErr: true,
},
{
name: "cannot have multiple columns with the same position value",
args: args{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "timestamp",
Position: 2,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "message",
Position: 2,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
},
},
},
wantErr: true,
},
{
name: "each column must have a visibility encoding value of either 'visible' or 'hidden'",
args: args{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "timestamp",
Position: 2,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "bob",
},
},
},
{
Name: "message",
Position: 3,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
},
},
},
wantErr: true,
},
{
name: "severity column can have 1 of each icon and text label encoding",
args: args{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "severity",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "color",
Value: "info",
Name: "rainforest",
},
{
Type: "label",
Value: "icon",
},
{
Type: "label",
Value: "text",
},
},
},
},
},
},
wantErr: false,
},
{
name: "severity column can 1 icon label encoding",
args: args{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "severity",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "color",
Value: "info",
Name: "rainforest",
},
{
Type: "label",
Value: "icon",
},
},
},
},
},
},
wantErr: false,
},
{
name: "severity column can have 1 text label encoding",
args: args{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "severity",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "color",
Value: "info",
Name: "rainforest",
},
{
Type: "label",
Value: "text",
},
},
},
},
},
},
wantErr: false,
},
{
name: "severity column cannot have 0 label encodings",
args: args{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "severity",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "color",
Value: "info",
Name: "rainforest",
},
},
},
},
},
},
wantErr: true,
},
{
name: "severity column cannot have more than 1 icon label encoding",
args: args{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "severity",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "color",
Value: "info",
Name: "rainforest",
},
{
Type: "label",
Value: "icon",
},
{
Type: "label",
Value: "icon",
},
},
},
},
},
},
wantErr: true,
},
{
name: "severity column cannot have more than 1 text label encoding",
args: args{
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "severity",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "color",
Value: "info",
Name: "rainforest",
},
{
Type: "label",
Value: "text",
},
{
Type: "label",
Value: "text",
},
},
},
},
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := validLogViewerConfig(tt.args.LogViewer)
if (tt.wantErr && got == nil) || (!tt.wantErr && got != nil) {
t.Errorf("%q. validLogViewerConfig().\ngot: %v\nwantErr: %v", tt.name, got, tt.wantErr)
}
})
}
}