Repository: mainflux/mainflux
Pull request #1589 (2022-04-01 16:20:43)

NOISSUE - Get user by ID and get all users by SDK
Signed-off-by: 0x6f736f646f <blackd0t@protonmail.com>

### What does this do?
It underlines the clear distinction between get user by id and get all users if you are admin.

### Which issue(s) does this PR fix/relate to?
Resolvers #1618

### List any changes that modify/break current functionality
It adds get all users to the mainflux SDK and also get user by id. The current implementation for cli fails to get user by id as show here

```sh
mainflux-cli users get $USERTOKEN

{}

```

The new implementation presents
```sh
./mainflux-cli users get all $USERTOKEN  
[
  {
    "email": "admin@example.com",
    "id": "3803babf-7bf3-458b-a337-6905b575a6c6"
  },
  {
    "email": "pedantic_elion@email.com",
    "id": "be1c2ef2-6712-41d5-b64d-d22c0a5d3803"
  },
  {
    "email": "reverent_murdock@email.com",
    "id": "124422b7-883e-460c-bebc-12ea04d4934b"
  }
]

./mainflux-cli users get 124422b7-883e-460c-bebc-12ea04d4934b $USERTOKEN

{
  "email": "reverent_murdock@email.com",
  "id": "124422b7-883e-460c-bebc-12ea04d4934b"
}

```

### Have you included tests for your changes?
Yes

### Did you document any new/modified functionality?
No

### Notes
N/A


0x6f736f646f: Where should I provide the pagination option
- If it is on the cli, I have provided
- If it is on the SDK, I haven't as the user's endpoint doesn't provide a means of querying it

