From 0b95dfecbb87a60a47b16329628e2b351a84a85b Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Tue, 21 Feb 2017 18:34:53 -0600 Subject: [PATCH] Add tests for meta users and roles --- enterprise/enterprise.go | 9 +- enterprise/enterprise_test.go | 19 +- enterprise/{meta_client.go => meta.go} | 121 +-- enterprise/meta_test.go | 1189 ++++++++++++++++++++++++ 4 files changed, 1268 insertions(+), 70 deletions(-) rename enterprise/{meta_client.go => meta.go} (65%) create mode 100644 enterprise/meta_test.go diff --git a/enterprise/enterprise.go b/enterprise/enterprise.go index eca32c215..7926d3835 100644 --- a/enterprise/enterprise.go +++ b/enterprise/enterprise.go @@ -51,16 +51,15 @@ func NewClientWithTimeSeries(lg chronograf.Logger, series ...chronograf.TimeSeri // Acceptable URLs include host:port combinations as well as scheme://host:port // varieties. TLS is used when the URL contains "https" or when the TLS // parameter is set. The latter option is provided for host:port combinations -func NewClientWithURL(mu string, tls bool, lg chronograf.Logger) (*Client, error) { +// Username and Password are used for Basic Auth +func NewClientWithURL(mu, username, password string, tls bool, lg chronograf.Logger) (*Client, error) { metaURL, err := parseMetaURL(mu, tls) if err != nil { return nil, err } - + metaURL.User = url.UserPassword(username, password) return &Client{ - Ctrl: &MetaClient{ - MetaHostPort: metaURL.Host, - }, + Ctrl: NewMetaClient(metaURL), Logger: lg, }, nil } diff --git a/enterprise/enterprise_test.go b/enterprise/enterprise_test.go index f240a9847..6fc8a64a4 100644 --- a/enterprise/enterprise_test.go +++ b/enterprise/enterprise_test.go @@ -98,23 +98,26 @@ func Test_Enterprise_NewClientWithURL(t *testing.T) { urls := []struct { url string + username string + password string tls bool shouldErr bool }{ - {"http://localhost:8086", false, false}, - {"https://localhost:8086", false, false}, + {"http://localhost:8086", "", "", false, false}, + {"https://localhost:8086", "", "", false, false}, + {"http://localhost:8086", "username", "password", false, false}, - {"http://localhost:8086", true, false}, - {"https://localhost:8086", true, false}, + {"http://localhost:8086", "", "", true, false}, + {"https://localhost:8086", "", "", true, false}, - {"localhost:8086", false, false}, - {"localhost:8086", true, false}, + {"localhost:8086", "", "", false, false}, + {"localhost:8086", "", "", true, false}, - {":http", false, true}, + {":http", "", "", false, true}, } for _, testURL := range urls { - _, err := enterprise.NewClientWithURL(testURL.url, testURL.tls, log.New(log.DebugLevel)) + _, err := enterprise.NewClientWithURL(testURL.url, testURL.username, testURL.password, testURL.tls, log.New(log.DebugLevel)) if err != nil && !testURL.shouldErr { t.Errorf("Unexpected error creating Client with URL %s and TLS preference %t. err: %s", testURL.url, testURL.tls, err.Error()) } else if err == nil && testURL.shouldErr { diff --git a/enterprise/meta_client.go b/enterprise/meta.go similarity index 65% rename from enterprise/meta_client.go rename to enterprise/meta.go index 45eafbd3d..1a1274369 100644 --- a/enterprise/meta_client.go +++ b/enterprise/meta.go @@ -15,14 +15,23 @@ import ( // MetaClient represents a Meta node in an Influx Enterprise cluster type MetaClient struct { - MetaHostPort string - Username string - Password string + URL *url.URL + client interface { + Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error) + } +} + +// NewMetaClient represents a meta node in an Influx Enterprise cluster +func NewMetaClient(url *url.URL) *MetaClient { + return &MetaClient{ + URL: url, + client: &defaultClient{}, + } } // ShowCluster returns the cluster configuration (not health) -func (t *MetaClient) ShowCluster(ctx context.Context) (*Cluster, error) { - res, err := t.Do(ctx, "GET", "/show-cluster", nil, nil) +func (m *MetaClient) ShowCluster(ctx context.Context) (*Cluster, error) { + res, err := m.Do(ctx, "GET", "/show-cluster", nil, nil) if err != nil { return nil, err } @@ -38,12 +47,12 @@ func (t *MetaClient) ShowCluster(ctx context.Context) (*Cluster, error) { } // Users gets all the users. If name is not nil it filters for a single user -func (t *MetaClient) Users(ctx context.Context, name *string) (*Users, error) { +func (m *MetaClient) Users(ctx context.Context, name *string) (*Users, error) { params := map[string]string{} if name != nil { params["name"] = *name } - res, err := t.Do(ctx, "GET", "/user", params, nil) + res, err := m.Do(ctx, "GET", "/user", params, nil) if err != nil { return nil, err } @@ -59,11 +68,12 @@ func (t *MetaClient) Users(ctx context.Context, name *string) (*Users, error) { } // User returns a single Influx Enterprise user -func (t *MetaClient) User(ctx context.Context, name string) (*User, error) { - users, err := t.Users(ctx, &name) +func (m *MetaClient) User(ctx context.Context, name string) (*User, error) { + users, err := m.Users(ctx, &name) if err != nil { return nil, err } + for _, user := range users.Users { return &user, nil } @@ -71,17 +81,17 @@ func (t *MetaClient) User(ctx context.Context, name string) (*User, error) { } // CreateUser adds a user to Influx Enterprise -func (t *MetaClient) CreateUser(ctx context.Context, name, passwd string) error { - return t.CreateUpdateUser(ctx, "create", name, passwd) +func (m *MetaClient) CreateUser(ctx context.Context, name, passwd string) error { + return m.CreateUpdateUser(ctx, "create", name, passwd) } // ChangePassword updates a user's password in Influx Enterprise -func (t *MetaClient) ChangePassword(ctx context.Context, name, passwd string) error { - return t.CreateUpdateUser(ctx, "change-password", name, passwd) +func (m *MetaClient) ChangePassword(ctx context.Context, name, passwd string) error { + return m.CreateUpdateUser(ctx, "change-password", name, passwd) } // CreateUpdateUser is a helper function to POST to the /user Influx Enterprise endpoint -func (t *MetaClient) CreateUpdateUser(ctx context.Context, action, name, passwd string) error { +func (m *MetaClient) CreateUpdateUser(ctx context.Context, action, name, passwd string) error { a := &UserAction{ Action: action, User: &User{ @@ -89,11 +99,11 @@ func (t *MetaClient) CreateUpdateUser(ctx context.Context, action, name, passwd Password: passwd, }, } - return t.Post(ctx, "/user", a, nil) + return m.Post(ctx, "/user", a, nil) } // DeleteUser removes a user from Influx Enterprise -func (t *MetaClient) DeleteUser(ctx context.Context, name string) error { +func (m *MetaClient) DeleteUser(ctx context.Context, name string) error { a := &UserAction{ Action: "delete", User: &User{ @@ -101,12 +111,12 @@ func (t *MetaClient) DeleteUser(ctx context.Context, name string) error { }, } - return t.Post(ctx, "/user", a, nil) + return m.Post(ctx, "/user", a, nil) } // RemoveAllUserPerms revokes all permissions for a user in Influx Enterprise -func (t *MetaClient) RemoveAllUserPerms(ctx context.Context, name string) error { - user, err := t.User(ctx, name) +func (m *MetaClient) RemoveAllUserPerms(ctx context.Context, name string) error { + user, err := m.User(ctx, name) if err != nil { return err } @@ -120,12 +130,12 @@ func (t *MetaClient) RemoveAllUserPerms(ctx context.Context, name string) error Action: "remove-permissions", User: user, } - return t.Post(ctx, "/user", a, nil) + return m.Post(ctx, "/user", a, nil) } // SetUserPerms removes all permissions and then adds the requested perms -func (t *MetaClient) SetUserPerms(ctx context.Context, name string, perms Permissions) error { - err := t.RemoveAllUserPerms(ctx, name) +func (m *MetaClient) SetUserPerms(ctx context.Context, name string, perms Permissions) error { + err := m.RemoveAllUserPerms(ctx, name) if err != nil { return err } @@ -142,16 +152,16 @@ func (t *MetaClient) SetUserPerms(ctx context.Context, name string, perms Permis Permissions: perms, }, } - return t.Post(ctx, "/user", a, nil) + return m.Post(ctx, "/user", a, nil) } // Roles gets all the roles. If name is not nil it filters for a single role -func (t *MetaClient) Roles(ctx context.Context, name *string) (*Roles, error) { +func (m *MetaClient) Roles(ctx context.Context, name *string) (*Roles, error) { params := map[string]string{} if name != nil { params["name"] = *name } - res, err := t.Do(ctx, "GET", "/role", params, nil) + res, err := m.Do(ctx, "GET", "/role", params, nil) if err != nil { return nil, err } @@ -167,8 +177,8 @@ func (t *MetaClient) Roles(ctx context.Context, name *string) (*Roles, error) { } // Role returns a single named role -func (t *MetaClient) Role(ctx context.Context, name string) (*Role, error) { - roles, err := t.Roles(ctx, &name) +func (m *MetaClient) Role(ctx context.Context, name string) (*Role, error) { + roles, err := m.Roles(ctx, &name) if err != nil { return nil, err } @@ -179,30 +189,30 @@ func (t *MetaClient) Role(ctx context.Context, name string) (*Role, error) { } // CreateRole adds a role to Influx Enterprise -func (t *MetaClient) CreateRole(ctx context.Context, name string) error { +func (m *MetaClient) CreateRole(ctx context.Context, name string) error { a := &RoleAction{ Action: "create", Role: &Role{ Name: name, }, } - return t.Post(ctx, "/role", a, nil) + return m.Post(ctx, "/role", a, nil) } // DeleteRole removes a role from Influx Enterprise -func (t *MetaClient) DeleteRole(ctx context.Context, name string) error { +func (m *MetaClient) DeleteRole(ctx context.Context, name string) error { a := &RoleAction{ Action: "delete", Role: &Role{ Name: name, }, } - return t.Post(ctx, "/role", a, nil) + return m.Post(ctx, "/role", a, nil) } // RemoveAllRolePerms removes all permissions from a role -func (t *MetaClient) RemoveAllRolePerms(ctx context.Context, name string) error { - role, err := t.Role(ctx, name) +func (m *MetaClient) RemoveAllRolePerms(ctx context.Context, name string) error { + role, err := m.Role(ctx, name) if err != nil { return err } @@ -216,12 +226,12 @@ func (t *MetaClient) RemoveAllRolePerms(ctx context.Context, name string) error Action: "remove-permissions", Role: role, } - return t.Post(ctx, "/role", a, nil) + return m.Post(ctx, "/role", a, nil) } // SetRolePerms removes all permissions and then adds the requested perms to role -func (t *MetaClient) SetRolePerms(ctx context.Context, name string, perms Permissions) error { - err := t.RemoveAllRolePerms(ctx, name) +func (m *MetaClient) SetRolePerms(ctx context.Context, name string, perms Permissions) error { + err := m.RemoveAllRolePerms(ctx, name) if err != nil { return err } @@ -238,12 +248,12 @@ func (t *MetaClient) SetRolePerms(ctx context.Context, name string, perms Permis Permissions: perms, }, } - return t.Post(ctx, "/role", a, nil) + return m.Post(ctx, "/role", a, nil) } // RemoveAllRoleUsers removes all users from a role -func (t *MetaClient) RemoveAllRoleUsers(ctx context.Context, name string) error { - role, err := t.Role(ctx, name) +func (m *MetaClient) RemoveAllRoleUsers(ctx context.Context, name string) error { + role, err := m.Role(ctx, name) if err != nil { return err } @@ -257,12 +267,12 @@ func (t *MetaClient) RemoveAllRoleUsers(ctx context.Context, name string) error Action: "remove-users", Role: role, } - return t.Post(ctx, "/role", a, nil) + return m.Post(ctx, "/role", a, nil) } // SetRoleUsers removes all users and then adds the requested users to role -func (t *MetaClient) SetRoleUsers(ctx context.Context, name string, users []string) error { - err := t.RemoveAllRoleUsers(ctx, name) +func (m *MetaClient) SetRoleUsers(ctx context.Context, name string, users []string) error { + err := m.RemoveAllRoleUsers(ctx, name) if err != nil { return err } @@ -279,37 +289,34 @@ func (t *MetaClient) SetRoleUsers(ctx context.Context, name string, users []stri Users: users, }, } - return t.Post(ctx, "/role", a, nil) + return m.Post(ctx, "/role", a, nil) } // Post is a helper function to POST to Influx Enterprise -func (t *MetaClient) Post(ctx context.Context, path string, action interface{}, params map[string]string) error { +func (m *MetaClient) Post(ctx context.Context, path string, action interface{}, params map[string]string) error { b, err := json.Marshal(action) if err != nil { return err } body := bytes.NewReader(b) - _, err = t.Do(ctx, "POST", path, params, body) + _, err = m.Do(ctx, "POST", path, params, body) if err != nil { return err } return nil } -// do is a helper function to interface with Influx Enterprise's Meta API -func (t *MetaClient) do(method, path string, params map[string]string, body io.Reader) (*http.Response, error) { +type defaultClient struct{} + +// Do is a helper function to interface with Influx Enterprise's Meta API +func (d *defaultClient) Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error) { p := url.Values{} - p.Add("u", t.Username) - p.Add("p", t.Password) for k, v := range params { p.Add(k, v) } - URL := url.URL{ - Scheme: "http", - Host: t.MetaHostPort, - Path: path, - RawQuery: p.Encode(), - } + + URL.Path = path + URL.RawQuery = p.Encode() req, err := http.NewRequest(method, URL.String(), body) if err != nil { @@ -340,14 +347,14 @@ func (t *MetaClient) do(method, path string, params map[string]string, body io.R } // Do is a cancelable function to interface with Influx Enterprise's Meta API -func (t *MetaClient) Do(ctx context.Context, method, path string, params map[string]string, body io.Reader) (*http.Response, error) { +func (m *MetaClient) Do(ctx context.Context, method, path string, params map[string]string, body io.Reader) (*http.Response, error) { type result struct { Response *http.Response Err error } resps := make(chan (result)) go func() { - resp, err := t.do(method, path, params, body) + resp, err := m.client.Do(m.URL, path, method, params, body) resps <- result{resp, err} }() diff --git a/enterprise/meta_test.go b/enterprise/meta_test.go new file mode 100644 index 000000000..cc89e0c77 --- /dev/null +++ b/enterprise/meta_test.go @@ -0,0 +1,1189 @@ +package enterprise + +import ( + "bytes" + "context" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "reflect" + "testing" +) + +func TestMetaClient_ShowCluster(t *testing.T) { + type fields struct { + URL *url.URL + client interface { + Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error) + } + } + tests := []struct { + name string + fields fields + want *Cluster + wantErr bool + }{ + { + name: "Successful Show Cluster", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + []byte(`{"data":[{"id":2,"version":"1.1.0-c1.1.0","tcpAddr":"data-1.twinpinesmall.net:8088","httpAddr":"data-1.twinpinesmall.net:8086","httpScheme":"https","status":"joined"}],"meta":[{"id":1,"addr":"meta-0.twinpinesmall.net:8091","httpScheme":"http","tcpAddr":"meta-0.twinpinesmall.net:8089","version":"1.1.0-c1.1.0"}]}`), + nil, + nil, + ), + }, + want: &Cluster{ + DataNodes: []DataNode{ + { + ID: 2, + TCPAddr: "data-1.twinpinesmall.net:8088", + HTTPAddr: "data-1.twinpinesmall.net:8086", + HTTPScheme: "https", + Status: "joined", + }, + }, + MetaNodes: []Node{ + { + ID: 1, + Addr: "meta-0.twinpinesmall.net:8091", + HTTPScheme: "http", + TCPAddr: "meta-0.twinpinesmall.net:8089", + }, + }, + }, + }, + { + name: "Failed Show Cluster", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusBadGateway, + nil, + nil, + fmt.Errorf("Time circuits on. Flux Capacitor... fluxxing."), + ), + }, + wantErr: true, + }, + { + name: "Bad JSON from Show Cluster", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + []byte(`{data}`), + nil, + nil, + ), + }, + wantErr: true, + }, + } + for _, tt := range tests { + m := &MetaClient{ + URL: tt.fields.URL, + client: tt.fields.client, + } + got, err := m.ShowCluster(context.Background()) + if (err != nil) != tt.wantErr { + t.Errorf("%q. MetaClient.ShowCluster() error = %v, wantErr %v", tt.name, err, tt.wantErr) + continue + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%q. MetaClient.ShowCluster() = %v, want %v", tt.name, got, tt.want) + } + if tt.wantErr { + continue + } + reqs := tt.fields.client.(*MockClient).Requests + if len(reqs) != 1 { + t.Errorf("%q. MetaClient.ShowCluster() expected 1 but got %d", tt.name, len(reqs)) + continue + } + req := reqs[0] + if req.Method != "GET" { + t.Errorf("%q. MetaClient.ShowCluster() expected GET method", tt.name) + } + if req.URL.Path != "/show-cluster" { + t.Errorf("%q. MetaClient.ShowCluster() expected /show-cluster path but got %s", tt.name, req.URL.Path) + } + } +} + +func TestMetaClient_Users(t *testing.T) { + type fields struct { + URL *url.URL + client interface { + Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error) + } + } + type args struct { + ctx context.Context + name *string + } + tests := []struct { + name string + fields fields + args args + want *Users + wantErr bool + }{ + { + name: "Successful Show users", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + []byte(`{"users":[{"name":"admin","hash":"1234","permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: nil, + }, + want: &Users{ + Users: []User{ + { + Name: "admin", + Permissions: map[string][]string{ + "": []string{ + "ViewAdmin", "ViewChronograf", + }, + }, + }, + }, + }, + }, + { + name: "Successful Show users single user", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + []byte(`{"users":[{"name":"admin","hash":"1234","permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: &[]string{"admin"}[0], + }, + want: &Users{ + Users: []User{ + { + Name: "admin", + Permissions: map[string][]string{ + "": []string{ + "ViewAdmin", "ViewChronograf", + }, + }, + }, + }, + }, + }, + { + name: "Failure Show users", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + []byte(`{"users":[{"name":"admin","hash":"1234","permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), + nil, + fmt.Errorf("Time circuits on. Flux Capacitor... fluxxing."), + ), + }, + args: args{ + ctx: context.Background(), + name: nil, + }, + wantErr: true, + }, + { + name: "Bad JSON from Show users", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + []byte(`{foo}`), + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: nil, + }, + wantErr: true, + }, + } + for _, tt := range tests { + m := &MetaClient{ + URL: tt.fields.URL, + client: tt.fields.client, + } + got, err := m.Users(tt.args.ctx, tt.args.name) + if (err != nil) != tt.wantErr { + t.Errorf("%q. MetaClient.Users() error = %v, wantErr %v", tt.name, err, tt.wantErr) + continue + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%q. MetaClient.Users() = %v, want %v", tt.name, got, tt.want) + } + } +} + +func TestMetaClient_User(t *testing.T) { + type fields struct { + URL *url.URL + client interface { + Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error) + } + } + type args struct { + ctx context.Context + name string + } + tests := []struct { + name string + fields fields + args args + want *User + wantErr bool + }{ + { + name: "Successful Show users", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + []byte(`{"users":[{"name":"admin","hash":"1234","permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: "admin", + }, + want: &User{ + Name: "admin", + Permissions: map[string][]string{ + "": []string{ + "ViewAdmin", "ViewChronograf", + }, + }, + }, + }, + { + name: "No such user", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusNotFound, + []byte(`{"error":"user not found"}`), + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: "unknown", + }, + wantErr: true, + }, + { + name: "Bad JSON", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusNotFound, + []byte(`{BAD}`), + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + }, + wantErr: true, + }, + } + for _, tt := range tests { + m := &MetaClient{ + URL: tt.fields.URL, + client: tt.fields.client, + } + got, err := m.User(tt.args.ctx, tt.args.name) + if (err != nil) != tt.wantErr { + t.Errorf("%q. MetaClient.User() error = %v, wantErr %v", tt.name, err, tt.wantErr) + continue + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%q. MetaClient.User() = %v, want %v", tt.name, got, tt.want) + } + } +} + +func TestMetaClient_CreateUser(t *testing.T) { + type fields struct { + URL *url.URL + client interface { + Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error) + } + } + type args struct { + ctx context.Context + name string + passwd string + } + tests := []struct { + name string + fields fields + args args + want string + wantErr bool + }{ + { + name: "Successful Create User", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + nil, + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: "admin", + passwd: "hunter2", + }, + want: `{"action":"create","user":{"name":"admin","password":"hunter2"}}`, + }, + } + for _, tt := range tests { + m := &MetaClient{ + URL: tt.fields.URL, + client: tt.fields.client, + } + if err := m.CreateUser(tt.args.ctx, tt.args.name, tt.args.passwd); (err != nil) != tt.wantErr { + t.Errorf("%q. MetaClient.CreateUser() error = %v, wantErr %v", tt.name, err, tt.wantErr) + } + if tt.wantErr { + continue + } + reqs := tt.fields.client.(*MockClient).Requests + if len(reqs) != 1 { + t.Errorf("%q. MetaClient.CreateUser() expected 1 but got %d", tt.name, len(reqs)) + continue + } + req := reqs[0] + if req.Method != "POST" { + t.Errorf("%q. MetaClient.CreateUser() expected POST method", tt.name) + } + if req.URL.Path != "/user" { + t.Errorf("%q. MetaClient.CreateUser() expected /user path but got %s", tt.name, req.URL.Path) + } + got, _ := ioutil.ReadAll(req.Body) + if string(got) != tt.want { + t.Errorf("%q. MetaClient.CreateUser() = %v, want %v", tt.name, string(got), tt.want) + } + } +} + +func TestMetaClient_ChangePassword(t *testing.T) { + type fields struct { + URL *url.URL + client interface { + Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error) + } + } + type args struct { + ctx context.Context + name string + passwd string + } + tests := []struct { + name string + fields fields + args args + want string + wantErr bool + }{ + { + name: "Successful Change Password", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + nil, + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: "admin", + passwd: "hunter2", + }, + want: `{"action":"change-password","user":{"name":"admin","password":"hunter2"}}`, + }, + } + for _, tt := range tests { + m := &MetaClient{ + URL: tt.fields.URL, + client: tt.fields.client, + } + if err := m.ChangePassword(tt.args.ctx, tt.args.name, tt.args.passwd); (err != nil) != tt.wantErr { + t.Errorf("%q. MetaClient.ChangePassword() error = %v, wantErr %v", tt.name, err, tt.wantErr) + } + + if tt.wantErr { + continue + } + reqs := tt.fields.client.(*MockClient).Requests + if len(reqs) != 1 { + t.Errorf("%q. MetaClient.ChangePassword() expected 1 but got %d", tt.name, len(reqs)) + continue + } + req := reqs[0] + if req.Method != "POST" { + t.Errorf("%q. MetaClient.ChangePassword() expected POST method", tt.name) + } + if req.URL.Path != "/user" { + t.Errorf("%q. MetaClient.ChangePassword() expected /user path but got %s", tt.name, req.URL.Path) + } + got, _ := ioutil.ReadAll(req.Body) + if string(got) != tt.want { + t.Errorf("%q. MetaClient.ChangePassword() = %v, want %v", tt.name, string(got), tt.want) + } + } +} + +func TestMetaClient_DeleteUser(t *testing.T) { + type fields struct { + URL *url.URL + client interface { + Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error) + } + } + type args struct { + ctx context.Context + name string + } + tests := []struct { + name string + fields fields + args args + want string + wantErr bool + }{ + { + name: "Successful delete User", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + nil, + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: "admin", + }, + want: `{"action":"delete","user":{"name":"admin"}}`, + }, + } + for _, tt := range tests { + m := &MetaClient{ + URL: tt.fields.URL, + client: tt.fields.client, + } + if err := m.DeleteUser(tt.args.ctx, tt.args.name); (err != nil) != tt.wantErr { + t.Errorf("%q. MetaClient.DeleteUser() error = %v, wantErr %v", tt.name, err, tt.wantErr) + } + if tt.wantErr { + continue + } + reqs := tt.fields.client.(*MockClient).Requests + if len(reqs) != 1 { + t.Errorf("%q. MetaClient.DeleteUser() expected 1 but got %d", tt.name, len(reqs)) + continue + } + req := reqs[0] + if req.Method != "POST" { + t.Errorf("%q. MetaClient.DeleteUser() expected POST method", tt.name) + } + if req.URL.Path != "/user" { + t.Errorf("%q. MetaClient.DeleteUser() expected /user path but got %s", tt.name, req.URL.Path) + } + got, _ := ioutil.ReadAll(req.Body) + if string(got) != tt.want { + t.Errorf("%q. MetaClient.DeleteUser() = %v, want %v", tt.name, string(got), tt.want) + } + } +} + +func TestMetaClient_SetUserPerms(t *testing.T) { + type fields struct { + URL *url.URL + client interface { + Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error) + } + } + type args struct { + ctx context.Context + name string + perms Permissions + } + tests := []struct { + name string + fields fields + args args + wantRm string + wantAdd string + wantErr bool + }{ + { + name: "Successful set permissions User", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + []byte(`{"users":[{"name":"admin","hash":"1234","permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: "admin", + }, + wantRm: `{"action":"remove-permissions","user":{"name":"admin","permissions":{"":["ViewAdmin","ViewChronograf"]}}}`, + }, + { + name: "Successful set permissions User", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + []byte(`{"users":[{"name":"admin","hash":"1234","permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: "admin", + perms: Permissions{ + "telegraf": []string{ + "ReadData", + }, + }, + }, + wantRm: `{"action":"remove-permissions","user":{"name":"admin","permissions":{"":["ViewAdmin","ViewChronograf"]}}}`, + wantAdd: `{"action":"add-permissions","user":{"name":"admin","permissions":{"telegraf":["ReadData"]}}}`, + }, + } + for _, tt := range tests { + m := &MetaClient{ + URL: tt.fields.URL, + client: tt.fields.client, + } + if err := m.SetUserPerms(tt.args.ctx, tt.args.name, tt.args.perms); (err != nil) != tt.wantErr { + t.Errorf("%q. MetaClient.SetUserPerms() error = %v, wantErr %v", tt.name, err, tt.wantErr) + } + if tt.wantErr { + continue + } + reqs := tt.fields.client.(*MockClient).Requests + if len(reqs) < 2 { + t.Errorf("%q. MetaClient.SetUserPerms() expected 2 but got %d", tt.name, len(reqs)) + continue + } + + usr := reqs[0] + if usr.Method != "GET" { + t.Errorf("%q. MetaClient.SetUserPerms() expected GET method", tt.name) + } + if usr.URL.Path != "/user" { + t.Errorf("%q. MetaClient.SetUserPerms() expected /user path but got %s", tt.name, usr.URL.Path) + } + + prm := reqs[1] + if prm.Method != "POST" { + t.Errorf("%q. MetaClient.SetUserPerms() expected GET method", tt.name) + } + if prm.URL.Path != "/user" { + t.Errorf("%q. MetaClient.SetUserPerms() expected /user path but got %s", tt.name, prm.URL.Path) + } + + got, _ := ioutil.ReadAll(prm.Body) + if string(got) != tt.wantRm { + t.Errorf("%q. MetaClient.SetUserPerms() = %v, want %v", tt.name, string(got), tt.wantRm) + } + if tt.wantAdd != "" { + prm := reqs[2] + if prm.Method != "POST" { + t.Errorf("%q. MetaClient.SetUserPerms() expected GET method", tt.name) + } + if prm.URL.Path != "/user" { + t.Errorf("%q. MetaClient.SetUserPerms() expected /user path but got %s", tt.name, prm.URL.Path) + } + + got, _ := ioutil.ReadAll(prm.Body) + if string(got) != tt.wantAdd { + t.Errorf("%q. MetaClient.SetUserPerms() = %v, want %v", tt.name, string(got), tt.wantAdd) + } + } + } +} + +func TestMetaClient_Roles(t *testing.T) { + type fields struct { + URL *url.URL + client interface { + Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error) + } + } + type args struct { + ctx context.Context + name *string + } + tests := []struct { + name string + fields fields + args args + want *Roles + wantErr bool + }{ + { + name: "Successful Show role", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + []byte(`{"roles":[{"name":"admin","users":["marty"],"permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: nil, + }, + want: &Roles{ + Roles: []Role{ + { + Name: "admin", + Permissions: map[string][]string{ + "": []string{ + "ViewAdmin", "ViewChronograf", + }, + }, + Users: []string{"marty"}, + }, + }, + }, + }, + { + name: "Successful Show role single role", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + []byte(`{"roles":[{"name":"admin","users":["marty"],"permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: &[]string{"admin"}[0], + }, + want: &Roles{ + Roles: []Role{ + { + Name: "admin", + Permissions: map[string][]string{ + "": []string{ + "ViewAdmin", "ViewChronograf", + }, + }, + Users: []string{"marty"}, + }, + }, + }, + }, + } + for _, tt := range tests { + m := &MetaClient{ + URL: tt.fields.URL, + client: tt.fields.client, + } + got, err := m.Roles(tt.args.ctx, tt.args.name) + if (err != nil) != tt.wantErr { + t.Errorf("%q. MetaClient.Roles() error = %v, wantErr %v", tt.name, err, tt.wantErr) + continue + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%q. MetaClient.Roles() = %v, want %v", tt.name, got, tt.want) + } + } +} + +func TestMetaClient_Role(t *testing.T) { + type fields struct { + URL *url.URL + client interface { + Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error) + } + } + type args struct { + ctx context.Context + name string + } + tests := []struct { + name string + fields fields + args args + want *Role + wantErr bool + }{ + { + name: "Successful Show role", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + []byte(`{"roles":[{"name":"admin","users":["marty"],"permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: "admin", + }, + want: &Role{ + Name: "admin", + Permissions: map[string][]string{ + "": []string{ + "ViewAdmin", "ViewChronograf", + }, + }, + Users: []string{"marty"}, + }, + }, + { + name: "No such role", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusNotFound, + []byte(`{"error":"user not found"}`), + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: "unknown", + }, + wantErr: true, + }, + } + for _, tt := range tests { + m := &MetaClient{ + URL: tt.fields.URL, + client: tt.fields.client, + } + got, err := m.Role(tt.args.ctx, tt.args.name) + if (err != nil) != tt.wantErr { + t.Errorf("%q. MetaClient.Role() error = %v, wantErr %v", tt.name, err, tt.wantErr) + continue + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%q. MetaClient.Role() = %v, want %v", tt.name, got, tt.want) + } + } +} + +func TestMetaClient_CreateRole(t *testing.T) { + type fields struct { + URL *url.URL + client interface { + Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error) + } + } + type args struct { + ctx context.Context + name string + } + tests := []struct { + name string + fields fields + args args + want string + wantErr bool + }{ + { + name: "Successful Create Role", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + nil, + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: "admin", + }, + want: `{"action":"create","role":{"name":"admin"}}`, + }, + } + for _, tt := range tests { + m := &MetaClient{ + URL: tt.fields.URL, + client: tt.fields.client, + } + if err := m.CreateRole(tt.args.ctx, tt.args.name); (err != nil) != tt.wantErr { + t.Errorf("%q. MetaClient.CreateRole() error = %v, wantErr %v", tt.name, err, tt.wantErr) + } + reqs := tt.fields.client.(*MockClient).Requests + if len(reqs) != 1 { + t.Errorf("%q. MetaClient.CreateRole() expected 1 but got %d", tt.name, len(reqs)) + continue + } + req := reqs[0] + if req.Method != "POST" { + t.Errorf("%q. MetaClient.CreateRole() expected POST method", tt.name) + } + if req.URL.Path != "/role" { + t.Errorf("%q. MetaClient.CreateRole() expected /role path but got %s", tt.name, req.URL.Path) + } + got, _ := ioutil.ReadAll(req.Body) + if string(got) != tt.want { + t.Errorf("%q. MetaClient.CreateRole() = %v, want %v", tt.name, string(got), tt.want) + } + } +} + +func TestMetaClient_DeleteRole(t *testing.T) { + type fields struct { + URL *url.URL + client interface { + Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error) + } + } + type args struct { + ctx context.Context + name string + } + tests := []struct { + name string + fields fields + args args + want string + wantErr bool + }{ + { + name: "Successful delete role", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + nil, + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: "admin", + }, + want: `{"action":"delete","role":{"name":"admin"}}`, + }, + } + for _, tt := range tests { + m := &MetaClient{ + URL: tt.fields.URL, + client: tt.fields.client, + } + if err := m.DeleteRole(tt.args.ctx, tt.args.name); (err != nil) != tt.wantErr { + t.Errorf("%q. MetaClient.DeleteRole() error = %v, wantErr %v", tt.name, err, tt.wantErr) + } + if tt.wantErr { + continue + } + reqs := tt.fields.client.(*MockClient).Requests + if len(reqs) != 1 { + t.Errorf("%q. MetaClient.DeleteRole() expected 1 but got %d", tt.name, len(reqs)) + continue + } + req := reqs[0] + if req.Method != "POST" { + t.Errorf("%q. MetaClient.DeleDeleteRoleteUser() expected POST method", tt.name) + } + if req.URL.Path != "/role" { + t.Errorf("%q. MetaClient.DeleteRole() expected /role path but got %s", tt.name, req.URL.Path) + } + got, _ := ioutil.ReadAll(req.Body) + if string(got) != tt.want { + t.Errorf("%q. MetaClient.DeleteRole() = %v, want %v", tt.name, string(got), tt.want) + } + } +} + +func TestMetaClient_SetRolePerms(t *testing.T) { + type fields struct { + URL *url.URL + client interface { + Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error) + } + } + type args struct { + ctx context.Context + name string + perms Permissions + } + tests := []struct { + name string + fields fields + args args + wantRm string + wantAdd string + wantErr bool + }{ + { + name: "Successful set permissions role", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + []byte(`{"roles":[{"name":"admin","users":["marty"],"permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: "admin", + }, + wantRm: `{"action":"remove-permissions","role":{"name":"admin","permissions":{"":["ViewAdmin","ViewChronograf"]},"users":["marty"]}}`, + }, + { + name: "Successful set single permissions role", + fields: fields{ + URL: &url.URL{ + Host: "twinpinesmall.net:8091", + Scheme: "https", + }, + client: NewMockClient( + http.StatusOK, + []byte(`{"roles":[{"name":"admin","users":["marty"],"permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), + nil, + nil, + ), + }, + args: args{ + ctx: context.Background(), + name: "admin", + perms: Permissions{ + "telegraf": []string{ + "ReadData", + }, + }, + }, + wantRm: `{"action":"remove-permissions","role":{"name":"admin","permissions":{"":["ViewAdmin","ViewChronograf"]},"users":["marty"]}}`, + wantAdd: `{"action":"add-permissions","role":{"name":"admin","permissions":{"telegraf":["ReadData"]}}}`, + }, + } + for _, tt := range tests { + m := &MetaClient{ + URL: tt.fields.URL, + client: tt.fields.client, + } + if err := m.SetRolePerms(tt.args.ctx, tt.args.name, tt.args.perms); (err != nil) != tt.wantErr { + t.Errorf("%q. MetaClient.SetRolePerms() error = %v, wantErr %v", tt.name, err, tt.wantErr) + } + if tt.wantErr { + continue + } + reqs := tt.fields.client.(*MockClient).Requests + if len(reqs) < 2 { + t.Errorf("%q. MetaClient.SetRolePerms() expected 2 but got %d", tt.name, len(reqs)) + continue + } + + usr := reqs[0] + if usr.Method != "GET" { + t.Errorf("%q. MetaClient.SetRolePerms() expected GET method", tt.name) + } + if usr.URL.Path != "/role" { + t.Errorf("%q. MetaClient.SetRolePerms() expected /user path but got %s", tt.name, usr.URL.Path) + } + + prm := reqs[1] + if prm.Method != "POST" { + t.Errorf("%q. MetaClient.SetRolePerms() expected GET method", tt.name) + } + if prm.URL.Path != "/role" { + t.Errorf("%q. MetaClient.SetRolePerms() expected /role path but got %s", tt.name, prm.URL.Path) + } + + got, _ := ioutil.ReadAll(prm.Body) + if string(got) != tt.wantRm { + t.Errorf("%q. MetaClient.SetRolePerms() = %v, want %v", tt.name, string(got), tt.wantRm) + } + if tt.wantAdd != "" { + prm := reqs[2] + if prm.Method != "POST" { + t.Errorf("%q. MetaClient.SetRolePerms() expected GET method", tt.name) + } + if prm.URL.Path != "/role" { + t.Errorf("%q. MetaClient.SetRolePerms() expected /role path but got %s", tt.name, prm.URL.Path) + } + + got, _ := ioutil.ReadAll(prm.Body) + if string(got) != tt.wantAdd { + t.Errorf("%q. MetaClient.SetRolePerms() = %v, want %v", tt.name, string(got), tt.wantAdd) + } + } + } +} + +type MockClient struct { + Code int // HTTP Status code + Body []byte + HeaderMap http.Header + Err error + + Requests []*http.Request +} + +func NewMockClient(code int, body []byte, headers http.Header, err error) *MockClient { + return &MockClient{ + Code: code, + Body: body, + HeaderMap: headers, + Err: err, + Requests: make([]*http.Request, 0), + } +} + +func (c *MockClient) Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error) { + if c == nil { + return nil, fmt.Errorf("NIL MockClient") + } + if URL == nil { + return nil, fmt.Errorf("NIL url") + } + if c.Err != nil { + return nil, c.Err + } + + // Record the request in the mock client + p := url.Values{} + for k, v := range params { + p.Add(k, v) + } + + URL.Path = path + URL.RawQuery = p.Encode() + + req, err := http.NewRequest(method, URL.String(), body) + if err != nil { + return nil, err + } + c.Requests = append(c.Requests, req) + + return &http.Response{ + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + StatusCode: c.Code, + Status: http.StatusText(c.Code), + Header: c.HeaderMap, + Body: ioutil.NopCloser(bytes.NewReader(c.Body)), + }, nil +}