chronograf/server/roles_test.go

698 lines
19 KiB
Go

package server
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/bouk/httprouter"
"github.com/influxdata/chronograf"
"github.com/influxdata/chronograf/log"
"github.com/influxdata/chronograf/mocks"
)
func TestService_NewSourceRole(t *testing.T) {
type fields struct {
SourcesStore chronograf.SourcesStore
TimeSeries TimeSeriesClient
Logger chronograf.Logger
}
type args struct {
w *httptest.ResponseRecorder
r *http.Request
}
tests := []struct {
name string
fields fields
args args
ID string
wantStatus int
wantContentType string
wantBody string
}{
{
name: "Bad JSON",
args: args{
w: httptest.NewRecorder(),
r: httptest.NewRequest(
"POST",
"http://server.local/chronograf/v1/sources/1/roles",
ioutil.NopCloser(
bytes.NewReader([]byte(`{BAD}`)))),
},
fields: fields{
Logger: log.New(log.DebugLevel),
},
wantStatus: http.StatusBadRequest,
wantContentType: "application/json",
wantBody: `{"code":400,"message":"Unparsable JSON"}`,
},
{
name: "Invalid request",
args: args{
w: httptest.NewRecorder(),
r: httptest.NewRequest(
"POST",
"http://server.local/chronograf/v1/sources/1/roles",
ioutil.NopCloser(
bytes.NewReader([]byte(`{"name": ""}`)))),
},
fields: fields{
Logger: log.New(log.DebugLevel),
},
ID: "1",
wantStatus: http.StatusUnprocessableEntity,
wantContentType: "application/json",
wantBody: `{"code":422,"message":"Name is required for a role"}`,
},
{
name: "Invalid source ID",
args: args{
w: httptest.NewRecorder(),
r: httptest.NewRequest(
"POST",
"http://server.local/chronograf/v1/sources/1/roles",
ioutil.NopCloser(
bytes.NewReader([]byte(`{"name": "newrole"}`)))),
},
fields: fields{
Logger: log.New(log.DebugLevel),
},
ID: "BADROLE",
wantStatus: http.StatusUnprocessableEntity,
wantContentType: "application/json",
wantBody: `{"code":422,"message":"Error converting ID BADROLE"}`,
},
{
name: "Source doesn't support roles",
args: args{
w: httptest.NewRecorder(),
r: httptest.NewRequest(
"POST",
"http://server.local/chronograf/v1/sources/1/roles",
ioutil.NopCloser(
bytes.NewReader([]byte(`{"name": "role"}`)))),
},
fields: fields{
Logger: log.New(log.DebugLevel),
SourcesStore: &mocks.SourcesStore{
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
return chronograf.Source{
ID: 1,
Name: "muh source",
Username: "name",
Password: "hunter2",
URL: "http://localhost:8086",
}, nil
},
},
TimeSeries: &mocks.TimeSeries{
ConnectF: func(ctx context.Context, src *chronograf.Source) error {
return nil
},
RolesF: func(ctx context.Context) (chronograf.RolesStore, error) {
return nil, fmt.Errorf("roles not supported")
},
},
},
ID: "1",
wantStatus: http.StatusNotFound,
wantContentType: "application/json",
wantBody: `{"code":404,"message":"Source 1 does not have role capability"}`,
},
{
name: "Unable to add role to server",
args: args{
w: httptest.NewRecorder(),
r: httptest.NewRequest(
"POST",
"http://server.local/chronograf/v1/sources/1/roles",
ioutil.NopCloser(
bytes.NewReader([]byte(`{"name": "role"}`)))),
},
fields: fields{
Logger: log.New(log.DebugLevel),
SourcesStore: &mocks.SourcesStore{
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
return chronograf.Source{
ID: 1,
Name: "muh source",
Username: "name",
Password: "hunter2",
URL: "http://localhost:8086",
}, nil
},
},
TimeSeries: &mocks.TimeSeries{
ConnectF: func(ctx context.Context, src *chronograf.Source) error {
return nil
},
RolesF: func(ctx context.Context) (chronograf.RolesStore, error) {
return &mocks.RolesStore{
AddF: func(ctx context.Context, u *chronograf.Role) (*chronograf.Role, error) {
return nil, fmt.Errorf("server had and issue")
},
GetF: func(ctx context.Context, name string) (*chronograf.Role, error) {
return nil, fmt.Errorf("No such role")
},
}, nil
},
},
},
ID: "1",
wantStatus: http.StatusBadRequest,
wantContentType: "application/json",
wantBody: `{"code":400,"message":"server had and issue"}`,
},
{
name: "New role for data source",
args: args{
w: httptest.NewRecorder(),
r: httptest.NewRequest(
"POST",
"http://server.local/chronograf/v1/sources/1/roles",
ioutil.NopCloser(
bytes.NewReader([]byte(`{"name": "biffsgang","users": [{"name": "match"},{"name": "skinhead"},{"name": "3-d"}]}`)))),
},
fields: fields{
Logger: log.New(log.DebugLevel),
SourcesStore: &mocks.SourcesStore{
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
return chronograf.Source{
ID: 1,
Name: "muh source",
Username: "name",
Password: "hunter2",
URL: "http://localhost:8086",
}, nil
},
},
TimeSeries: &mocks.TimeSeries{
ConnectF: func(ctx context.Context, src *chronograf.Source) error {
return nil
},
RolesF: func(ctx context.Context) (chronograf.RolesStore, error) {
return &mocks.RolesStore{
AddF: func(ctx context.Context, u *chronograf.Role) (*chronograf.Role, error) {
return u, nil
},
GetF: func(ctx context.Context, name string) (*chronograf.Role, error) {
return nil, fmt.Errorf("no such role")
},
}, nil
},
},
},
ID: "1",
wantStatus: http.StatusCreated,
wantContentType: "application/json",
wantBody: `{"users":[{"links":{"self":"/chronograf/v1/sources/1/users/match"},"name":"match"},{"links":{"self":"/chronograf/v1/sources/1/users/skinhead"},"name":"skinhead"},{"links":{"self":"/chronograf/v1/sources/1/users/3-d"},"name":"3-d"}],"name":"biffsgang","permissions":[],"links":{"self":"/chronograf/v1/sources/1/roles/biffsgang"}}
`,
},
}
for _, tt := range tests {
h := &Service{
SourcesStore: tt.fields.SourcesStore,
TimeSeriesClient: tt.fields.TimeSeries,
Logger: tt.fields.Logger,
}
tt.args.r = tt.args.r.WithContext(httprouter.WithParams(
context.Background(),
httprouter.Params{
{
Key: "id",
Value: tt.ID,
},
}))
h.NewRole(tt.args.w, tt.args.r)
resp := tt.args.w.Result()
content := resp.Header.Get("Content-Type")
body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != tt.wantStatus {
t.Errorf("%q. NewRole() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus)
}
if tt.wantContentType != "" && content != tt.wantContentType {
t.Errorf("%q. NewRole() = %v, want %v", tt.name, content, tt.wantContentType)
}
if tt.wantBody != "" && string(body) != tt.wantBody {
t.Errorf("%q. NewRole() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody)
}
}
}
func TestService_UpdateRole(t *testing.T) {
type fields struct {
SourcesStore chronograf.SourcesStore
TimeSeries TimeSeriesClient
Logger chronograf.Logger
}
type args struct {
w *httptest.ResponseRecorder
r *http.Request
}
tests := []struct {
name string
fields fields
args args
ID string
RoleID string
wantStatus int
wantContentType string
wantBody string
}{
{
name: "Update role for data source",
args: args{
w: httptest.NewRecorder(),
r: httptest.NewRequest(
"POST",
"http://server.local/chronograf/v1/sources/1/roles",
ioutil.NopCloser(
bytes.NewReader([]byte(`{"name": "biffsgang","users": [{"name": "match"},{"name": "skinhead"},{"name": "3-d"}]}`)))),
},
fields: fields{
Logger: log.New(log.DebugLevel),
SourcesStore: &mocks.SourcesStore{
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
return chronograf.Source{
ID: 1,
Name: "muh source",
Username: "name",
Password: "hunter2",
URL: "http://localhost:8086",
}, nil
},
},
TimeSeries: &mocks.TimeSeries{
ConnectF: func(ctx context.Context, src *chronograf.Source) error {
return nil
},
RolesF: func(ctx context.Context) (chronograf.RolesStore, error) {
return &mocks.RolesStore{
UpdateF: func(ctx context.Context, u *chronograf.Role) error {
return nil
},
GetF: func(ctx context.Context, name string) (*chronograf.Role, error) {
return &chronograf.Role{
Name: "biffsgang",
Users: []chronograf.User{
{
Name: "match",
},
{
Name: "skinhead",
},
{
Name: "3-d",
},
},
}, nil
},
}, nil
},
},
},
ID: "1",
RoleID: "biffsgang",
wantStatus: http.StatusOK,
wantContentType: "application/json",
wantBody: `{"users":[{"links":{"self":"/chronograf/v1/sources/1/users/match"},"name":"match"},{"links":{"self":"/chronograf/v1/sources/1/users/skinhead"},"name":"skinhead"},{"links":{"self":"/chronograf/v1/sources/1/users/3-d"},"name":"3-d"}],"name":"biffsgang","permissions":[],"links":{"self":"/chronograf/v1/sources/1/roles/biffsgang"}}
`,
},
}
for _, tt := range tests {
h := &Service{
SourcesStore: tt.fields.SourcesStore,
TimeSeriesClient: tt.fields.TimeSeries,
Logger: tt.fields.Logger,
}
tt.args.r = tt.args.r.WithContext(httprouter.WithParams(
context.Background(),
httprouter.Params{
{
Key: "id",
Value: tt.ID,
},
{
Key: "rid",
Value: tt.RoleID,
},
}))
h.UpdateRole(tt.args.w, tt.args.r)
resp := tt.args.w.Result()
content := resp.Header.Get("Content-Type")
body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != tt.wantStatus {
t.Errorf("%q. NewRole() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus)
}
if tt.wantContentType != "" && content != tt.wantContentType {
t.Errorf("%q. NewRole() = %v, want %v", tt.name, content, tt.wantContentType)
}
if tt.wantBody != "" && string(body) != tt.wantBody {
t.Errorf("%q. NewRole() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody)
}
}
}
func TestService_RoleID(t *testing.T) {
type fields struct {
SourcesStore chronograf.SourcesStore
TimeSeries TimeSeriesClient
Logger chronograf.Logger
}
type args struct {
w *httptest.ResponseRecorder
r *http.Request
}
tests := []struct {
name string
fields fields
args args
ID string
RoleID string
wantStatus int
wantContentType string
wantBody string
}{
{
name: "Get role for data source",
args: args{
w: httptest.NewRecorder(),
r: httptest.NewRequest(
"GET",
"http://server.local/chronograf/v1/sources/1/roles/biffsgang",
nil),
},
fields: fields{
Logger: log.New(log.DebugLevel),
SourcesStore: &mocks.SourcesStore{
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
return chronograf.Source{
ID: 1,
Name: "muh source",
Username: "name",
Password: "hunter2",
URL: "http://localhost:8086",
}, nil
},
},
TimeSeries: &mocks.TimeSeries{
ConnectF: func(ctx context.Context, src *chronograf.Source) error {
return nil
},
RolesF: func(ctx context.Context) (chronograf.RolesStore, error) {
return &mocks.RolesStore{
GetF: func(ctx context.Context, name string) (*chronograf.Role, error) {
return &chronograf.Role{
Name: "biffsgang",
Permissions: chronograf.Permissions{
{
Name: "grays_sports_almanac",
Scope: "DBScope",
Allowed: chronograf.Allowances{
"ReadData",
},
},
},
Users: []chronograf.User{
{
Name: "match",
},
{
Name: "skinhead",
},
{
Name: "3-d",
},
},
}, nil
},
}, nil
},
},
},
ID: "1",
RoleID: "biffsgang",
wantStatus: http.StatusOK,
wantContentType: "application/json",
wantBody: `{"users":[{"links":{"self":"/chronograf/v1/sources/1/users/match"},"name":"match"},{"links":{"self":"/chronograf/v1/sources/1/users/skinhead"},"name":"skinhead"},{"links":{"self":"/chronograf/v1/sources/1/users/3-d"},"name":"3-d"}],"name":"biffsgang","permissions":[{"scope":"DBScope","name":"grays_sports_almanac","allowed":["ReadData"]}],"links":{"self":"/chronograf/v1/sources/1/roles/biffsgang"}}
`,
},
}
for _, tt := range tests {
h := &Service{
SourcesStore: tt.fields.SourcesStore,
TimeSeriesClient: tt.fields.TimeSeries,
Logger: tt.fields.Logger,
}
tt.args.r = tt.args.r.WithContext(httprouter.WithParams(
context.Background(),
httprouter.Params{
{
Key: "id",
Value: tt.ID,
},
{
Key: "rid",
Value: tt.RoleID,
},
}))
h.RoleID(tt.args.w, tt.args.r)
resp := tt.args.w.Result()
content := resp.Header.Get("Content-Type")
body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != tt.wantStatus {
t.Errorf("%q. RoleID() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus)
}
if tt.wantContentType != "" && content != tt.wantContentType {
t.Errorf("%q. RoleID() = %v, want %v", tt.name, content, tt.wantContentType)
}
if tt.wantBody != "" && string(body) != tt.wantBody {
t.Errorf("%q. RoleID() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody)
}
}
}
func TestService_RemoveRole(t *testing.T) {
type fields struct {
SourcesStore chronograf.SourcesStore
TimeSeries TimeSeriesClient
Logger chronograf.Logger
}
type args struct {
w *httptest.ResponseRecorder
r *http.Request
}
tests := []struct {
name string
fields fields
args args
ID string
RoleID string
wantStatus int
}{
{
name: "remove role for data source",
args: args{
w: httptest.NewRecorder(),
r: httptest.NewRequest(
"GET",
"http://server.local/chronograf/v1/sources/1/roles/biffsgang",
nil),
},
fields: fields{
Logger: log.New(log.DebugLevel),
SourcesStore: &mocks.SourcesStore{
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
return chronograf.Source{
ID: 1,
Name: "muh source",
Username: "name",
Password: "hunter2",
URL: "http://localhost:8086",
}, nil
},
},
TimeSeries: &mocks.TimeSeries{
ConnectF: func(ctx context.Context, src *chronograf.Source) error {
return nil
},
RolesF: func(ctx context.Context) (chronograf.RolesStore, error) {
return &mocks.RolesStore{
DeleteF: func(context.Context, *chronograf.Role) error {
return nil
},
}, nil
},
},
},
ID: "1",
RoleID: "biffsgang",
wantStatus: http.StatusNoContent,
},
}
for _, tt := range tests {
h := &Service{
SourcesStore: tt.fields.SourcesStore,
TimeSeriesClient: tt.fields.TimeSeries,
Logger: tt.fields.Logger,
}
tt.args.r = tt.args.r.WithContext(httprouter.WithParams(
context.Background(),
httprouter.Params{
{
Key: "id",
Value: tt.ID,
},
{
Key: "rid",
Value: tt.RoleID,
},
}))
h.RemoveRole(tt.args.w, tt.args.r)
resp := tt.args.w.Result()
if resp.StatusCode != tt.wantStatus {
t.Errorf("%q. RemoveRole() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus)
}
}
}
func TestService_Roles(t *testing.T) {
type fields struct {
SourcesStore chronograf.SourcesStore
TimeSeries TimeSeriesClient
Logger chronograf.Logger
}
type args struct {
w *httptest.ResponseRecorder
r *http.Request
}
tests := []struct {
name string
fields fields
args args
ID string
RoleID string
wantStatus int
wantContentType string
wantBody string
}{
{
name: "Get roles for data source",
args: args{
w: httptest.NewRecorder(),
r: httptest.NewRequest(
"GET",
"http://server.local/chronograf/v1/sources/1/roles",
nil),
},
fields: fields{
Logger: log.New(log.DebugLevel),
SourcesStore: &mocks.SourcesStore{
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
return chronograf.Source{
ID: 1,
}, nil
},
},
TimeSeries: &mocks.TimeSeries{
ConnectF: func(ctx context.Context, src *chronograf.Source) error {
return nil
},
RolesF: func(ctx context.Context) (chronograf.RolesStore, error) {
return &mocks.RolesStore{
AllF: func(ctx context.Context) ([]chronograf.Role, error) {
return []chronograf.Role{
chronograf.Role{
Name: "biffsgang",
Permissions: chronograf.Permissions{
{
Name: "grays_sports_almanac",
Scope: "DBScope",
Allowed: chronograf.Allowances{
"ReadData",
},
},
},
Users: []chronograf.User{
{
Name: "match",
},
{
Name: "skinhead",
},
{
Name: "3-d",
},
},
},
}, nil
},
}, nil
},
},
},
ID: "1",
RoleID: "biffsgang",
wantStatus: http.StatusOK,
wantContentType: "application/json",
wantBody: `{"roles":[{"users":[{"links":{"self":"/chronograf/v1/sources/1/users/match"},"name":"match"},{"links":{"self":"/chronograf/v1/sources/1/users/skinhead"},"name":"skinhead"},{"links":{"self":"/chronograf/v1/sources/1/users/3-d"},"name":"3-d"}],"name":"biffsgang","permissions":[{"scope":"DBScope","name":"grays_sports_almanac","allowed":["ReadData"]}],"links":{"self":"/chronograf/v1/sources/1/roles/biffsgang"}}]}
`,
},
}
for _, tt := range tests {
h := &Service{
SourcesStore: tt.fields.SourcesStore,
TimeSeriesClient: tt.fields.TimeSeries,
Logger: tt.fields.Logger,
}
tt.args.r = tt.args.r.WithContext(httprouter.WithParams(
context.Background(),
httprouter.Params{
{
Key: "id",
Value: tt.ID,
},
{
Key: "rid",
Value: tt.RoleID,
},
}))
h.Roles(tt.args.w, tt.args.r)
resp := tt.args.w.Result()
content := resp.Header.Get("Content-Type")
body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != tt.wantStatus {
t.Errorf("%q. RoleID() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus)
}
if tt.wantContentType != "" && content != tt.wantContentType {
t.Errorf("%q. RoleID() = %v, want %v", tt.name, content, tt.wantContentType)
}
if tt.wantBody != "" && string(body) != tt.wantBody {
t.Errorf("%q. RoleID() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody)
}
}
}