codecov-commenter: # [Codecov](https://codecov.io/gh/mainflux/mainflux/pull/1589?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mainflux) Report
> Merging [#1589](https://codecov.io/gh/mainflux/mainflux/pull/1589?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mainflux) (0aa0970) into [master](https://codecov.io/gh/mainflux/mainflux/commit/c6f7c69798adfc044df16b48d5c71024c780894f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mainflux) (c6f7c69) will **increase** coverage by `0.25%`.
> The diff coverage is `60.00%`.

```diff
@@            Coverage Diff             @@
##           master    #1589      +/-   ##
==========================================
+ Coverage   68.87%   69.13%   +0.25%     
==========================================
  Files         136      136              
  Lines       11024    11042      +18     
==========================================
+ Hits         7593     7634      +41     
+ Misses       2785     2748      -37     
- Partials      646      660      +14     
```


| [Impacted Files](https://codecov.io/gh/mainflux/mainflux/pull/1589?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mainflux) | Coverage Δ | |
|---|---|---|
| [pkg/sdk/go/sdk.go](https://codecov.io/gh/mainflux/mainflux/pull/1589/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mainflux#diff-cGtnL3Nkay9nby9zZGsuZ28=) | `100.00% <ø> (ø)` | |
| [pkg/sdk/go/users.go](https://codecov.io/gh/mainflux/mainflux/pull/1589/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mainflux#diff-cGtnL3Nkay9nby91c2Vycy5nbw==) | `58.00% <60.00%> (+34.82%)` | :arrow_up: |
| [things/service.go](https://codecov.io/gh/mainflux/mainflux/pull/1589/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mainflux#diff-dGhpbmdzL3NlcnZpY2UuZ28=) | `66.07% <0.00%> (+0.71%)` | :arrow_up: |

:mega: Codecov can now indicate which changes are the most critical in Pull Requests. [Learn more](https://about.codecov.io/product/feature/runtime-insights/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mainflux)

0x6f736f646f: https://github.com/mainflux/mainflux/pull/1594#issuecomment-1112040258

Users pagemetadata has name field instead of email field
0x6f736f646f: - Used `pageMetadata` as referenced [here](https://github.com/mainflux/mainflux/pull/1594#issuecomment-1112040258)
- Change Users `Name` to `Email` field
manuio: @0x6f736f646f can you check why tests are failling please? 
drasko: @dusanb94 please do a review of this one, so we can move forward.

Patch:
diff --git a/cli/channels.go b/cli/channels.go
index f37e67ba..3a1d4e3c 100644
--- a/cli/channels.go
+++ b/cli/channels.go
@@ -39,15 +39,29 @@ var cmdChannels = []cobra.Command{
 	{
 		Use:   "get [all | <channel_id>] <user_auth_token>",
 		Short: "Get channel",
-		Long:  `Gets list of all channels or gets channel by id`,
+		Long: `Get all channels or get channel by id. Channels can be filtered by name or metadata.
+		all - lists all channels
+		<channel_id> - shows thing with provided <channel_id>`,
+
 		Run: func(cmd *cobra.Command, args []string) {
 			if len(args) != 2 {
 				logUsage(cmd.Use)
 				return
 			}
+			metadata, err := convertMetadata(Metadata)
+			if err != nil {
+				logError(err)
+				return
+			}
+			pageMetadata := mfxsdk.PageMetadata{
+				Name:     "",
+				Offset:   uint64(Offset),
+				Limit:    uint64(Limit),
+				Metadata: metadata,
+			}
 
 			if args[0] == "all" {
-				l, err := sdk.Channels(args[1], uint64(Offset), uint64(Limit), Name)
+				l, err := sdk.Channels(args[1], pageMetadata)
 				if err != nil {
 					logError(err)
 					return
@@ -56,7 +70,6 @@ var cmdChannels = []cobra.Command{
 				logJSON(l)
 				return
 			}
-
 			c, err := sdk.Channel(args[0], args[1])
 			if err != nil {
 				logError(err)
diff --git a/cli/things.go b/cli/things.go
index c4a6a0b0..86dde21a 100644
--- a/cli/things.go
+++ b/cli/things.go
@@ -39,15 +39,27 @@ var cmdThings = []cobra.Command{
 	{
 		Use:   "get [all | <thing_id>] <user_auth_token>",
 		Short: "Get things",
-		Long:  `Get a list of things or thing by id`,
+		Long: `Get all things or get thing by id. Things can be filtered by name or metadata
+		all - lists all things
+		<thing_id> - shows thing with provided <thing_id>`,
 		Run: func(cmd *cobra.Command, args []string) {
 			if len(args) != 2 {
 				logUsage(cmd.Use)
 				return
 			}
-
+			metadata, err := convertMetadata(Metadata)
+			if err != nil {
+				logError(err)
+				return
+			}
+			pageMetadata := mfxsdk.PageMetadata{
+				Name:     "",
+				Offset:   uint64(Offset),
+				Limit:    uint64(Limit),
+				Metadata: metadata,
+			}
 			if args[0] == "all" {
-				l, err := sdk.Things(args[1], uint64(Offset), uint64(Limit), Name)
+				l, err := sdk.Things(args[1], pageMetadata)
 				if err != nil {
 					logError(err)
 					return
@@ -55,7 +67,6 @@ var cmdThings = []cobra.Command{
 				logJSON(l)
 				return
 			}
-
 			t, err := sdk.Thing(args[0], args[1])
 			if err != nil {
 				logError(err)
diff --git a/cli/users.go b/cli/users.go
index 9ce80057..928aea17 100644
--- a/cli/users.go
+++ b/cli/users.go
@@ -38,16 +38,37 @@ var cmdUsers = []cobra.Command{
 		},
 	},
 	{
-		Use:   "get <user_auth_token>",
-		Short: "Get user",
-		Long:  `Returns user object`,
+		Use:   "get [all | <user_id> ] <user_auth_token>",
+		Short: "Get users",
+		Long: `Get all users or get user by id. Users can be filtered by name or metadata
+		all - lists all users
+		<user_id> - shows user with provided <user_id>`,
 		Run: func(cmd *cobra.Command, args []string) {
-			if len(args) != 1 {
+			if len(args) != 2 {
 				logUsage(cmd.Use)
 				return
 			}
-
-			u, err := sdk.User(args[0])
+			metadata, err := convertMetadata(Metadata)
+			if err != nil {
+				logError(err)
+				return
+			}
+			pageMetadata := mfxsdk.PageMetadata{
+				Email:    "",
+				Offset:   uint64(Offset),
+				Limit:    uint64(Limit),
+				Metadata: metadata,
+			}
+			if args[0] == "all" {
+				l, err := sdk.Users(args[1], pageMetadata)
+				if err != nil {
+					logError(err)
+					return
+				}
+				logJSON(l)
+				return
+			}
+			u, err := sdk.User(args[0], args[1])
 			if err != nil {
 				logError(err)
 				return
diff --git a/cli/utils.go b/cli/utils.go
index 5fe28583..167ddea8 100644
--- a/cli/utils.go
+++ b/cli/utils.go
@@ -18,6 +18,10 @@ var (
 	Offset uint = 0
 	// Name query parameter
 	Name string = ""
+	// Email query parameter
+	Email string = ""
+	// Metadata query parameter
+	Metadata string = ""
 	// ConfigPath config path parameter
 	ConfigPath string = ""
 	// RawOutput raw output mode
@@ -64,3 +68,14 @@ func logCreated(e string) {
 		fmt.Printf(color.BlueString("\ncreated: %s\n\n"), e)
 	}
 }
+
+func convertMetadata(m string) (map[string]interface{}, error) {
+	var metadata map[string]interface{}
+	if m == "" {
+		return nil, nil
+	}
+	if err := json.Unmarshal([]byte(Metadata), &metadata); err != nil {
+		return nil, err
+	}
+	return nil, nil
+}
diff --git a/cmd/cli/main.go b/cmd/cli/main.go
index bd22c236..59de4f48 100644
--- a/cmd/cli/main.go
+++ b/cmd/cli/main.go
@@ -84,7 +84,7 @@ func main() {
 	rootCmd.PersistentFlags().StringVarP(
 		&sdkConf.CertsURL,
 		"certs-url",
-		"e",
+		"s",
 		sdkConf.CertsURL,
 		"Certs service URL",
 	)
@@ -170,6 +170,22 @@ func main() {
 		"Name query parameter",
 	)
 
+	rootCmd.PersistentFlags().StringVarP(
+		&cli.Email,
+		"email",
+		"e",
+		"",
+		"Email query parameter",
+	)
+
+	rootCmd.PersistentFlags().StringVarP(
+		&cli.Metadata,
+		"metadata",
+		"m",
+		"",
+		"Metadata query parameter",
+	)
+
 	if err := rootCmd.Execute(); err != nil {
 		log.Fatal(err)
 	}
diff --git a/pkg/sdk/go/channels.go b/pkg/sdk/go/channels.go
index d027e4f3..dc58d6bf 100644
--- a/pkg/sdk/go/channels.go
+++ b/pkg/sdk/go/channels.go
@@ -76,8 +76,11 @@ func (sdk mfSDK) CreateChannels(chs []Channel, token string) ([]Channel, error)
 	return ccr.Channels, nil
 }
 
-func (sdk mfSDK) Channels(token string, offset, limit uint64, name string) (ChannelsPage, error) {
-	url := fmt.Sprintf("%s/%s?offset=%d&limit=%d&name=%s", sdk.thingsURL, channelsEndpoint, offset, limit, name)
+func (sdk mfSDK) Channels(token string, pm PageMetadata) (ChannelsPage, error) {
+	url, err := sdk.withQueryParams(sdk.thingsURL, channelsEndpoint, pm)
+	if err != nil {
+		return ChannelsPage{}, err
+	}
 	req, err := http.NewRequest(http.MethodGet, url, nil)
 	if err != nil {
 		return ChannelsPage{}, err
diff --git a/pkg/sdk/go/sdk.go b/pkg/sdk/go/sdk.go
index f1098d23..20af46eb 100644
--- a/pkg/sdk/go/sdk.go
+++ b/pkg/sdk/go/sdk.go
@@ -5,8 +5,12 @@ package sdk
 
 import (
 	"crypto/tls"
+	"encoding/json"
 	"errors"
+	"fmt"
 	"net/http"
+	"net/url"
+	"strconv"
 	"time"
 
 	"github.com/mainflux/mainflux"
@@ -85,6 +89,16 @@ type User struct {
 	Password string                 `json:"password,omitempty"`
 	Metadata map[string]interface{} `json:"metadata,omitempty"`
 }
+type PageMetadata struct {
+	Total    uint64                 `json:"total"`
+	Offset   uint64                 `json:"offset"`
+	Limit    uint64                 `json:"limit"`
+	Level    uint64                 `json:"level,omitempty"`
+	Email    string                 `json:"email,omitempty"`
+	Name     string                 `json:"name,omitempty"`
+	Type     string                 `json:"type,omitempty"`
+	Metadata map[string]interface{} `json:"metadata,omitempty"`
+}
 
 // Group represents mainflux users group.
 type Group struct {
@@ -95,16 +109,6 @@ type Group struct {
 	Metadata    map[string]interface{} `json:"metadata,omitempty"`
 }
 
-type PageMetadata struct {
-	Total    uint64
-	Offset   uint64
-	Limit    uint64
-	Level    uint64
-	Name     string
-	Type     string
-	Metadata map[string]interface{}
-}
-
 // Thing represents mainflux thing.
 type Thing struct {
 	ID       string                 `json:"id,omitempty"`
@@ -134,8 +138,11 @@ type SDK interface {
 	// CreateUser registers mainflux user.
 	CreateUser(token string, user User) (string, error)
 
-	// User returns user object.
-	User(token string) (User, error)
+	// User returns user object by id.
+	User(token, id string) (User, error)
+
+	// Users returns list of users.
+	Users(token string, pm PageMetadata) (UsersPage, error)
 
 	// CreateToken receives credentials and returns user token.
 	CreateToken(user User) (string, error)
@@ -153,7 +160,7 @@ type SDK interface {
 	CreateThings(things []Thing, token string) ([]Thing, error)
 
 	// Things returns page of things.
-	Things(token string, offset, limit uint64, name string) (ThingsPage, error)
+	Things(token string, pm PageMetadata) (ThingsPage, error)
 
 	// ThingsByChannel returns page of things that are connected or not connected
 	// to specified channel.
@@ -214,7 +221,7 @@ type SDK interface {
 	CreateChannels(channels []Channel, token string) ([]Channel, error)
 
 	// Channels returns page of channels.
-	Channels(token string, offset, limit uint64, name string) (ChannelsPage, error)
+	Channels(token string, pm PageMetadata) (ChannelsPage, error)
 
 	// ChannelsByThing returns page of channels that are connected or not connected
 	// to specified thing.
@@ -353,3 +360,38 @@ func (sdk mfSDK) sendThingRequest(req *http.Request, key, contentType string) (*
 
 	return sdk.client.Do(req)
 }
+
+func (sdk mfSDK) withQueryParams(baseURL, endpoint string, pm PageMetadata) (string, error) {
+	q, err := pm.query()
+	if err != nil {
+		return "", err
+	}
+	return fmt.Sprintf("%s/%s?%s", baseURL, endpoint, q), nil
+}
+
+func (pm PageMetadata) query() (string, error) {
+	q := url.Values{}
+	q.Add("total", strconv.FormatUint(pm.Total, 10))
+	q.Add("offset", strconv.FormatUint(pm.Offset, 10))
+	q.Add("limit", strconv.FormatUint(pm.Limit, 10))
+	if pm.Level != 0 {
+		q.Add("level", strconv.FormatUint(pm.Level, 10))
+	}
+	if pm.Email != "" {
+		q.Add("email", pm.Email)
+	}
+	if pm.Name != "" {
+		q.Add("name", pm.Name)
+	}
+	if pm.Type != "" {
+		q.Add("type", pm.Type)
+	}
+	if pm.Metadata != nil {
+		md, err := json.Marshal(pm.Metadata)
+		if err != nil {
+			return "", err
+		}
+		q.Add("metadata", string(md))
+	}
+	return q.Encode(), nil
+}
diff --git a/pkg/sdk/go/things.go b/pkg/sdk/go/things.go
index b7f1d16e..5421bb40 100644
--- a/pkg/sdk/go/things.go
+++ b/pkg/sdk/go/things.go
@@ -80,9 +80,11 @@ func (sdk mfSDK) CreateThings(things []Thing, token string) ([]Thing, error) {
 	return ctr.Things, nil
 }
 
-func (sdk mfSDK) Things(token string, offset, limit uint64, name string) (ThingsPage, error) {
-	endpoint := fmt.Sprintf("%s?offset=%d&limit=%d&name=%s", thingsEndpoint, offset, limit, name)
-	url := fmt.Sprintf("%s/%s", sdk.thingsURL, endpoint)
+func (sdk mfSDK) Things(token string, pm PageMetadata) (ThingsPage, error) {
+	url, err := sdk.withQueryParams(sdk.thingsURL, thingsEndpoint, pm)
+	if err != nil {
+		return ThingsPage{}, err
+	}
 
 	req, err := http.NewRequest(http.MethodGet, url, nil)
 	if err != nil {
diff --git a/pkg/sdk/go/users.go b/pkg/sdk/go/users.go
index 54dffc31..4063d877 100644
--- a/pkg/sdk/go/users.go
+++ b/pkg/sdk/go/users.go
@@ -46,8 +46,8 @@ func (sdk mfSDK) CreateUser(token string, u User) (string, error) {
 	return id, nil
 }
 
-func (sdk mfSDK) User(token string) (User, error) {
-	url := fmt.Sprintf("%s/%s", sdk.usersURL, usersEndpoint)
+func (sdk mfSDK) User(userID, token string) (User, error) {
+	url := fmt.Sprintf("%s/%s/%s", sdk.usersURL, usersEndpoint, userID)
 	req, err := http.NewRequest(http.MethodGet, url, nil)
 	if err != nil {
 		return User{}, err
@@ -76,6 +76,38 @@ func (sdk mfSDK) User(token string) (User, error) {
 	return u, nil
 }
 
+func (sdk mfSDK) Users(token string, pm PageMetadata) (UsersPage, error) {
+	url, err := sdk.withQueryParams(sdk.usersURL, usersEndpoint, pm)
+	if err != nil {
+		return UsersPage{}, err
+	}
+	req, err := http.NewRequest(http.MethodGet, url, nil)
+	if err != nil {
+		return UsersPage{}, err
+	}
+
+	resp, err := sdk.sendRequest(req, token, string(CTJSON))
+	if err != nil {
+		return UsersPage{}, err
+	}
+	defer resp.Body.Close()
+
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return UsersPage{}, err
+	}
+
+	if resp.StatusCode != http.StatusOK {
+		return UsersPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
+	}
+	var up UsersPage
+	if err := json.Unmarshal(body, &up); err != nil {
+		return UsersPage{}, err
+	}
+
+	return up, nil
+}
+
 func (sdk mfSDK) CreateToken(user User) (string, error) {
 	data, err := json.Marshal(user)
 	if err != nil {
diff --git a/provision/service.go b/provision/service.go
index f90d387a..4a3cf652 100644
--- a/provision/service.go
+++ b/provision/service.go
@@ -34,6 +34,9 @@ var (
 	ErrFailedBootstrap          = errors.New("failed to create bootstrap config")
 	ErrFailedBootstrapValidate  = errors.New("failed to validate bootstrap config creation")
 	ErrGatewayUpdate            = errors.New("failed to updated gateway metadata")
+
+	limit  uint = 10
+	offset uint = 0
 )
 
 var _ Service = (*provisionService)(nil)
@@ -89,7 +92,14 @@ func New(cfg Config, sdk SDK.SDK, logger logger.Logger) Service {
 
 // Mapping retrieves current configuration
 func (ps *provisionService) Mapping(token string) (map[string]interface{}, error) {
-	if _, err := ps.sdk.User(token); err != nil {
+	userFilter := SDK.PageMetadata{
+		Email:    "",
+		Offset:   uint64(offset),
+		Limit:    uint64(limit),
+		Metadata: make(map[string]interface{}),
+	}
+
+	if _, err := ps.sdk.Users(token, userFilter); err != nil {
 		return map[string]interface{}{}, errors.Wrap(ErrUnauthorized, err)
 	}
 	return ps.conf.Bootstrap.Content, nil
diff --git a/things/api/things/http/transport.go b/things/api/things/http/transport.go
index e359e2fe..88958ecf 100644
--- a/things/api/things/http/transport.go
+++ b/things/api/things/http/transport.go
@@ -519,6 +519,7 @@ func encodeError(_ context.Context, err error, w http.ResponseWriter) {
 		err == apiutil.ErrMalformedPolicy,
 		err == apiutil.ErrBearerKey,
 		err == apiutil.ErrLimitSize,
+		err == apiutil.ErrOffsetSize,
 		err == apiutil.ErrInvalidOrder,
 		err == apiutil.ErrInvalidDirection,
 		err == apiutil.ErrInvalidIDFormat:
diff --git a/users/api/transport.go b/users/api/transport.go
index a106e871..ac2550b9 100644
--- a/users/api/transport.go
+++ b/users/api/transport.go
@@ -294,6 +294,8 @@ func encodeError(_ context.Context, err error, w http.ResponseWriter) {
 		err == apiutil.ErrMissingHost,
 		err == apiutil.ErrMissingPass,
 		err == apiutil.ErrMissingConfPass,
+		err == apiutil.ErrLimitSize,
+		err == apiutil.ErrOffsetSize,
 		err == apiutil.ErrInvalidResetPass:
 		w.WriteHeader(http.StatusBadRequest)
 	case errors.Contains(err, errors.ErrAuthentication),
diff --git a/users/mocks/users.go b/users/mocks/users.go
index 7c92d1fe..b838f11a 100644
--- a/users/mocks/users.go
+++ b/users/mocks/users.go
@@ -5,6 +5,7 @@ package mocks
 
 import (
 	"context"
+	"sort"
 	"sync"
 
 	"github.com/mainflux/mainflux/pkg/errors"
@@ -97,7 +98,19 @@ func (urm *userRepositoryMock) RetrieveAll(ctx context.Context, offset, limit ui
 	up := users.UserPage{}
 	i := uint64(0)
 
-	for _, u := range urm.users {
+	if email != "" {
+		val, ok := urm.users[email]
+		if !ok {
+			return users.UserPage{}, errors.ErrNotFound
+		}
+		up.Offset = offset
+		up.Limit = limit
+		up.Total = uint64(i)
+		up.Users = []users.User{val}
+		return up, nil
+	}
+
+	for _, u := range sortUsers(urm.users) {
 		if i >= offset && i < (limit+offset) {
 			up.Users = append(up.Users, u)
 		}
@@ -120,3 +133,18 @@ func (urm *userRepositoryMock) UpdatePassword(_ context.Context, token, password
 	}
 	return nil
 }
+
+func sortUsers(us map[string]users.User) []users.User {
+	users := []users.User{}
+	ids := make([]string, 0, len(us))
+	for k := range us {
+		ids = append(ids, k)
+	}
+
+	sort.Strings(ids)
+	for _, id := range ids {
+		users = append(users, us[id])
+	}
+
+	return users
+}
diff --git a/users/service.go b/users/service.go
index 49af996e..115f1ad9 100644
--- a/users/service.go
+++ b/users/service.go
@@ -81,7 +81,7 @@ type PageMetadata struct {
 	Total  uint64
 	Offset uint64
 	Limit  uint64
-	Name   string
+	Email  string
 }
 
 // GroupPage contains a page of groups.


Test patch:
diff --git a/pkg/sdk/go/channels_test.go b/pkg/sdk/go/channels_test.go
index 74b1ba61..ef9aaf19 100644
--- a/pkg/sdk/go/channels_test.go
+++ b/pkg/sdk/go/channels_test.go
@@ -239,58 +239,73 @@ func TestChannels(t *testing.T) {
 		name     string
 		err      error
 		response []sdk.Channel
+		metadata map[string]interface{}
 	}{
 		{
 			desc:     "get a list of channels",
 			token:    token,
-			offset:   0,
-			limit:    5,
+			offset:   offset,
+			limit:    limit,
 			err:      nil,
-			response: channels[0:5],
+			response: channels[0:limit],
+			metadata: make(map[string]interface{}),
 		},
 		{
 			desc:     "get a list of channels with invalid token",
 			token:    wrongValue,
-			offset:   0,
-			limit:    5,
+			offset:   offset,
+			limit:    limit,
 			err:      createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
 			response: nil,
+			metadata: make(map[string]interface{}),
 		},
 		{
 			desc:     "get a list of channels with empty token",
 			token:    "",
-			offset:   0,
-			limit:    5,
+			offset:   offset,
+			limit:    limit,
 			err:      createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
 			response: nil,
+			metadata: make(map[string]interface{}),
 		},
 		{
 			desc:     "get a list of channels without limit, default 10",
 			token:    token,
-			offset:   0,
+			offset:   offset,
 			limit:    0,
 			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
 			response: nil,
+			metadata: make(map[string]interface{}),
 		},
 		{
 			desc:     "get a list of channels with limit greater than max",
 			token:    token,
-			offset:   0,
+			offset:   offset,
 			limit:    110,
 			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
 			response: nil,
+			metadata: make(map[string]interface{}),
 		},
 		{
 			desc:     "get a list of channels with offset greater than max",
 			token:    token,
 			offset:   110,
-			limit:    5,
+			limit:    limit,
 			err:      nil,
 			response: []sdk.Channel{},
+			metadata: make(map[string]interface{}),
 		},
 	}
 	for _, tc := range cases {
-		page, err := mainfluxSDK.Channels(tc.token, tc.offset, tc.limit, tc.name)
+		filter := sdk.PageMetadata{
+			Name:     tc.name,
+			Total:    total,
+			Offset:   uint64(tc.offset),
+			Limit:    uint64(tc.limit),
+			Metadata: tc.metadata,
+		}
+
+		page, err := mainfluxSDK.Channels(tc.token, filter)
 		assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
 		assert.Equal(t, tc.response, page.Channels, fmt.Sprintf("%s: expected response channel %s, got %s", tc.desc, tc.response, page.Channels))
 	}
@@ -350,17 +365,17 @@ func TestChannelsByThing(t *testing.T) {
 			desc:     "get a list of channels by thing",
 			thing:    tid,
 			token:    token,
-			offset:   0,
-			limit:    5,
+			offset:   offset,
+			limit:    limit,
 			err:      nil,
-			response: channels[0:5],
+			response: channels[0:limit],
 		},
 		{
 			desc:     "get a list of channels by thing with invalid token",
 			thing:    tid,
 			token:    wrongValue,
-			offset:   0,
-			limit:    5,
+			offset:   offset,
+			limit:    limit,
 			err:      createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
 			response: nil,
 		},
@@ -368,8 +383,8 @@ func TestChannelsByThing(t *testing.T) {
 			desc:     "get a list of channels by thing with empty token",
 			thing:    tid,
 			token:    "",
-			offset:   0,
-			limit:    5,
+			offset:   offset,
+			limit:    limit,
 			err:      createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
 			response: nil,
 		},
@@ -377,7 +392,7 @@ func TestChannelsByThing(t *testing.T) {
 			desc:     "get a list of channels by thing with zero limit",
 			thing:    tid,
 			token:    token,
-			offset:   0,
+			offset:   offset,
 			limit:    0,
 			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
 			response: nil,
@@ -386,7 +401,7 @@ func TestChannelsByThing(t *testing.T) {
 			desc:     "get a list of channels by thing with limit greater than max",
 			thing:    tid,
 			token:    token,
-			offset:   0,
+			offset:   offset,
 			limit:    110,
 			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
 			response: nil,
@@ -396,7 +411,7 @@ func TestChannelsByThing(t *testing.T) {
 			thing:    tid,
 			token:    token,
 			offset:   110,
-			limit:    5,
+			limit:    limit,
 			err:      nil,
 			response: []sdk.Channel{},
 		},
@@ -404,7 +419,7 @@ func TestChannelsByThing(t *testing.T) {
 			desc:     "get a list of channels by thing with invalid args (zero limit) and invalid token",
 			thing:    tid,
 			token:    wrongValue,
-			offset:   0,
+			offset:   offset,
 			limit:    0,
 			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
 			response: nil,
@@ -413,7 +428,7 @@ func TestChannelsByThing(t *testing.T) {
 			desc:         "get a list of not connected channels by thing",
 			thing:        tid,
 			token:        token,
-			offset:       0,
+			offset:       offset,
 			limit:        100,
 			disconnected: true,
 			err:          nil,
diff --git a/pkg/sdk/go/things_test.go b/pkg/sdk/go/things_test.go
index dc93a0c7..5f5496d3 100644
--- a/pkg/sdk/go/things_test.go
+++ b/pkg/sdk/go/things_test.go
@@ -287,30 +287,34 @@ func TestThings(t *testing.T) {
 		err      error
 		response []sdk.Thing
 		name     string
+		metadata map[string]interface{}
 	}{
 		{
 			desc:     "get a list of things",
 			token:    token,
-			offset:   0,
-			limit:    5,
+			offset:   offset,
+			limit:    limit,
 			err:      nil,
-			response: things[0:5],
+			response: things[0:limit],
+			metadata: make(map[string]interface{}),
 		},
 		{
 			desc:     "get a list of things with invalid token",
 			token:    wrongValue,
-			offset:   0,
-			limit:    5,
+			offset:   offset,
+			limit:    limit,
 			err:      createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
 			response: nil,
+			metadata: make(map[string]interface{}),
 		},
 		{
 			desc:     "get a list of things with empty token",
 			token:    "",
-			offset:   0,
-			limit:    5,
+			offset:   offset,
+			limit:    limit,
 			err:      createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
 			response: nil,
+			metadata: make(map[string]interface{}),
 		},
 		{
 			desc:     "get a list of things with zero limit",
@@ -319,26 +323,36 @@ func TestThings(t *testing.T) {
 			limit:    0,
 			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
 			response: nil,
+			metadata: make(map[string]interface{}),
 		},
 		{
 			desc:     "get a list of things with limit greater than max",
 			token:    token,
-			offset:   0,
+			offset:   offset,
 			limit:    110,
 			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
 			response: nil,
+			metadata: make(map[string]interface{}),
 		},
 		{
 			desc:     "get a list of things with offset greater than max",
 			token:    token,
 			offset:   110,
-			limit:    5,
+			limit:    limit,
 			err:      nil,
 			response: []sdk.Thing{},
+			metadata: make(map[string]interface{}),
 		},
 	}
 	for _, tc := range cases {
-		page, err := mainfluxSDK.Things(tc.token, tc.offset, tc.limit, tc.name)
+		filter := sdk.PageMetadata{
+			Name:     tc.name,
+			Total:    total,
+			Offset:   uint64(tc.offset),
+			Limit:    uint64(tc.limit),
+			Metadata: tc.metadata,
+		}
+		page, err := mainfluxSDK.Things(tc.token, filter)
 		assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
 		assert.Equal(t, tc.response, page.Things, fmt.Sprintf("%s: expected response channel %s, got %s", tc.desc, tc.response, page.Things))
 	}
@@ -405,17 +419,17 @@ func TestThingsByChannel(t *testing.T) {
 			desc:     "get a list of things by channel",
 			channel:  cid,
 			token:    token,
-			offset:   0,
-			limit:    5,
+			offset:   offset,
+			limit:    limit,
 			err:      nil,
-			response: things[0:5],
+			response: things[0:limit],
 		},
 		{
 			desc:     "get a list of things by channel with invalid token",
 			channel:  cid,
 			token:    wrongValue,
-			offset:   0,
-			limit:    5,
+			offset:   offset,
+			limit:    limit,
 			err:      createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
 			response: nil,
 		},
@@ -423,8 +437,8 @@ func TestThingsByChannel(t *testing.T) {
 			desc:     "get a list of things by channel with empty token",
 			channel:  cid,
 			token:    "",
-			offset:   0,
-			limit:    5,
+			offset:   offset,
+			limit:    limit,
 			err:      createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
 			response: nil,
 		},
@@ -432,7 +446,7 @@ func TestThingsByChannel(t *testing.T) {
 			desc:     "get a list of things by channel with zero limit",
 			channel:  cid,
 			token:    token,
-			offset:   0,
+			offset:   offset,
 			limit:    0,
 			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
 			response: nil,
@@ -441,7 +455,7 @@ func TestThingsByChannel(t *testing.T) {
 			desc:     "get a list of things by channel with limit greater than max",
 			channel:  cid,
 			token:    token,
-			offset:   0,
+			offset:   offset,
 			limit:    110,
 			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
 			response: nil,
@@ -451,7 +465,7 @@ func TestThingsByChannel(t *testing.T) {
 			channel:  cid,
 			token:    token,
 			offset:   110,
-			limit:    5,
+			limit:    limit,
 			err:      nil,
 			response: []sdk.Thing{},
 		},
@@ -459,7 +473,7 @@ func TestThingsByChannel(t *testing.T) {
 			desc:     "get a list of things by channel with invalid args (zero limit) and invalid token",
 			channel:  cid,
 			token:    wrongValue,
-			offset:   0,
+			offset:   offset,
 			limit:    0,
 			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
 			response: nil,
@@ -468,7 +482,7 @@ func TestThingsByChannel(t *testing.T) {
 			desc:         "get a list of not connected things by channel",
 			channel:      cid,
 			token:        token,
-			offset:       0,
+			offset:       offset,
 			limit:        100,
 			disconnected: true,
 			err:          nil,
diff --git a/pkg/sdk/go/users_test.go b/pkg/sdk/go/users_test.go
index ec11a794..37d500e0 100644
--- a/pkg/sdk/go/users_test.go
+++ b/pkg/sdk/go/users_test.go
@@ -12,6 +12,7 @@ import (
 	"testing"
 
 	"github.com/mainflux/mainflux"
+	mfauth "github.com/mainflux/mainflux/auth"
 	"github.com/mainflux/mainflux/logger"
 	sdk "github.com/mainflux/mainflux/pkg/sdk/go"
 	"github.com/mainflux/mainflux/pkg/uuid"
@@ -28,7 +29,10 @@ const (
 )
 
 var (
-	passRegex = regexp.MustCompile("^.{8,}$")
+	passRegex        = regexp.MustCompile("^.{8,}$")
+	limit     uint64 = 5
+	offset    uint64 = 0
+	total     uint64 = 200
 )
 
 func newUserService() users.Service {
@@ -68,7 +72,7 @@ func TestCreateUser(t *testing.T) {
 	mockAuthzDB[user.Email] = append(mockAuthzDB[user.Email], mocks.SubjectSet{Object: "authorities", Relation: "member"})
 	auth := mocks.NewAuthService(map[string]string{user.Email: user.Email}, mockAuthzDB)
 
-	tkn, _ := auth.Issue(context.Background(), &mainflux.IssueReq{Id: user.ID, Email: user.Email, Type: 0})
+	tkn, _ := auth.Issue(context.Background(), &mainflux.IssueReq{Id: user.ID, Email: user.Email, Type: mfauth.APIKey})
 	token := tkn.GetValue()
 
 	mainfluxSDK := sdk.NewSDK(sdkConf)
@@ -128,6 +132,195 @@ func TestCreateUser(t *testing.T) {
 	}
 }
 
+func TestUser(t *testing.T) {
+	svc := newUserService()
+	ts := newUserServer(svc)
+	defer ts.Close()
+	sdkConf := sdk.Config{
+		UsersURL:        ts.URL,
+		MsgContentType:  contentType,
+		TLSVerification: false,
+	}
+
+	mainfluxSDK := sdk.NewSDK(sdkConf)
+	user := sdk.User{Email: "user@example.com", Password: "password"}
+
+	mockAuthzDB := map[string][]mocks.SubjectSet{}
+	mockAuthzDB[user.Email] = append(mockAuthzDB[user.Email], mocks.SubjectSet{Object: "authorities", Relation: "member"})
+	auth := mocks.NewAuthService(map[string]string{user.Email: user.Email}, mockAuthzDB)
+
+	tkn, _ := auth.Issue(context.Background(), &mainflux.IssueReq{Id: user.ID, Email: user.Email, Type: mfauth.APIKey})
+	token := tkn.GetValue()
+	userID, err := mainfluxSDK.CreateUser(token, user)
+	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
+	usertoken, err := mainfluxSDK.CreateToken(user)
+	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
+	user.ID = userID
+	user.Password = ""
+
+	cases := []struct {
+		desc     string
+		userID   string
+		token    string
+		err      error
+		response sdk.User
+	}{
+		{
+			desc:     "get existing user",
+			userID:   userID,
+			token:    usertoken,
+			err:      nil,
+			response: user,
+		},
+		{
+			desc:     "get non-existent user",
+			userID:   "43",
+			token:    usertoken,
+			err:      createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
+			response: sdk.User{},
+		},
+
+		{
+			desc:     "get user with invalid token",
+			userID:   userID,
+			token:    wrongValue,
+			err:      createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
+			response: sdk.User{},
+		},
+	}
+	for _, tc := range cases {
+		respUs, err := mainfluxSDK.User(tc.userID, tc.token)
+		assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
+		assert.Equal(t, tc.response, respUs, fmt.Sprintf("%s: expected response user %s, got %s", tc.desc, tc.response, respUs))
+	}
+}
+
+func TestUsers(t *testing.T) {
+	svc := newUserService()
+	ts := newUserServer(svc)
+	defer ts.Close()
+	sdkConf := sdk.Config{
+		UsersURL:        ts.URL,
+		MsgContentType:  contentType,
+		TLSVerification: false,
+	}
+
+	mainfluxSDK := sdk.NewSDK(sdkConf)
+	user := sdk.User{Email: "user@example.com", Password: "password"}
+
+	mockAuthzDB := map[string][]mocks.SubjectSet{}
+	mockAuthzDB[user.Email] = append(mockAuthzDB[user.Email], mocks.SubjectSet{Object: "authorities", Relation: "member"})
+	auth := mocks.NewAuthService(map[string]string{user.Email: user.Email}, mockAuthzDB)
+
+	tkn, _ := auth.Issue(context.Background(), &mainflux.IssueReq{Id: user.ID, Email: user.Email, Type: mfauth.APIKey})
+	token := tkn.GetValue()
+
+	var users []sdk.User
+
+	for i := 10; i < 100; i++ {
+		email := fmt.Sprintf("user%d@example.com", i)
+		password := fmt.Sprintf("password%d", i)
+		metadata := map[string]interface{}{"name": fmt.Sprintf("user%d", i)}
+		us := sdk.User{Email: email, Password: password, Metadata: metadata}
+		userID, err := mainfluxSDK.CreateUser(token, us)
+		require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
+		us.ID = userID
+		us.Password = ""
+		users = append(users, us)
+	}
+
+	cases := []struct {
+		desc     string
+		token    string
+		offset   uint64
+		limit    uint64
+		err      error
+		response []sdk.User
+		email    string
+		metadata map[string]interface{}
+	}{
+		{
+			desc:     "get a list of users",
+			token:    token,
+			offset:   offset,
+			limit:    limit,
+			err:      nil,
+			email:    "",
+			response: users[offset:limit],
+		},
+		{
+			desc:     "get a list of users with invalid token",
+			token:    wrongValue,
+			offset:   offset,
+			limit:    limit,
+			err:      createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
+			email:    "",
+			response: nil,
+		},
+		{
+			desc:     "get a list of users with empty token",
+			token:    "",
+			offset:   offset,
+			limit:    limit,
+			err:      createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
+			email:    "",
+			response: nil,
+		},
+		{
+			desc:     "get a list of users with zero limit",
+			token:    token,
+			offset:   offset,
+			limit:    0,
+			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
+			email:    "",
+			response: nil,
+		},
+		{
+			desc:     "get a list of users with limit greater than max",
+			token:    token,
+			offset:   offset,
+			limit:    110,
+			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
+			email:    "",
+			response: []sdk.User(nil),
+		},
+		{
+			desc:     "get a list of users with same email address",
+			token:    token,
+			offset:   0,
+			limit:    1,
+			err:      nil,
+			email:    "user99@example.com",
+			metadata: make(map[string]interface{}),
+			response: []sdk.User{users[89]},
+		},
+		{
+			desc:   "get a list of users with same email address and metadata",
+			token:  token,
+			offset: 0,
+			limit:  1,
+			err:    nil,
+			email:  "user99@example.com",
+			metadata: map[string]interface{}{
+				"name": "user99",
+			},
+			response: []sdk.User{users[89]},
+		},
+	}
+	for _, tc := range cases {
+		filter := sdk.PageMetadata{
+			Email:    tc.email,
+			Total:    total,
+			Offset:   uint64(tc.offset),
+			Limit:    uint64(tc.limit),
+			Metadata: tc.metadata,
+		}
+		page, err := mainfluxSDK.Users(tc.token, filter)
+		assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
+		assert.Equal(t, tc.response, page.Users, fmt.Sprintf("%s: expected response user %s, got %s", tc.desc, tc.response, page.Users))
+	}
+}
+
 func TestCreateToken(t *testing.T) {
 	svc := newUserService()
 	ts := newUserServer(svc)
@@ -145,7 +338,7 @@ func TestCreateToken(t *testing.T) {
 	mockAuthzDB[user.Email] = append(mockAuthzDB[user.Email], mocks.SubjectSet{Object: "authorities", Relation: "member"})
 	auth := mocks.NewAuthService(map[string]string{user.Email: user.Email}, mockAuthzDB)
 
-	tkn, _ := auth.Issue(context.Background(), &mainflux.IssueReq{Id: user.ID, Email: user.Email, Type: 0})
+	tkn, _ := auth.Issue(context.Background(), &mainflux.IssueReq{Id: user.ID, Email: user.Email, Type: mfauth.APIKey})
 	token := tkn.GetValue()
 	_, err := mainfluxSDK.CreateUser(token, user)
 	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
@@ -181,3 +374,123 @@ func TestCreateToken(t *testing.T) {
 		assert.Equal(t, tc.token, token, fmt.Sprintf("%s: expected response: %s, got:  %s", tc.desc, token, tc.token))
 	}
 }
+
+func TestUpdateUser(t *testing.T) {
+	svc := newUserService()
+	ts := newUserServer(svc)
+	defer ts.Close()
+	sdkConf := sdk.Config{
+		UsersURL:        ts.URL,
+		MsgContentType:  contentType,
+		TLSVerification: false,
+	}
+
+	mainfluxSDK := sdk.NewSDK(sdkConf)
+	user := sdk.User{Email: "user@example.com", Password: "password"}
+
+	mockAuthzDB := map[string][]mocks.SubjectSet{}
+	mockAuthzDB[user.Email] = append(mockAuthzDB[user.Email], mocks.SubjectSet{Object: "authorities", Relation: "member"})
+	auth := mocks.NewAuthService(map[string]string{user.Email: user.Email}, mockAuthzDB)
+
+	tkn, _ := auth.Issue(context.Background(), &mainflux.IssueReq{Id: user.ID, Email: user.Email, Type: mfauth.APIKey})
+	token := tkn.GetValue()
+	userID, err := mainfluxSDK.CreateUser(token, user)
+	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
+	usertoken, err := mainfluxSDK.CreateToken(user)
+	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
+
+	cases := []struct {
+		desc  string
+		user  sdk.User
+		token string
+		err   error
+	}{
+		{
+			desc:  "update email for user",
+			user:  sdk.User{ID: userID, Email: "user2@example.com", Password: "password"},
+			token: usertoken,
+			err:   nil,
+		},
+		{
+			desc:  "update email for user with invalid token",
+			user:  sdk.User{ID: userID, Email: "user2@example.com", Password: "password"},
+			token: wrongValue,
+			err:   createError(sdk.ErrFailedUpdate, http.StatusUnauthorized),
+		},
+		{
+			desc:  "update email for user with empty token",
+			user:  sdk.User{ID: userID, Email: "user2@example.com", Password: "password"},
+			token: "",
+			err:   createError(sdk.ErrFailedUpdate, http.StatusUnauthorized),
+		},
+		{
+			desc:  "update metadata for user",
+			user:  sdk.User{ID: userID, Metadata: metadata, Password: "password"},
+			token: usertoken,
+			err:   nil,
+		},
+	}
+	for _, tc := range cases {
+		err := mainfluxSDK.UpdateUser(tc.user, tc.token)
+		assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
+	}
+}
+
+func TestUpdatePassword(t *testing.T) {
+	svc := newUserService()
+	ts := newUserServer(svc)
+	defer ts.Close()
+	sdkConf := sdk.Config{
+		UsersURL:        ts.URL,
+		MsgContentType:  contentType,
+		TLSVerification: false,
+	}
+
+	mainfluxSDK := sdk.NewSDK(sdkConf)
+	user := sdk.User{Email: "user@example.com", Password: "password"}
+
+	mockAuthzDB := map[string][]mocks.SubjectSet{}
+	mockAuthzDB[user.Email] = append(mockAuthzDB[user.Email], mocks.SubjectSet{Object: "authorities", Relation: "member"})
+	auth := mocks.NewAuthService(map[string]string{user.Email: user.Email}, mockAuthzDB)
+
+	tkn, _ := auth.Issue(context.Background(), &mainflux.IssueReq{Id: user.ID, Email: user.Email, Type: mfauth.APIKey})
+	token := tkn.GetValue()
+	_, err := mainfluxSDK.CreateUser(token, user)
+	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
+	usertoken, err := mainfluxSDK.CreateToken(user)
+	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
+
+	cases := []struct {
+		desc    string
+		oldPass string
+		newPass string
+		token   string
+		err     error
+	}{
+		{
+			desc:    "update password for user",
+			oldPass: "password",
+			newPass: "password123",
+			token:   usertoken,
+			err:     nil,
+		},
+		{
+			desc:    "update password for user with invalid token",
+			oldPass: "password",
+			newPass: "password123",
+			token:   wrongValue,
+			err:     createError(sdk.ErrFailedUpdate, http.StatusUnauthorized),
+		},
+		{
+			desc:    "update password for user with empty token",
+			oldPass: "password",
+			newPass: "password123",
+			token:   "",
+			err:     createError(sdk.ErrFailedUpdate, http.StatusUnauthorized),
+		},
+	}
+	for _, tc := range cases {
+		err := mainfluxSDK.UpdatePassword(tc.oldPass, tc.newPass, tc.token)
+		assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
+	}
+}