mirror of
https://github.com/mainflux/mainflux.git
synced 2025-04-24 13:48:49 +08:00
NOISSUE - Unify group and clients implementations on things and users (#1793)
* unify groups repo implementation Signed-off-by: SammyOina <sammyoina@gmail.com> * unify clients implementation Signed-off-by: SammyOina <sammyoina@gmail.com> * closer client integration Signed-off-by: SammyOina <sammyoina@gmail.com> * further unification of groups Signed-off-by: SammyOina <sammyoina@gmail.com> * enable on update secret & owner Signed-off-by: SammyOina <sammyoina@gmail.com> * unify retrieve all Signed-off-by: SammyOina <sammyoina@gmail.com> * fully unify groups repository Signed-off-by: SammyOina <sammyoina@gmail.com> * add secret to retrieve all Signed-off-by: SammyOina <sammyoina@gmail.com> * save updated at Signed-off-by: SammyOina <sammyoina@gmail.com> * fix test Signed-off-by: SammyOina <sammyoina@gmail.com> * fix retrieve all tests Signed-off-by: SammyOina <sammyoina@gmail.com> * restore files Signed-off-by: SammyOina <sammyoina@gmail.com> * fix build Signed-off-by: SammyOina <sammyoina@gmail.com> * remove unused files Signed-off-by: SammyOina <sammyoina@gmail.com> * fix retrieve all tests Signed-off-by: SammyOina <sammyoina@gmail.com> * fix linting Signed-off-by: SammyOina <sammyoina@gmail.com> * fix linting Signed-off-by: SammyOina <sammyoina@gmail.com> * restore broken changes Signed-off-by: SammyOina <sammyoina@gmail.com> * restore setup tests Signed-off-by: SammyOina <sammyoina@gmail.com> * update where condition Signed-off-by: SammyOina <sammyoina@gmail.com> * remove extra db object Signed-off-by: SammyOina <sammyoina@gmail.com> * unify groups test Signed-off-by: SammyOina <sammyoina@gmail.com> * unify clients test Signed-off-by: SammyOina <sammyoina@gmail.com> * remove unused variables Signed-off-by: SammyOina <sammyoina@gmail.com> * update changes Signed-off-by: SammyOina <sammyoina@gmail.com> * sync with master current updates Signed-off-by: SammyOina <sammyoina@gmail.com> * update test Signed-off-by: SammyOina <sammyoina@gmail.com> * fix tests Signed-off-by: SammyOina <sammyoina@gmail.com> * fix test Signed-off-by: SammyOina <sammyoina@gmail.com> * fix test Signed-off-by: SammyOina <sammyoina@gmail.com> * fix tests Signed-off-by: SammyOina <sammyoina@gmail.com> * match changes in #1877 Signed-off-by: SammyOina <sammyoina@gmail.com> * separate things and users repos Signed-off-by: SammyOina <sammyoina@gmail.com> * remove comments implement retrieveBysecret in things only Signed-off-by: SammyOina <sammyoina@gmail.com> * remove exec Signed-off-by: SammyOina <sammyoina@gmail.com> * remove duplicate imports Signed-off-by: SammyOina <sammyoina@gmail.com> * wrap errors Signed-off-by: SammyOina <sammyoina@gmail.com> --------- Signed-off-by: SammyOina <sammyoina@gmail.com>
This commit is contained in:
parent
0f0d761a1b
commit
06800c1038
@ -27,6 +27,7 @@ import (
|
||||
grpcserver "github.com/mainflux/mainflux/internal/server/grpc"
|
||||
httpserver "github.com/mainflux/mainflux/internal/server/http"
|
||||
mflog "github.com/mainflux/mainflux/logger"
|
||||
gpostgres "github.com/mainflux/mainflux/pkg/groups/postgres"
|
||||
"github.com/mainflux/mainflux/pkg/uuid"
|
||||
"github.com/mainflux/mainflux/things/clients"
|
||||
capi "github.com/mainflux/mainflux/things/clients/api"
|
||||
@ -36,7 +37,6 @@ import (
|
||||
ctracing "github.com/mainflux/mainflux/things/clients/tracing"
|
||||
"github.com/mainflux/mainflux/things/groups"
|
||||
gapi "github.com/mainflux/mainflux/things/groups/api"
|
||||
gpostgres "github.com/mainflux/mainflux/things/groups/postgres"
|
||||
chcache "github.com/mainflux/mainflux/things/groups/redis"
|
||||
gtracing "github.com/mainflux/mainflux/things/groups/tracing"
|
||||
tpolicies "github.com/mainflux/mainflux/things/policies"
|
||||
@ -214,7 +214,7 @@ func main() {
|
||||
func newService(ctx context.Context, db *sqlx.DB, dbConfig pgClient.Config, auth upolicies.AuthServiceClient, cacheClient *redis.Client, esClient *redis.Client, keyDuration string, tracer trace.Tracer, logger mflog.Logger) (clients.Service, groups.Service, tpolicies.Service) {
|
||||
database := postgres.NewDatabase(db, dbConfig, tracer)
|
||||
cRepo := cpostgres.NewRepository(database)
|
||||
gRepo := gpostgres.NewRepository(database)
|
||||
gRepo := gpostgres.New(database)
|
||||
pRepo := ppostgres.NewRepository(database)
|
||||
|
||||
idp := uuid.New()
|
||||
|
@ -29,16 +29,16 @@ import (
|
||||
httpserver "github.com/mainflux/mainflux/internal/server/http"
|
||||
mflog "github.com/mainflux/mainflux/logger"
|
||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||
gpostgres "github.com/mainflux/mainflux/pkg/groups/postgres"
|
||||
"github.com/mainflux/mainflux/pkg/uuid"
|
||||
"github.com/mainflux/mainflux/users/clients"
|
||||
capi "github.com/mainflux/mainflux/users/clients/api"
|
||||
"github.com/mainflux/mainflux/users/clients/emailer"
|
||||
cpostgres "github.com/mainflux/mainflux/users/clients/postgres"
|
||||
uclients "github.com/mainflux/mainflux/users/clients/postgres"
|
||||
ucache "github.com/mainflux/mainflux/users/clients/redis"
|
||||
ctracing "github.com/mainflux/mainflux/users/clients/tracing"
|
||||
"github.com/mainflux/mainflux/users/groups"
|
||||
gapi "github.com/mainflux/mainflux/users/groups/api"
|
||||
gpostgres "github.com/mainflux/mainflux/users/groups/postgres"
|
||||
gcache "github.com/mainflux/mainflux/users/groups/redis"
|
||||
gtracing "github.com/mainflux/mainflux/users/groups/tracing"
|
||||
"github.com/mainflux/mainflux/users/hasher"
|
||||
@ -200,8 +200,8 @@ func main() {
|
||||
|
||||
func newService(ctx context.Context, db *sqlx.DB, dbConfig pgClient.Config, esClient *redis.Client, tracer trace.Tracer, c config, ec email.Config, logger mflog.Logger) (clients.Service, groups.Service, policies.Service) {
|
||||
database := postgres.NewDatabase(db, dbConfig, tracer)
|
||||
cRepo := cpostgres.NewRepository(database)
|
||||
gRepo := gpostgres.NewRepository(database)
|
||||
cRepo := uclients.NewRepository(database)
|
||||
gRepo := gpostgres.New(database)
|
||||
pRepo := ppostgres.NewRepository(database)
|
||||
|
||||
idp := uuid.New()
|
||||
@ -250,7 +250,7 @@ func newService(ctx context.Context, db *sqlx.DB, dbConfig pgClient.Config, esCl
|
||||
return csvc, gsvc, psvc
|
||||
}
|
||||
|
||||
func createAdmin(ctx context.Context, c config, crepo mfclients.Repository, hsr clients.Hasher, svc clients.Service) error {
|
||||
func createAdmin(ctx context.Context, c config, crepo uclients.Repository, hsr clients.Hasher, svc clients.Service) error {
|
||||
id, err := uuid.New().ID()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -68,9 +68,6 @@ type MembersPage struct {
|
||||
|
||||
// Repository specifies an account persistence API.
|
||||
type Repository interface {
|
||||
// Save persists the client account. A non-nil error is returned to indicate
|
||||
// operation failure.
|
||||
Save(ctx context.Context, client ...Client) ([]Client, error)
|
||||
|
||||
// RetrieveByID retrieves client by its unique ID.
|
||||
RetrieveByID(ctx context.Context, id string) (Client, error)
|
||||
@ -101,8 +98,6 @@ type Repository interface {
|
||||
|
||||
// ChangeStatus changes client status to enabled or disabled
|
||||
ChangeStatus(ctx context.Context, client Client) (Client, error)
|
||||
|
||||
RetrieveBySecret(ctx context.Context, key string) (Client, error)
|
||||
}
|
||||
|
||||
// Validate returns an error if client representation is invalid.
|
||||
|
456
pkg/clients/postgres/clients.go
Normal file
456
pkg/clients/postgres/clients.go
Normal file
@ -0,0 +1,456 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/mainflux/mainflux/internal/postgres"
|
||||
"github.com/mainflux/mainflux/pkg/clients"
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
"github.com/mainflux/mainflux/pkg/groups"
|
||||
)
|
||||
|
||||
type ClientRepository struct {
|
||||
DB postgres.Database
|
||||
}
|
||||
|
||||
func (repo ClientRepository) Update(ctx context.Context, client clients.Client) (clients.Client, error) {
|
||||
var query []string
|
||||
var upq string
|
||||
if client.Name != "" {
|
||||
query = append(query, "name = :name,")
|
||||
}
|
||||
if client.Metadata != nil {
|
||||
query = append(query, "metadata = :metadata,")
|
||||
}
|
||||
if len(query) > 0 {
|
||||
upq = strings.Join(query, " ")
|
||||
}
|
||||
client.Status = clients.EnabledStatus
|
||||
q := fmt.Sprintf(`UPDATE clients SET %s updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id AND status = :status
|
||||
RETURNING id, name, tags, identity, secret, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`,
|
||||
upq)
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo ClientRepository) UpdateTags(ctx context.Context, client clients.Client) (clients.Client, error) {
|
||||
client.Status = clients.EnabledStatus
|
||||
q := `UPDATE clients SET tags = :tags, updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id AND status = :status
|
||||
RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo ClientRepository) UpdateIdentity(ctx context.Context, client clients.Client) (clients.Client, error) {
|
||||
q := `UPDATE clients SET identity = :identity, updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id AND status = :status
|
||||
RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo ClientRepository) UpdateSecret(ctx context.Context, client clients.Client) (clients.Client, error) {
|
||||
q := `UPDATE clients SET secret = :secret, updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id AND status = :status
|
||||
RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo ClientRepository) UpdateOwner(ctx context.Context, client clients.Client) (clients.Client, error) {
|
||||
q := `UPDATE clients SET owner_id = :owner_id, updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id AND status = :status
|
||||
RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo ClientRepository) ChangeStatus(ctx context.Context, client clients.Client) (clients.Client, error) {
|
||||
q := `UPDATE clients SET status = :status WHERE id = :id
|
||||
RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo ClientRepository) RetrieveByID(ctx context.Context, id string) (clients.Client, error) {
|
||||
q := `SELECT id, name, tags, COALESCE(owner_id, '') AS owner_id, identity, secret, metadata, created_at, updated_at, updated_by, status
|
||||
FROM clients WHERE id = :id`
|
||||
|
||||
dbc := DBClient{
|
||||
ID: id,
|
||||
}
|
||||
|
||||
row, err := repo.DB.NamedQueryContext(ctx, q, dbc)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return clients.Client{}, errors.Wrap(errors.ErrNotFound, err)
|
||||
}
|
||||
return clients.Client{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
defer row.Close()
|
||||
row.Next()
|
||||
dbc = DBClient{}
|
||||
if err := row.StructScan(&dbc); err != nil {
|
||||
return clients.Client{}, errors.Wrap(errors.ErrNotFound, err)
|
||||
}
|
||||
|
||||
return ToClient(dbc)
|
||||
}
|
||||
|
||||
func (repo ClientRepository) RetrieveByIdentity(ctx context.Context, identity string) (clients.Client, error) {
|
||||
q := `SELECT id, name, tags, COALESCE(owner_id, '') AS owner_id, identity, secret, metadata, created_at, updated_at, updated_by, status
|
||||
FROM clients WHERE identity = :identity AND status = :status`
|
||||
|
||||
dbc := DBClient{
|
||||
Identity: identity,
|
||||
Status: clients.EnabledStatus,
|
||||
}
|
||||
|
||||
row, err := repo.DB.NamedQueryContext(ctx, q, dbc)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return clients.Client{}, errors.Wrap(errors.ErrNotFound, err)
|
||||
}
|
||||
return clients.Client{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
defer row.Close()
|
||||
row.Next()
|
||||
dbc = DBClient{}
|
||||
if err := row.StructScan(&dbc); err != nil {
|
||||
return clients.Client{}, errors.Wrap(errors.ErrNotFound, err)
|
||||
}
|
||||
|
||||
return ToClient(dbc)
|
||||
}
|
||||
|
||||
func (repo ClientRepository) RetrieveAll(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) {
|
||||
query, err := pageQuery(pm)
|
||||
if err != nil {
|
||||
return clients.ClientsPage{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.secret, c.metadata, COALESCE(c.owner_id, '') AS owner_id, c.status,
|
||||
c.created_at, c.updated_at, COALESCE(c.updated_by, '') AS updated_by FROM clients c %s ORDER BY c.created_at LIMIT :limit OFFSET :offset;`, query)
|
||||
|
||||
dbPage, err := toDBClientsPage(pm)
|
||||
if err != nil {
|
||||
return clients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
rows, err := repo.DB.NamedQueryContext(ctx, q, dbPage)
|
||||
if err != nil {
|
||||
return clients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []clients.Client
|
||||
for rows.Next() {
|
||||
dbc := DBClient{}
|
||||
if err := rows.StructScan(&dbc); err != nil {
|
||||
return clients.ClientsPage{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
c, err := ToClient(dbc)
|
||||
if err != nil {
|
||||
return clients.ClientsPage{}, err
|
||||
}
|
||||
|
||||
items = append(items, c)
|
||||
}
|
||||
cq := fmt.Sprintf(`SELECT COUNT(*) FROM clients c %s;`, query)
|
||||
|
||||
total, err := postgres.Total(ctx, repo.DB, cq, dbPage)
|
||||
if err != nil {
|
||||
return clients.ClientsPage{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
page := clients.ClientsPage{
|
||||
Clients: items,
|
||||
Page: clients.Page{
|
||||
Total: total,
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
},
|
||||
}
|
||||
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func (repo ClientRepository) Members(ctx context.Context, groupID string, pm clients.Page) (clients.MembersPage, error) {
|
||||
emq, err := pageQuery(pm)
|
||||
if err != nil {
|
||||
return clients.MembersPage{}, err
|
||||
}
|
||||
|
||||
aq := ""
|
||||
// If not admin, the client needs to have a g_list action on the group or they are the owner.
|
||||
if pm.Subject != "" {
|
||||
aq = `AND (EXISTS (SELECT 1 FROM policies p WHERE p.subject = :subject AND :action=ANY(actions))
|
||||
OR EXISTS (SELECT 1 FROM groups g WHERE g.owner_id = :subject AND g.id = :group_id))
|
||||
AND c.id != :subject`
|
||||
}
|
||||
q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.metadata, c.identity, c.status,
|
||||
c.created_at, c.updated_at FROM clients c
|
||||
INNER JOIN policies ON c.id=policies.subject %s AND policies.object = :group_id %s
|
||||
ORDER BY c.created_at LIMIT :limit OFFSET :offset;`, emq, aq)
|
||||
dbPage, err := toDBClientsPage(pm)
|
||||
if err != nil {
|
||||
return clients.MembersPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
dbPage.GroupID = groupID
|
||||
rows, err := repo.DB.NamedQueryContext(ctx, q, dbPage)
|
||||
if err != nil {
|
||||
return clients.MembersPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembers, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []clients.Client
|
||||
for rows.Next() {
|
||||
dbc := DBClient{}
|
||||
if err := rows.StructScan(&dbc); err != nil {
|
||||
return clients.MembersPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembers, err)
|
||||
}
|
||||
|
||||
c, err := ToClient(dbc)
|
||||
if err != nil {
|
||||
return clients.MembersPage{}, err
|
||||
}
|
||||
|
||||
items = append(items, c)
|
||||
}
|
||||
cq := fmt.Sprintf(`SELECT COUNT(*) FROM clients c INNER JOIN policies ON c.id=policies.subject %s AND policies.object = :group_id`, emq)
|
||||
if pm.Subject != "" {
|
||||
cq = fmt.Sprintf("%s AND c.id != :subject", cq)
|
||||
}
|
||||
|
||||
total, err := postgres.Total(ctx, repo.DB, cq, dbPage)
|
||||
if err != nil {
|
||||
return clients.MembersPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembers, err)
|
||||
}
|
||||
|
||||
page := clients.MembersPage{
|
||||
Members: items,
|
||||
Page: clients.Page{
|
||||
Total: total,
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
},
|
||||
}
|
||||
return page, nil
|
||||
}
|
||||
|
||||
// generic update function.
|
||||
func (repo ClientRepository) update(ctx context.Context, client clients.Client, query string) (clients.Client, error) {
|
||||
dbc, err := ToDBClient(client)
|
||||
if err != nil {
|
||||
return clients.Client{}, errors.Wrap(errors.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
row, err := repo.DB.NamedQueryContext(ctx, query, dbc)
|
||||
if err != nil {
|
||||
return clients.Client{}, postgres.HandleError(err, errors.ErrUpdateEntity)
|
||||
}
|
||||
|
||||
defer row.Close()
|
||||
if ok := row.Next(); !ok {
|
||||
return clients.Client{}, errors.Wrap(errors.ErrNotFound, row.Err())
|
||||
}
|
||||
dbc = DBClient{}
|
||||
if err := row.StructScan(&dbc); err != nil {
|
||||
return clients.Client{}, err
|
||||
}
|
||||
|
||||
return ToClient(dbc)
|
||||
}
|
||||
|
||||
type DBClient struct {
|
||||
ID string `db:"id"`
|
||||
Name string `db:"name,omitempty"`
|
||||
Tags pgtype.TextArray `db:"tags,omitempty"`
|
||||
Identity string `db:"identity"`
|
||||
Owner *string `db:"owner_id,omitempty"` // nullable
|
||||
Secret string `db:"secret"`
|
||||
Metadata []byte `db:"metadata,omitempty"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt sql.NullTime `db:"updated_at,omitempty"`
|
||||
UpdatedBy *string `db:"updated_by,omitempty"`
|
||||
Groups []groups.Group `db:"groups,omitempty"`
|
||||
Status clients.Status `db:"status"`
|
||||
Role clients.Role `db:"role,omitempty"`
|
||||
}
|
||||
|
||||
func ToDBClient(c clients.Client) (DBClient, error) {
|
||||
data := []byte("{}")
|
||||
if len(c.Metadata) > 0 {
|
||||
b, err := json.Marshal(c.Metadata)
|
||||
if err != nil {
|
||||
return DBClient{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
data = b
|
||||
}
|
||||
var tags pgtype.TextArray
|
||||
if err := tags.Set(c.Tags); err != nil {
|
||||
return DBClient{}, err
|
||||
}
|
||||
var owner *string
|
||||
if c.Owner != "" {
|
||||
owner = &c.Owner
|
||||
}
|
||||
var updatedBy *string
|
||||
if c.UpdatedBy != "" {
|
||||
updatedBy = &c.UpdatedBy
|
||||
}
|
||||
var updatedAt sql.NullTime
|
||||
if c.UpdatedAt != (time.Time{}) {
|
||||
updatedAt = sql.NullTime{Time: c.UpdatedAt, Valid: true}
|
||||
}
|
||||
|
||||
return DBClient{
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
Tags: tags,
|
||||
Owner: owner,
|
||||
Identity: c.Credentials.Identity,
|
||||
Secret: c.Credentials.Secret,
|
||||
Metadata: data,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: updatedAt,
|
||||
UpdatedBy: updatedBy,
|
||||
Status: c.Status,
|
||||
Role: c.Role,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ToClient(c DBClient) (clients.Client, error) {
|
||||
var metadata clients.Metadata
|
||||
if c.Metadata != nil {
|
||||
if err := json.Unmarshal([]byte(c.Metadata), &metadata); err != nil {
|
||||
return clients.Client{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
}
|
||||
var tags []string
|
||||
for _, e := range c.Tags.Elements {
|
||||
tags = append(tags, e.String)
|
||||
}
|
||||
var owner string
|
||||
if c.Owner != nil {
|
||||
owner = *c.Owner
|
||||
}
|
||||
var updatedBy string
|
||||
if c.UpdatedBy != nil {
|
||||
updatedBy = *c.UpdatedBy
|
||||
}
|
||||
var updatedAt time.Time
|
||||
if c.UpdatedAt.Valid {
|
||||
updatedAt = c.UpdatedAt.Time
|
||||
}
|
||||
|
||||
return clients.Client{
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
Tags: tags,
|
||||
Owner: owner,
|
||||
Credentials: clients.Credentials{
|
||||
Identity: c.Identity,
|
||||
Secret: c.Secret,
|
||||
},
|
||||
Metadata: metadata,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: updatedAt,
|
||||
UpdatedBy: updatedBy,
|
||||
Status: c.Status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toDBClientsPage(pm clients.Page) (dbClientsPage, error) {
|
||||
_, data, err := postgres.CreateMetadataQuery("", pm.Metadata)
|
||||
if err != nil {
|
||||
return dbClientsPage{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
return dbClientsPage{
|
||||
Name: pm.Name,
|
||||
Identity: pm.Identity,
|
||||
Metadata: data,
|
||||
Owner: pm.Owner,
|
||||
Total: pm.Total,
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
Status: pm.Status,
|
||||
Tag: pm.Tag,
|
||||
Subject: pm.Subject,
|
||||
Action: pm.Action,
|
||||
SharedBy: pm.SharedBy,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type dbClientsPage struct {
|
||||
Total uint64 `db:"total"`
|
||||
Limit uint64 `db:"limit"`
|
||||
Offset uint64 `db:"offset"`
|
||||
Name string `db:"name"`
|
||||
Owner string `db:"owner_id"`
|
||||
Identity string `db:"identity"`
|
||||
Metadata []byte `db:"metadata"`
|
||||
Tag string `db:"tag"`
|
||||
Status clients.Status `db:"status"`
|
||||
GroupID string `db:"group_id"`
|
||||
SharedBy string `db:"shared_by"`
|
||||
Subject string `db:"subject"`
|
||||
Action string `db:"action"`
|
||||
}
|
||||
|
||||
func pageQuery(pm clients.Page) (string, error) {
|
||||
mq, _, err := postgres.CreateMetadataQuery("", pm.Metadata)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
var query []string
|
||||
var emq string
|
||||
if mq != "" {
|
||||
query = append(query, mq)
|
||||
}
|
||||
if len(pm.IDs) != 0 {
|
||||
query = append(query, fmt.Sprintf("id IN ('%s')", strings.Join(pm.IDs, "','")))
|
||||
}
|
||||
if pm.Identity != "" {
|
||||
query = append(query, "c.identity = :identity")
|
||||
}
|
||||
if pm.Name != "" {
|
||||
query = append(query, "c.name = :name")
|
||||
}
|
||||
if pm.Tag != "" {
|
||||
query = append(query, ":tag = ANY(c.tags)")
|
||||
}
|
||||
if pm.Status != clients.AllStatus {
|
||||
query = append(query, "c.status = :status")
|
||||
}
|
||||
// For listing clients that the specified client owns but not sharedby
|
||||
if pm.Owner != "" && pm.SharedBy == "" {
|
||||
query = append(query, "c.owner_id = :owner_id")
|
||||
}
|
||||
|
||||
// For listing clients that the specified client owns and that are shared with the specified client
|
||||
if pm.Owner != "" && pm.SharedBy != "" {
|
||||
query = append(query, "(c.owner_id = :owner_id OR (policies.object IN (SELECT object FROM policies WHERE subject = :shared_by AND :action=ANY(actions)))) AND c.id != :shared_by")
|
||||
}
|
||||
// For listing clients that the specified client is shared with
|
||||
if pm.SharedBy != "" && pm.Owner == "" {
|
||||
query = append(query, "c.owner_id != :shared_by AND (policies.object IN (SELECT object FROM policies WHERE subject = :shared_by AND :action=ANY(actions)))")
|
||||
}
|
||||
if len(query) > 0 {
|
||||
emq = fmt.Sprintf("WHERE %s", strings.Join(query, " AND "))
|
||||
if strings.Contains(emq, "policies") {
|
||||
emq = fmt.Sprintf("LEFT JOIN policies ON policies.subject = c.id %s", emq)
|
||||
}
|
||||
}
|
||||
return emq, nil
|
||||
}
|
997
pkg/clients/postgres/clients_test.go
Normal file
997
pkg/clients/postgres/clients_test.go
Normal file
@ -0,0 +1,997 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mainflux/mainflux/internal/testsutil"
|
||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
||||
gpostgres "github.com/mainflux/mainflux/pkg/groups/postgres"
|
||||
"github.com/mainflux/mainflux/pkg/uuid"
|
||||
cpostgres "github.com/mainflux/mainflux/users/clients/postgres"
|
||||
"github.com/mainflux/mainflux/users/policies"
|
||||
ppostgres "github.com/mainflux/mainflux/users/policies/postgres"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
idProvider = uuid.New()
|
||||
password = "$tr0ngPassw0rd"
|
||||
clientIdentity = "client-identity@example.com"
|
||||
clientName = "client name"
|
||||
wrongName = "wrong-name"
|
||||
wrongID = "wrong-id"
|
||||
)
|
||||
|
||||
func TestClientsRetrieveByID(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: clientName,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: clientIdentity,
|
||||
Secret: password,
|
||||
},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
clients, err := repo.Save(context.Background(), client)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
client = clients
|
||||
|
||||
cases := map[string]struct {
|
||||
ID string
|
||||
err error
|
||||
}{
|
||||
"retrieve existing client": {client.ID, nil},
|
||||
"retrieve non-existing client": {wrongID, errors.ErrNotFound},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
cli, err := repo.RetrieveByID(context.Background(), tc.ID)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client.ID, cli.ID, fmt.Sprintf("retrieve client by ID : client ID : expected %s got %s\n", client.ID, cli.ID))
|
||||
assert.Equal(t, client.Name, cli.Name, fmt.Sprintf("retrieve client by ID : client Name : expected %s got %s\n", client.Name, cli.Name))
|
||||
assert.Equal(t, client.Credentials.Identity, cli.Credentials.Identity, fmt.Sprintf("retrieve client by ID : client Identity : expected %s got %s\n", client.Credentials.Identity, cli.Credentials.Identity))
|
||||
assert.Equal(t, client.Status, cli.Status, fmt.Sprintf("retrieve client by ID : client Status : expected %d got %d\n", client.Status, cli.Status))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsRetrieveByIdentity(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: clientName,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: clientIdentity,
|
||||
Secret: password,
|
||||
},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
_, err := repo.Save(context.Background(), client)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
|
||||
cases := map[string]struct {
|
||||
identity string
|
||||
err error
|
||||
}{
|
||||
"retrieve existing client": {clientIdentity, nil},
|
||||
"retrieve non-existing client": {wrongID, errors.ErrNotFound},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
_, err := repo.RetrieveByIdentity(context.Background(), tc.identity)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsRetrieveAll(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.New(database)
|
||||
prepo := ppostgres.NewRepository(database)
|
||||
|
||||
var nClients = uint64(200)
|
||||
var ownerID = testsutil.GenerateUUID(t, idProvider)
|
||||
|
||||
meta := mfclients.Metadata{
|
||||
"admin": "true",
|
||||
}
|
||||
wrongMeta := mfclients.Metadata{
|
||||
"admin": "false",
|
||||
}
|
||||
var expectedClients = []mfclients.Client{}
|
||||
|
||||
var sharedGroup = mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "shared-group",
|
||||
}
|
||||
_, err := grepo.Save(context.Background(), sharedGroup)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
|
||||
for i := uint64(0); i < nClients; i++ {
|
||||
identity := fmt.Sprintf("TestRetrieveAll%d@example.com", i)
|
||||
client := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: identity,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: identity,
|
||||
Secret: password,
|
||||
},
|
||||
Metadata: mfclients.Metadata{},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
if i%10 == 0 {
|
||||
client.Owner = ownerID
|
||||
client.Metadata = meta
|
||||
client.Tags = []string{"Test"}
|
||||
}
|
||||
if i%50 == 0 {
|
||||
client.Status = mfclients.DisabledStatus
|
||||
}
|
||||
_, err := repo.Save(context.Background(), client)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
expectedClients = append(expectedClients, client)
|
||||
var policy = policies.Policy{
|
||||
Subject: client.ID,
|
||||
Object: sharedGroup.ID,
|
||||
Actions: []string{"c_list"},
|
||||
}
|
||||
err = prepo.Save(context.Background(), policy)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
size uint64
|
||||
pm mfclients.Page
|
||||
response []mfclients.Client
|
||||
}{
|
||||
"retrieve all clients empty page": {
|
||||
pm: mfclients.Page{},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients,
|
||||
size: 200,
|
||||
},
|
||||
"retrieve all clients with limit": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: 50,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients[0:50],
|
||||
size: 50,
|
||||
},
|
||||
"retrieve all clients with offset": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 50,
|
||||
Limit: nClients,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients[50:200],
|
||||
size: 150,
|
||||
},
|
||||
"retrieve all clients with limit and offset": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 50,
|
||||
Limit: 50,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients[50:100],
|
||||
size: 50,
|
||||
},
|
||||
"retrieve all clients with limit and offset not full": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 170,
|
||||
Limit: 50,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients[170:200],
|
||||
size: 30,
|
||||
},
|
||||
"retrieve all clients by metadata": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Metadata: meta,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60],
|
||||
expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130],
|
||||
expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190],
|
||||
},
|
||||
size: 20,
|
||||
},
|
||||
"retrieve clients by wrong metadata": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Metadata: wrongMeta,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients by name": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Name: "TestRetrieveAll3@example.com",
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[3]},
|
||||
size: 1,
|
||||
},
|
||||
"retrieve clients by wrong name": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Name: wrongName,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients by owner": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Owner: ownerID,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60],
|
||||
expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130],
|
||||
expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190],
|
||||
},
|
||||
size: 20,
|
||||
},
|
||||
"retrieve clients by wrong owner": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Owner: wrongID,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients shared by": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
SharedBy: expectedClients[0].ID,
|
||||
Action: "c_list",
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60],
|
||||
expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130],
|
||||
expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190],
|
||||
},
|
||||
size: 20,
|
||||
},
|
||||
"retrieve all clients shared by and owned by": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
SharedBy: ownerID,
|
||||
Owner: ownerID,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60],
|
||||
expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130],
|
||||
expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190],
|
||||
},
|
||||
size: 20,
|
||||
},
|
||||
"retrieve all clients by disabled status": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Status: mfclients.DisabledStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[0], expectedClients[50], expectedClients[100], expectedClients[150]},
|
||||
size: 4,
|
||||
},
|
||||
"retrieve all clients by combined status": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients,
|
||||
size: 200,
|
||||
},
|
||||
"retrieve clients by the wrong status": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Status: 10,
|
||||
},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients by tags": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Tag: "Test",
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60],
|
||||
expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130],
|
||||
expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190],
|
||||
},
|
||||
size: 20,
|
||||
},
|
||||
"retrieve clients by wrong tags": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Tag: "wrongTags",
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients by sharedby": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
SharedBy: expectedClients[0].ID,
|
||||
Status: mfclients.AllStatus,
|
||||
Action: "c_list",
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60],
|
||||
expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130],
|
||||
expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190],
|
||||
},
|
||||
size: 20,
|
||||
},
|
||||
}
|
||||
for desc, tc := range cases {
|
||||
page, err := repo.RetrieveAll(context.Background(), tc.pm)
|
||||
size := uint64(len(page.Clients))
|
||||
assert.ElementsMatch(t, page.Clients, tc.response, fmt.Sprintf("%s: expected %v got %v\n", desc, tc.response, page.Clients))
|
||||
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", desc, tc.size, size))
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %d\n", desc, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupsMembers(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
crepo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.New(database)
|
||||
prepo := ppostgres.NewRepository(database)
|
||||
|
||||
clientA := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "client-memberships",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client-memberships1@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Metadata: mfclients.Metadata{},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
clientB := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "client-memberships",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client-memberships2@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Metadata: mfclients.Metadata{},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
group := mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "group-membership",
|
||||
Metadata: mfclients.Metadata{},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
policyA := policies.Policy{
|
||||
Subject: clientA.ID,
|
||||
Object: group.ID,
|
||||
Actions: []string{"g_list"},
|
||||
}
|
||||
policyB := policies.Policy{
|
||||
Subject: clientB.ID,
|
||||
Object: group.ID,
|
||||
Actions: []string{"g_list"},
|
||||
}
|
||||
|
||||
_, err := crepo.Save(context.Background(), clientA)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save client: expected %v got %s\n", nil, err))
|
||||
clientA.Credentials.Secret = ""
|
||||
_, err = crepo.Save(context.Background(), clientB)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save client: expected %v got %s\n", nil, err))
|
||||
clientB.Credentials.Secret = ""
|
||||
_, err = grepo.Save(context.Background(), group)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save group: expected %v got %s\n", nil, err))
|
||||
err = prepo.Save(context.Background(), policyA)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save policy: expected %v got %s\n", nil, err))
|
||||
err = prepo.Save(context.Background(), policyB)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save policy: expected %v got %s\n", nil, err))
|
||||
|
||||
cases := map[string]struct {
|
||||
ID string
|
||||
err error
|
||||
}{
|
||||
"retrieve members for existing group": {group.ID, nil},
|
||||
"retrieve members for non-existing group": {wrongID, nil},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
mp, err := crepo.Members(context.Background(), tc.ID, mfclients.Page{Total: 10, Offset: 0, Limit: 10, Status: mfclients.AllStatus, Subject: clientB.ID, Action: "g_list"})
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
if tc.ID == group.ID {
|
||||
assert.ElementsMatch(t, mp.Members, []mfclients.Client{clientA}, fmt.Sprintf("%s: expected %v got %v\n", desc, []mfclients.Client{clientA, clientB}, mp.Members))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsUpdateMetadata(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Metadata: mfclients.Metadata{
|
||||
"name": "enabled-client",
|
||||
},
|
||||
Tags: []string{"enabled", "tag1"},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
client2 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "disabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Metadata: mfclients.Metadata{
|
||||
"name": "disabled-client",
|
||||
},
|
||||
Tags: []string{"disabled", "tag1"},
|
||||
Status: mfclients.DisabledStatus,
|
||||
}
|
||||
|
||||
clients1, err := repo.Save(context.Background(), client1)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client with metadata: expected %v got %s\n", nil, err))
|
||||
clients2, err := repo.Save(context.Background(), client2)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err))
|
||||
client1 = clients1
|
||||
client2 = clients2
|
||||
|
||||
ucases := []struct {
|
||||
desc string
|
||||
update string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update metadata for enabled client",
|
||||
update: "metadata",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "metadata",
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update metadata for disabled client",
|
||||
update: "metadata",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "metadata",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update name for enabled client",
|
||||
update: "name",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Name: "updated name",
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update name for disabled client",
|
||||
update: "name",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Name: "updated name",
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update name and metadata for enabled client",
|
||||
update: "both",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Name: "updated name and metadata",
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "name and metadata",
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update name and metadata for a disabled client",
|
||||
update: "both",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Name: "updated name and metadata",
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "name and metadata",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update metadata for invalid client",
|
||||
update: "metadata",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "metadata",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update name for invalid client",
|
||||
update: "name",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Name: "updated name",
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update name and metadata for invalid client",
|
||||
update: "both",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Name: "updated name and metadata",
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "name and metadata",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range ucases {
|
||||
expected, err := repo.Update(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
if tc.client.Name != "" {
|
||||
assert.Equal(t, expected.Name, tc.client.Name, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, expected.Name, tc.client.Name))
|
||||
}
|
||||
if tc.client.Metadata != nil {
|
||||
assert.Equal(t, expected.Metadata, tc.client.Metadata, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, expected.Metadata, tc.client.Metadata))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsUpdateTags(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client-with-tags",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update-tags@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Tags: []string{"test", "enabled"},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
client2 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "disabled-client-with-tags",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update-tags@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Tags: []string{"test", "disabled"},
|
||||
Status: mfclients.DisabledStatus,
|
||||
}
|
||||
|
||||
clients1, err := repo.Save(context.Background(), client1)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client with tags: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client1.ID, client1.ID, fmt.Sprintf("add new client with tags: expected %v got %s\n", nil, err))
|
||||
}
|
||||
client1 = clients1
|
||||
clients2, err := repo.Save(context.Background(), client2)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client with tags: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client2.ID, client2.ID, fmt.Sprintf("add new disabled client with tags: expected %v got %s\n", nil, err))
|
||||
}
|
||||
client2 = clients2
|
||||
ucases := []struct {
|
||||
desc string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update tags for enabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Tags: []string{"updated"},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update tags for disabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Tags: []string{"updated"},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update tags for invalid client",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Tags: []string{"updated"},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range ucases {
|
||||
expected, err := repo.UpdateTags(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.client.Tags, expected.Tags, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Tags, expected.Tags))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsUpdateSecret(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
client2 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "disabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Status: mfclients.DisabledStatus,
|
||||
}
|
||||
|
||||
rClients1, err := repo.Save(context.Background(), client1)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client1.ID, rClients1.ID, fmt.Sprintf("add new client: expected %v got %s\n", nil, err))
|
||||
}
|
||||
rClients2, err := repo.Save(context.Background(), client2)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client2.ID, rClients2.ID, fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err))
|
||||
}
|
||||
|
||||
ucases := []struct {
|
||||
desc string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update secret for enabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update@example.com",
|
||||
Secret: "newpassword",
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update secret for disabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update@example.com",
|
||||
Secret: "newpassword",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update secret for invalid client",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client3-update@example.com",
|
||||
Secret: "newpassword",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range ucases {
|
||||
_, err := repo.UpdateSecret(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
c, err := repo.RetrieveByIdentity(context.Background(), tc.client.Credentials.Identity)
|
||||
require.Nil(t, err, fmt.Sprintf("retrieve client by id during update of secret unexpected error: %s", err))
|
||||
assert.Equal(t, tc.client.Credentials.Secret, c.Credentials.Secret, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Credentials.Secret, c.Credentials.Secret))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsUpdateIdentity(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
client2 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "disabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Status: mfclients.DisabledStatus,
|
||||
}
|
||||
|
||||
rClients1, err := repo.Save(context.Background(), client1)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client1.ID, rClients1.ID, fmt.Sprintf("add new client: expected %v got %s\n", nil, err))
|
||||
}
|
||||
rClients2, err := repo.Save(context.Background(), client2)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client2.ID, rClients2.ID, fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err))
|
||||
}
|
||||
|
||||
ucases := []struct {
|
||||
desc string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update identity for enabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-updated@example.com",
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update identity for disabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-updated@example.com",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update identity for invalid client",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client3-updated@example.com",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range ucases {
|
||||
expected, err := repo.UpdateIdentity(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.client.Credentials.Identity, expected.Credentials.Identity, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Credentials.Identity, expected.Credentials.Identity))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsUpdateOwner(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client-with-owner",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update-owner@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Owner: testsutil.GenerateUUID(t, idProvider),
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
client2 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "disabled-client-with-owner",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update-owner@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Owner: testsutil.GenerateUUID(t, idProvider),
|
||||
Status: mfclients.DisabledStatus,
|
||||
}
|
||||
|
||||
clients1, err := repo.Save(context.Background(), client1)
|
||||
client1 = clients1
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client with owner: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client1.ID, client1.ID, fmt.Sprintf("add new client with owner: expected %v got %s\n", nil, err))
|
||||
}
|
||||
clients2, err := repo.Save(context.Background(), client2)
|
||||
client2 = clients2
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client with owner: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client2.ID, client2.ID, fmt.Sprintf("add new disabled client with owner: expected %v got %s\n", nil, err))
|
||||
}
|
||||
ucases := []struct {
|
||||
desc string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update owner for enabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Owner: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update owner for disabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Owner: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update owner for invalid client",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Owner: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range ucases {
|
||||
expected, err := repo.UpdateOwner(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.client.Owner, expected.Owner, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Owner, expected.Owner))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsChangeStatus(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
clients1, err := repo.Save(context.Background(), client1)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client: expected %v got %s\n", nil, err))
|
||||
client1 = clients1
|
||||
|
||||
ucases := []struct {
|
||||
desc string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "change client status for an enabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Status: 0,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "change client status for a disabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Status: 1,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "change client status for non-existing client",
|
||||
client: mfclients.Client{
|
||||
ID: "invalid",
|
||||
Status: 2,
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range ucases {
|
||||
expected, err := repo.ChangeStatus(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.client.Status, expected.Status, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.client.Status, expected.Status))
|
||||
}
|
||||
}
|
||||
}
|
5
pkg/clients/postgres/doc.go
Normal file
5
pkg/clients/postgres/doc.go
Normal file
@ -0,0 +1,5 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package postgres contains the database implementation of clients repository layer.
|
||||
package postgres
|
@ -14,7 +14,7 @@ import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
pgClient "github.com/mainflux/mainflux/internal/clients/postgres"
|
||||
"github.com/mainflux/mainflux/internal/postgres"
|
||||
gpostgres "github.com/mainflux/mainflux/things/postgres"
|
||||
upostgres "github.com/mainflux/mainflux/users/postgres"
|
||||
dockertest "github.com/ory/dockertest/v3"
|
||||
"github.com/ory/dockertest/v3/docker"
|
||||
"go.opentelemetry.io/otel"
|
||||
@ -75,7 +75,8 @@ func TestMain(m *testing.M) {
|
||||
SSLKey: "",
|
||||
SSLRootCert: "",
|
||||
}
|
||||
if db, err = pgClient.SetupDB(dbConfig, *gpostgres.Migration()); err != nil {
|
||||
|
||||
if db, err = pgClient.SetupDB(dbConfig, *upostgres.Migration()); err != nil {
|
||||
log.Fatalf("Could not setup test DB connection: %s", err)
|
||||
}
|
||||
|
@ -1,6 +1,3 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
@ -11,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/mainflux/mainflux/internal/postgres"
|
||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
@ -23,9 +21,9 @@ type groupRepository struct {
|
||||
db postgres.Database
|
||||
}
|
||||
|
||||
// NewRepository instantiates a PostgreSQL implementation of group
|
||||
// New instantiates a PostgreSQL implementation of group
|
||||
// repository.
|
||||
func NewRepository(db postgres.Database) mfgroups.Repository {
|
||||
func New(db postgres.Database) mfgroups.Repository {
|
||||
return &groupRepository{
|
||||
db: db,
|
||||
}
|
||||
@ -36,7 +34,6 @@ func (repo groupRepository) Save(ctx context.Context, g mfgroups.Group) (mfgroup
|
||||
q := `INSERT INTO groups (name, description, id, owner_id, parent_id, metadata, created_at, status)
|
||||
VALUES (:name, :description, :id, :owner_id, :parent_id, :metadata, :created_at, :status)
|
||||
RETURNING id, name, description, owner_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, status;`
|
||||
|
||||
dbg, err := toDBGroup(g)
|
||||
if err != nil {
|
||||
return mfgroups.Group{}, err
|
||||
@ -56,89 +53,6 @@ func (repo groupRepository) Save(ctx context.Context, g mfgroups.Group) (mfgroup
|
||||
return toGroup(dbg)
|
||||
}
|
||||
|
||||
func (repo groupRepository) RetrieveByID(ctx context.Context, id string) (mfgroups.Group, error) {
|
||||
q := `SELECT id, name, owner_id, COALESCE(parent_id, '') AS parent_id, description, metadata, created_at, updated_at, updated_by, status FROM groups
|
||||
WHERE id = :id`
|
||||
|
||||
dbg := dbGroup{
|
||||
ID: id,
|
||||
}
|
||||
|
||||
row, err := repo.db.NamedQueryContext(ctx, q, dbg)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return mfgroups.Group{}, errors.Wrap(errors.ErrNotFound, err)
|
||||
}
|
||||
return mfgroups.Group{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
defer row.Close()
|
||||
row.Next()
|
||||
dbg = dbGroup{}
|
||||
if err := row.StructScan(&dbg); err != nil {
|
||||
return mfgroups.Group{}, errors.Wrap(errors.ErrNotFound, err)
|
||||
}
|
||||
|
||||
return toGroup(dbg)
|
||||
|
||||
}
|
||||
|
||||
func (repo groupRepository) RetrieveAll(ctx context.Context, gm mfgroups.GroupsPage) (mfgroups.GroupsPage, error) {
|
||||
var q string
|
||||
query, err := buildQuery(gm)
|
||||
if err != nil {
|
||||
return mfgroups.GroupsPage{}, err
|
||||
}
|
||||
|
||||
if gm.ID != "" {
|
||||
q = buildHierachy(gm)
|
||||
}
|
||||
if gm.ID == "" {
|
||||
q = `SELECT g.id, g.owner_id, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description,
|
||||
g.metadata, g.created_at, g.updated_at, g.updated_by, g.status FROM groups g`
|
||||
}
|
||||
q = fmt.Sprintf("%s %s ORDER BY g.updated_at LIMIT :limit OFFSET :offset;", q, query)
|
||||
|
||||
dbPage, err := toDBGroupPage(gm)
|
||||
if err != nil {
|
||||
return mfgroups.GroupsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
rows, err := repo.db.NamedQueryContext(ctx, q, dbPage)
|
||||
if err != nil {
|
||||
return mfgroups.GroupsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []mfgroups.Group
|
||||
for rows.Next() {
|
||||
dbg := dbGroup{}
|
||||
if err := rows.StructScan(&dbg); err != nil {
|
||||
return mfgroups.GroupsPage{}, err
|
||||
}
|
||||
group, err := toGroup(dbg)
|
||||
if err != nil {
|
||||
return mfgroups.GroupsPage{}, err
|
||||
}
|
||||
items = append(items, group)
|
||||
}
|
||||
|
||||
cq := "SELECT COUNT(*) FROM groups g"
|
||||
if query != "" {
|
||||
cq = fmt.Sprintf(" %s %s", cq, query)
|
||||
}
|
||||
|
||||
total, err := postgres.Total(ctx, repo.db, cq, dbPage)
|
||||
if err != nil {
|
||||
return mfgroups.GroupsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
|
||||
page := gm
|
||||
page.Groups = items
|
||||
page.Total = total
|
||||
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func (repo groupRepository) Memberships(ctx context.Context, clientID string, gm mfgroups.GroupsPage) (mfgroups.MembershipsPage, error) {
|
||||
var q string
|
||||
query, err := buildQuery(gm)
|
||||
@ -267,6 +181,80 @@ func (repo groupRepository) ChangeStatus(ctx context.Context, group mfgroups.Gro
|
||||
return toGroup(dbg)
|
||||
}
|
||||
|
||||
func (repo groupRepository) RetrieveByID(ctx context.Context, id string) (mfgroups.Group, error) {
|
||||
q := `SELECT id, name, owner_id, COALESCE(parent_id, '') AS parent_id, description, metadata, created_at, updated_at, updated_by, status FROM groups
|
||||
WHERE id = :id`
|
||||
|
||||
dbg := dbGroup{
|
||||
ID: id,
|
||||
}
|
||||
|
||||
row, err := repo.db.NamedQueryContext(ctx, q, dbg)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return mfgroups.Group{}, errors.Wrap(errors.ErrNotFound, err)
|
||||
}
|
||||
return mfgroups.Group{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
defer row.Close()
|
||||
row.Next()
|
||||
dbg = dbGroup{}
|
||||
if err := row.StructScan(&dbg); err != nil {
|
||||
return mfgroups.Group{}, errors.Wrap(errors.ErrNotFound, err)
|
||||
}
|
||||
|
||||
return toGroup(dbg)
|
||||
}
|
||||
|
||||
func (repo groupRepository) RetrieveAll(ctx context.Context, gm mfgroups.GroupsPage) (mfgroups.GroupsPage, error) {
|
||||
var q string
|
||||
query, err := buildQuery(gm)
|
||||
if err != nil {
|
||||
return mfgroups.GroupsPage{}, err
|
||||
}
|
||||
|
||||
if gm.ID != "" {
|
||||
q = buildHierachy(gm)
|
||||
}
|
||||
if gm.ID == "" {
|
||||
q = `SELECT DISTINCT g.id, g.owner_id, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description,
|
||||
g.metadata, g.created_at, g.updated_at, g.updated_by, g.status FROM groups g`
|
||||
}
|
||||
q = fmt.Sprintf("%s %s ORDER BY g.updated_at LIMIT :limit OFFSET :offset;", q, query)
|
||||
|
||||
dbPage, err := toDBGroupPage(gm)
|
||||
if err != nil {
|
||||
return mfgroups.GroupsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
rows, err := repo.db.NamedQueryContext(ctx, q, dbPage)
|
||||
if err != nil {
|
||||
return mfgroups.GroupsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
items, err := repo.processRows(rows)
|
||||
if err != nil {
|
||||
return mfgroups.GroupsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
|
||||
cq := "SELECT COUNT(*) FROM groups g"
|
||||
if query != "" {
|
||||
cq = fmt.Sprintf(" %s %s", cq, query)
|
||||
}
|
||||
|
||||
total, err := postgres.Total(ctx, repo.db, cq, dbPage)
|
||||
if err != nil {
|
||||
return mfgroups.GroupsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
|
||||
page := gm
|
||||
page.Groups = items
|
||||
page.Total = total
|
||||
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func buildHierachy(gm mfgroups.GroupsPage) string {
|
||||
query := ""
|
||||
switch {
|
||||
@ -446,3 +434,19 @@ type dbGroupPage struct {
|
||||
Action string `db:"action"`
|
||||
Status mfclients.Status `db:"status"`
|
||||
}
|
||||
|
||||
func (gr groupRepository) processRows(rows *sqlx.Rows) ([]mfgroups.Group, error) {
|
||||
var items []mfgroups.Group
|
||||
for rows.Next() {
|
||||
dbg := dbGroup{}
|
||||
if err := rows.StructScan(&dbg); err != nil {
|
||||
return items, err
|
||||
}
|
||||
group, err := toGroup(dbg)
|
||||
if err != nil {
|
||||
return items, err
|
||||
}
|
||||
items = append(items, group)
|
||||
}
|
||||
return items, nil
|
||||
}
|
@ -14,9 +14,9 @@ import (
|
||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
||||
gpostgres "github.com/mainflux/mainflux/pkg/groups/postgres"
|
||||
"github.com/mainflux/mainflux/pkg/uuid"
|
||||
cpostgres "github.com/mainflux/mainflux/users/clients/postgres"
|
||||
gpostgres "github.com/mainflux/mainflux/users/groups/postgres"
|
||||
"github.com/mainflux/mainflux/users/policies"
|
||||
ppostgres "github.com/mainflux/mainflux/users/policies/postgres"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -45,7 +45,7 @@ var (
|
||||
|
||||
func TestGroupSave(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
groupRepo := gpostgres.NewRepository(database)
|
||||
groupRepo := gpostgres.New(database)
|
||||
|
||||
usrID := testsutil.GenerateUUID(t, idProvider)
|
||||
grpID := testsutil.GenerateUUID(t, idProvider)
|
||||
@ -173,7 +173,7 @@ func TestGroupSave(t *testing.T) {
|
||||
|
||||
func TestGroupRetrieveByID(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
groupRepo := gpostgres.NewRepository(database)
|
||||
groupRepo := gpostgres.New(database)
|
||||
|
||||
uid := testsutil.GenerateUUID(t, idProvider)
|
||||
group1 := mfgroups.Group{
|
||||
@ -221,7 +221,7 @@ func TestGroupRetrieveByID(t *testing.T) {
|
||||
|
||||
func TestGroupRetrieveAll(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
groupRepo := gpostgres.NewRepository(database)
|
||||
groupRepo := gpostgres.New(database)
|
||||
|
||||
var nGroups = uint64(200)
|
||||
var ownerID = testsutil.GenerateUUID(t, idProvider)
|
||||
@ -339,7 +339,7 @@ func TestGroupRetrieveAll(t *testing.T) {
|
||||
|
||||
func TestGroupUpdate(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
groupRepo := gpostgres.NewRepository(database)
|
||||
groupRepo := gpostgres.New(database)
|
||||
|
||||
uid := testsutil.GenerateUUID(t, idProvider)
|
||||
|
||||
@ -464,7 +464,7 @@ func TestGroupUpdate(t *testing.T) {
|
||||
func TestClientsMemberships(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
crepo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
grepo := gpostgres.New(database)
|
||||
prepo := ppostgres.NewRepository(database)
|
||||
|
||||
clientA := mfclients.Client{
|
||||
@ -535,7 +535,7 @@ func TestClientsMemberships(t *testing.T) {
|
||||
|
||||
func TestGroupChangeStatus(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := gpostgres.NewRepository(database)
|
||||
repo := gpostgres.New(database)
|
||||
|
||||
group1 := mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
@ -6,29 +6,38 @@ package postgres
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgtype" // required for SQL access
|
||||
// required for SQL access.
|
||||
|
||||
"github.com/mainflux/mainflux/internal/postgres"
|
||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||
pgclients "github.com/mainflux/mainflux/pkg/clients/postgres"
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
"github.com/mainflux/mainflux/pkg/groups"
|
||||
)
|
||||
|
||||
var _ mfclients.Repository = (*clientRepo)(nil)
|
||||
|
||||
type clientRepo struct {
|
||||
db postgres.Database
|
||||
pgclients.ClientRepository
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
mfclients.Repository
|
||||
|
||||
// Save persists the client account. A non-nil error is returned to indicate
|
||||
// operation failure.
|
||||
Save(ctx context.Context, client ...mfclients.Client) ([]mfclients.Client, error)
|
||||
|
||||
// RetrieveBySecret retrieves a client based on the secret (key).
|
||||
RetrieveBySecret(ctx context.Context, key string) (mfclients.Client, error)
|
||||
}
|
||||
|
||||
// NewRepository instantiates a PostgreSQL
|
||||
// implementation of Clients repository.
|
||||
func NewRepository(db postgres.Database) mfclients.Repository {
|
||||
func NewRepository(db postgres.Database) Repository {
|
||||
return &clientRepo{
|
||||
db: db,
|
||||
ClientRepository: pgclients.ClientRepository{DB: db},
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,52 +47,48 @@ func (clientRepo) RetrieveByIdentity(ctx context.Context, identity string) (mfcl
|
||||
}
|
||||
|
||||
func (repo clientRepo) Save(ctx context.Context, cs ...mfclients.Client) ([]mfclients.Client, error) {
|
||||
tx, err := repo.db.BeginTxx(ctx, nil)
|
||||
tx, err := repo.ClientRepository.DB.BeginTxx(ctx, nil)
|
||||
if err != nil {
|
||||
return []mfclients.Client{}, errors.Wrap(errors.ErrCreateEntity, err)
|
||||
}
|
||||
var clients []mfclients.Client
|
||||
|
||||
for _, cli := range cs {
|
||||
q := `INSERT INTO clients (id, name, tags, owner_id, identity, secret, metadata, created_at, updated_at, updated_by, status)
|
||||
VALUES (:id, :name, :tags, :owner_id, :identity, :secret, :metadata, :created_at, :updated_at, :updated_by, :status)
|
||||
RETURNING id, name, tags, identity, secret, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`
|
||||
|
||||
dbcli, err := toDBClient(cli)
|
||||
dbcli, err := pgclients.ToDBClient(cli)
|
||||
if err != nil {
|
||||
return []mfclients.Client{}, errors.Wrap(errors.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
if _, err := tx.NamedExecContext(ctx, q, dbcli); err != nil {
|
||||
row, err := repo.ClientRepository.DB.NamedQueryContext(ctx, q, dbcli)
|
||||
if err != nil {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
return []mfclients.Client{}, postgres.HandleError(err, errors.ErrCreateEntity)
|
||||
}
|
||||
return []mfclients.Client{}, errors.Wrap(errors.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
defer row.Close()
|
||||
row.Next()
|
||||
dbcli = pgclients.DBClient{}
|
||||
if err := row.StructScan(&dbcli); err != nil {
|
||||
return []mfclients.Client{}, err
|
||||
}
|
||||
|
||||
client, err := pgclients.ToClient(dbcli)
|
||||
if err != nil {
|
||||
return []mfclients.Client{}, err
|
||||
}
|
||||
clients = append(clients, client)
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return []mfclients.Client{}, errors.Wrap(errors.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
func (repo clientRepo) RetrieveByID(ctx context.Context, id string) (mfclients.Client, error) {
|
||||
q := `SELECT id, name, tags, COALESCE(owner_id, '') AS owner_id, identity, secret, metadata, created_at, updated_at, updated_by, status
|
||||
FROM clients
|
||||
WHERE id = $1`
|
||||
|
||||
dbc := dbClient{
|
||||
ID: id,
|
||||
}
|
||||
|
||||
if err := repo.db.QueryRowxContext(ctx, q, id).StructScan(&dbc); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrNotFound, err)
|
||||
|
||||
}
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
return toClient(dbc)
|
||||
return clients, nil
|
||||
}
|
||||
|
||||
func (repo clientRepo) RetrieveBySecret(ctx context.Context, key string) (mfclients.Client, error) {
|
||||
@ -91,11 +96,11 @@ func (repo clientRepo) RetrieveBySecret(ctx context.Context, key string) (mfclie
|
||||
FROM clients
|
||||
WHERE secret = $1 AND status = %d`, mfclients.EnabledStatus)
|
||||
|
||||
dbc := dbClient{
|
||||
dbc := pgclients.DBClient{
|
||||
Secret: key,
|
||||
}
|
||||
|
||||
if err := repo.db.QueryRowxContext(ctx, q, key).StructScan(&dbc); err != nil {
|
||||
if err := repo.DB.QueryRowxContext(ctx, q, key).StructScan(&dbc); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrNotFound, err)
|
||||
|
||||
@ -103,371 +108,5 @@ func (repo clientRepo) RetrieveBySecret(ctx context.Context, key string) (mfclie
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
return toClient(dbc)
|
||||
}
|
||||
|
||||
func (repo clientRepo) RetrieveAll(ctx context.Context, pm mfclients.Page) (mfclients.ClientsPage, error) {
|
||||
query, err := pageQuery(pm)
|
||||
if err != nil {
|
||||
return mfclients.ClientsPage{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.secret, c.metadata, COALESCE(c.owner_id, '') AS owner_id, c.status, c.created_at
|
||||
FROM clients c %s ORDER BY c.created_at LIMIT :limit OFFSET :offset;`, query)
|
||||
|
||||
dbPage, err := toDBClientsPage(pm)
|
||||
if err != nil {
|
||||
return mfclients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
rows, err := repo.db.NamedQueryContext(ctx, q, dbPage)
|
||||
if err != nil {
|
||||
return mfclients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []mfclients.Client
|
||||
for rows.Next() {
|
||||
dbc := dbClient{}
|
||||
if err := rows.StructScan(&dbc); err != nil {
|
||||
return mfclients.ClientsPage{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
c, err := toClient(dbc)
|
||||
if err != nil {
|
||||
return mfclients.ClientsPage{}, err
|
||||
}
|
||||
|
||||
items = append(items, c)
|
||||
}
|
||||
cq := fmt.Sprintf(`SELECT COUNT(*) FROM clients c %s;`, query)
|
||||
|
||||
total, err := postgres.Total(ctx, repo.db, cq, dbPage)
|
||||
if err != nil {
|
||||
return mfclients.ClientsPage{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
page := mfclients.ClientsPage{
|
||||
Clients: items,
|
||||
Page: mfclients.Page{
|
||||
Total: total,
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
},
|
||||
}
|
||||
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func (repo clientRepo) Members(ctx context.Context, groupID string, pm mfclients.Page) (mfclients.MembersPage, error) {
|
||||
emq, err := pageQuery(pm)
|
||||
if err != nil {
|
||||
return mfclients.MembersPage{}, err
|
||||
}
|
||||
|
||||
aq := ""
|
||||
// If not admin, the client needs to have a g_list action on the group or they are the owner.
|
||||
if pm.Subject != "" {
|
||||
aq = `AND (EXISTS (SELECT 1 FROM policies p WHERE p.subject = :subject AND :action=ANY(actions))
|
||||
OR EXISTS (SELECT 1 FROM groups g WHERE g.owner_id = :subject AND g.id = :group_id))`
|
||||
}
|
||||
|
||||
q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.metadata, c.identity, c.secret, c.status, c.created_at FROM clients c
|
||||
INNER JOIN policies ON c.id=policies.subject %s AND policies.object = :group_id %s ORDER BY c.created_at LIMIT :limit OFFSET :offset;`, emq, aq)
|
||||
dbPage, err := toDBClientsPage(pm)
|
||||
if err != nil {
|
||||
return mfclients.MembersPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
dbPage.GroupID = groupID
|
||||
rows, err := repo.db.NamedQueryContext(ctx, q, dbPage)
|
||||
if err != nil {
|
||||
return mfclients.MembersPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembers, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []mfclients.Client
|
||||
for rows.Next() {
|
||||
dbc := dbClient{}
|
||||
if err := rows.StructScan(&dbc); err != nil {
|
||||
return mfclients.MembersPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembers, err)
|
||||
}
|
||||
|
||||
c, err := toClient(dbc)
|
||||
if err != nil {
|
||||
return mfclients.MembersPage{}, err
|
||||
}
|
||||
|
||||
items = append(items, c)
|
||||
}
|
||||
cq := fmt.Sprintf(`SELECT COUNT(*) FROM clients c INNER JOIN policies ON c.id=policies.subject %s AND policies.object = :group_id;`, emq)
|
||||
|
||||
total, err := postgres.Total(ctx, repo.db, cq, dbPage)
|
||||
if err != nil {
|
||||
return mfclients.MembersPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembers, err)
|
||||
}
|
||||
|
||||
page := mfclients.MembersPage{
|
||||
Members: items,
|
||||
Page: mfclients.Page{
|
||||
Total: total,
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
},
|
||||
}
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func (repo clientRepo) Update(ctx context.Context, client mfclients.Client) (mfclients.Client, error) {
|
||||
var query []string
|
||||
var upq string
|
||||
if client.Name != "" {
|
||||
query = append(query, "name = :name,")
|
||||
}
|
||||
if client.Metadata != nil {
|
||||
query = append(query, "metadata = :metadata,")
|
||||
}
|
||||
if len(query) > 0 {
|
||||
upq = strings.Join(query, " ")
|
||||
}
|
||||
client.Status = mfclients.EnabledStatus
|
||||
q := fmt.Sprintf(`UPDATE clients SET %s updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id AND status = :status
|
||||
RETURNING id, name, tags, identity, secret, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`,
|
||||
upq)
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo clientRepo) UpdateTags(ctx context.Context, client mfclients.Client) (mfclients.Client, error) {
|
||||
client.Status = mfclients.EnabledStatus
|
||||
q := `UPDATE clients SET tags = :tags, updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id AND status = :status
|
||||
RETURNING id, name, tags, identity, secret, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo clientRepo) UpdateIdentity(ctx context.Context, client mfclients.Client) (mfclients.Client, error) {
|
||||
client.Status = mfclients.EnabledStatus
|
||||
q := `UPDATE clients SET identity = :identity, updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id AND status = :status
|
||||
RETURNING id, name, tags, identity, secret, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo clientRepo) UpdateSecret(ctx context.Context, client mfclients.Client) (mfclients.Client, error) {
|
||||
client.Status = mfclients.EnabledStatus
|
||||
q := `UPDATE clients SET secret = :secret, updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id AND status = :status
|
||||
RETURNING id, name, tags, identity, secret, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo clientRepo) UpdateOwner(ctx context.Context, client mfclients.Client) (mfclients.Client, error) {
|
||||
client.Status = mfclients.EnabledStatus
|
||||
q := `UPDATE clients SET owner_id = :owner_id, updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id AND status = :status
|
||||
RETURNING id, name, tags, identity, secret, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo clientRepo) ChangeStatus(ctx context.Context, client mfclients.Client) (mfclients.Client, error) {
|
||||
q := `UPDATE clients SET status = :status WHERE id = :id
|
||||
RETURNING id, name, tags, identity, secret, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
// generic update function.
|
||||
func (repo clientRepo) update(ctx context.Context, client mfclients.Client, query string) (mfclients.Client, error) {
|
||||
dbc, err := toDBClient(client)
|
||||
if err != nil {
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
row, err := repo.db.NamedQueryContext(ctx, query, dbc)
|
||||
if err != nil {
|
||||
return mfclients.Client{}, postgres.HandleError(err, errors.ErrUpdateEntity)
|
||||
}
|
||||
|
||||
defer row.Close()
|
||||
if ok := row.Next(); !ok {
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrNotFound, row.Err())
|
||||
}
|
||||
dbc = dbClient{}
|
||||
if err := row.StructScan(&dbc); err != nil {
|
||||
return mfclients.Client{}, err
|
||||
}
|
||||
|
||||
return toClient(dbc)
|
||||
}
|
||||
|
||||
type dbClient struct {
|
||||
ID string `db:"id"`
|
||||
Name string `db:"name,omitempty"`
|
||||
Tags pgtype.TextArray `db:"tags,omitempty"`
|
||||
Identity string `db:"identity"`
|
||||
Owner string `db:"owner_id,omitempty"` // nullable
|
||||
Secret string `db:"secret"`
|
||||
Metadata []byte `db:"metadata,omitempty"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt sql.NullTime `db:"updated_at,omitempty"`
|
||||
UpdatedBy *string `db:"updated_by,omitempty"`
|
||||
Groups []groups.Group `db:"groups"`
|
||||
Status mfclients.Status `db:"status"`
|
||||
}
|
||||
|
||||
func toDBClient(c mfclients.Client) (dbClient, error) {
|
||||
data := []byte("{}")
|
||||
if len(c.Metadata) > 0 {
|
||||
b, err := json.Marshal(c.Metadata)
|
||||
if err != nil {
|
||||
return dbClient{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
data = b
|
||||
}
|
||||
var tags pgtype.TextArray
|
||||
if err := tags.Set(c.Tags); err != nil {
|
||||
return dbClient{}, err
|
||||
}
|
||||
var updatedBy *string
|
||||
if c.UpdatedBy != "" {
|
||||
updatedBy = &c.UpdatedBy
|
||||
}
|
||||
var updatedAt sql.NullTime
|
||||
if !c.UpdatedAt.IsZero() {
|
||||
updatedAt = sql.NullTime{Time: c.UpdatedAt, Valid: true}
|
||||
}
|
||||
|
||||
return dbClient{
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
Tags: tags,
|
||||
Owner: c.Owner,
|
||||
Identity: c.Credentials.Identity,
|
||||
Secret: c.Credentials.Secret,
|
||||
Metadata: data,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: updatedAt,
|
||||
UpdatedBy: updatedBy,
|
||||
Status: c.Status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toClient(c dbClient) (mfclients.Client, error) {
|
||||
var metadata mfclients.Metadata
|
||||
if c.Metadata != nil {
|
||||
if err := json.Unmarshal([]byte(c.Metadata), &metadata); err != nil {
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
}
|
||||
var tags []string
|
||||
for _, e := range c.Tags.Elements {
|
||||
tags = append(tags, e.String)
|
||||
}
|
||||
var updatedBy string
|
||||
if c.UpdatedBy != nil {
|
||||
updatedBy = *c.UpdatedBy
|
||||
}
|
||||
var updatedAt time.Time
|
||||
if c.UpdatedAt.Valid {
|
||||
updatedAt = c.UpdatedAt.Time
|
||||
}
|
||||
|
||||
return mfclients.Client{
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
Tags: tags,
|
||||
Owner: c.Owner,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: c.Identity,
|
||||
Secret: c.Secret,
|
||||
},
|
||||
Metadata: metadata,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: updatedAt,
|
||||
UpdatedBy: updatedBy,
|
||||
Status: c.Status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func pageQuery(pm mfclients.Page) (string, error) {
|
||||
mq, _, err := postgres.CreateMetadataQuery("", pm.Metadata)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
var query []string
|
||||
var emq string
|
||||
if mq != "" {
|
||||
query = append(query, mq)
|
||||
}
|
||||
if len(pm.IDs) != 0 {
|
||||
query = append(query, fmt.Sprintf("id IN ('%s')", strings.Join(pm.IDs, "','")))
|
||||
}
|
||||
if pm.Name != "" {
|
||||
query = append(query, "c.name = :name")
|
||||
}
|
||||
if pm.Tag != "" {
|
||||
query = append(query, ":tag = ANY(c.tags)")
|
||||
}
|
||||
if pm.Status != mfclients.AllStatus {
|
||||
query = append(query, "c.status = :status")
|
||||
}
|
||||
// For listing clients that the specified client owns but not sharedby
|
||||
if pm.Owner != "" && pm.SharedBy == "" {
|
||||
query = append(query, "c.owner_id = :owner_id")
|
||||
}
|
||||
|
||||
// For listing clients that the specified client owns and that are shared with the specified client
|
||||
if pm.Owner != "" && pm.SharedBy != "" {
|
||||
query = append(query, "(c.owner_id = :owner_id OR c.id IN (SELECT subject FROM policies WHERE object IN (SELECT object FROM policies WHERE subject = :shared_by AND :action=ANY(actions))))")
|
||||
}
|
||||
// For listing clients that the specified client is shared with
|
||||
if pm.SharedBy != "" && pm.Owner == "" {
|
||||
query = append(query, "c.owner_id != :shared_by AND (c.id IN (SELECT subject FROM policies WHERE object IN (SELECT object FROM policies WHERE subject = :shared_by AND :action=ANY(actions))))")
|
||||
}
|
||||
if len(query) > 0 {
|
||||
emq = fmt.Sprintf("WHERE %s", strings.Join(query, " AND "))
|
||||
}
|
||||
return emq, nil
|
||||
|
||||
}
|
||||
|
||||
func toDBClientsPage(pm mfclients.Page) (dbClientsPage, error) {
|
||||
_, data, err := postgres.CreateMetadataQuery("", pm.Metadata)
|
||||
if err != nil {
|
||||
return dbClientsPage{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
return dbClientsPage{
|
||||
Name: pm.Name,
|
||||
Metadata: data,
|
||||
Owner: pm.Owner,
|
||||
Total: pm.Total,
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
Status: pm.Status,
|
||||
Tag: pm.Tag,
|
||||
Identity: pm.Identity,
|
||||
SharedBy: pm.SharedBy,
|
||||
Subject: pm.Subject,
|
||||
Action: pm.Action,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type dbClientsPage struct {
|
||||
GroupID string `db:"group_id"`
|
||||
Name string `db:"name"`
|
||||
Owner string `db:"owner_id"`
|
||||
Identity string `db:"identity"`
|
||||
Metadata []byte `db:"metadata"`
|
||||
Tag string `db:"tag"`
|
||||
Status mfclients.Status `db:"status"`
|
||||
Total uint64 `db:"total"`
|
||||
Limit uint64 `db:"limit"`
|
||||
Offset uint64 `db:"offset"`
|
||||
SharedBy string `db:"shared_by"`
|
||||
Subject string `db:"subject"`
|
||||
Action string `db:"action"`
|
||||
return pgclients.ToClient(dbc)
|
||||
}
|
||||
|
@ -12,14 +12,9 @@ import (
|
||||
"github.com/mainflux/mainflux/internal/testsutil"
|
||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
||||
"github.com/mainflux/mainflux/pkg/uuid"
|
||||
cpostgres "github.com/mainflux/mainflux/things/clients/postgres"
|
||||
gpostgres "github.com/mainflux/mainflux/things/groups/postgres"
|
||||
"github.com/mainflux/mainflux/things/policies"
|
||||
ppostgres "github.com/mainflux/mainflux/things/policies/postgres"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const maxNameSize = 1024
|
||||
@ -29,8 +24,6 @@ var (
|
||||
invalidName = strings.Repeat("m", maxNameSize+10)
|
||||
clientIdentity = "client-identity@example.com"
|
||||
clientName = "client name"
|
||||
wrongName = "wrong-name"
|
||||
wrongID = "wrong-id"
|
||||
)
|
||||
|
||||
func TestClientsSave(t *testing.T) {
|
||||
@ -163,830 +156,3 @@ func TestClientsSave(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsRetrieveByID(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: clientName,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: clientIdentity,
|
||||
Secret: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
_, err := repo.Save(context.Background(), client)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
|
||||
cases := map[string]struct {
|
||||
ID string
|
||||
err error
|
||||
}{
|
||||
"retrieve existing client": {client.ID, nil},
|
||||
"retrieve non-existing client": {wrongID, errors.ErrNotFound},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
cli, err := repo.RetrieveByID(context.Background(), tc.ID)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client.ID, cli.ID, fmt.Sprintf("retrieve client by ID : client ID : expected %s got %s\n", client.ID, cli.ID))
|
||||
assert.Equal(t, client.Name, cli.Name, fmt.Sprintf("retrieve client by ID : client Name : expected %s got %s\n", client.Name, cli.Name))
|
||||
assert.Equal(t, client.Credentials.Identity, cli.Credentials.Identity, fmt.Sprintf("retrieve client by ID : client Identity : expected %s got %s\n", client.Credentials.Identity, cli.Credentials.Identity))
|
||||
assert.Equal(t, client.Status, cli.Status, fmt.Sprintf("retrieve client by ID : client Status : expected %d got %d\n", client.Status, cli.Status))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsRetrieveAll(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
prepo := ppostgres.NewRepository(database)
|
||||
|
||||
var nClients = uint64(200)
|
||||
var ownerID = testsutil.GenerateUUID(t, idProvider)
|
||||
|
||||
meta := mfclients.Metadata{
|
||||
"admin": "true",
|
||||
}
|
||||
wrongMeta := mfclients.Metadata{
|
||||
"admin": "false",
|
||||
}
|
||||
var expectedClients = []mfclients.Client{}
|
||||
|
||||
var sharedGroup = mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "shared-group",
|
||||
}
|
||||
_, err := grepo.Save(context.Background(), sharedGroup)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
|
||||
for i := uint64(0); i < nClients; i++ {
|
||||
identity := fmt.Sprintf("TestRetrieveAll%d@example.com", i)
|
||||
client := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: identity,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: identity,
|
||||
Secret: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
Metadata: mfclients.Metadata{},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
if i%10 == 0 {
|
||||
client.Owner = ownerID
|
||||
client.Metadata = meta
|
||||
client.Tags = []string{"Test"}
|
||||
}
|
||||
if i%50 == 0 {
|
||||
client.Status = mfclients.DisabledStatus
|
||||
}
|
||||
_, err := repo.Save(context.Background(), client)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
expectedClients = append(expectedClients, client)
|
||||
var policy = policies.Policy{
|
||||
Subject: client.ID,
|
||||
Object: sharedGroup.ID,
|
||||
Actions: []string{"c_list"},
|
||||
}
|
||||
_, err = prepo.Save(context.Background(), policy)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
size uint64
|
||||
pm mfclients.Page
|
||||
response []mfclients.Client
|
||||
}{
|
||||
"retrieve all clients empty page": {
|
||||
pm: mfclients.Page{},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients,
|
||||
size: 200,
|
||||
},
|
||||
"retrieve all clients with limit": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: 50,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients[0:50],
|
||||
size: 50,
|
||||
},
|
||||
"retrieve all clients with offset": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 50,
|
||||
Limit: nClients,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients[50:200],
|
||||
size: 150,
|
||||
},
|
||||
"retrieve all clients with limit and offset": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 50,
|
||||
Limit: 50,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients[50:100],
|
||||
size: 50,
|
||||
},
|
||||
"retrieve all clients with limit and offset not full": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 170,
|
||||
Limit: 50,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients[170:200],
|
||||
size: 30,
|
||||
},
|
||||
"retrieve all clients by metadata": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Metadata: meta,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60],
|
||||
expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130],
|
||||
expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190],
|
||||
},
|
||||
size: 20,
|
||||
},
|
||||
"retrieve clients by wrong metadata": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Metadata: wrongMeta,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients by name": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Name: "TestRetrieveAll3@example.com",
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[3]},
|
||||
size: 1,
|
||||
},
|
||||
"retrieve clients by wrong name": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Name: wrongName,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients by owner": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Owner: ownerID,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60],
|
||||
expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130],
|
||||
expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190],
|
||||
},
|
||||
size: 20,
|
||||
},
|
||||
"retrieve clients by wrong owner": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Owner: wrongID,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients shared by": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
SharedBy: expectedClients[0].ID,
|
||||
Action: "c_list",
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients,
|
||||
size: nClients,
|
||||
},
|
||||
"retrieve all clients shared by and owned by": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
SharedBy: ownerID,
|
||||
Owner: ownerID,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60],
|
||||
expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130],
|
||||
expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190],
|
||||
},
|
||||
size: 20,
|
||||
},
|
||||
"retrieve all clients by disabled status": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Status: mfclients.DisabledStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[0], expectedClients[50], expectedClients[100], expectedClients[150]},
|
||||
size: 4,
|
||||
},
|
||||
"retrieve all clients by combined status": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients,
|
||||
size: 200,
|
||||
},
|
||||
"retrieve clients by the wrong status": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Status: 10,
|
||||
},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients by tags": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Tag: "Test",
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60],
|
||||
expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130],
|
||||
expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190],
|
||||
},
|
||||
size: 20,
|
||||
},
|
||||
"retrieve clients by wrong tags": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Tag: "wrongTags",
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
}
|
||||
for desc, tc := range cases {
|
||||
page, err := repo.RetrieveAll(context.Background(), tc.pm)
|
||||
size := uint64(len(page.Clients))
|
||||
assert.ElementsMatch(t, page.Clients, tc.response, fmt.Sprintf("%s: expected %v got %v\n", desc, tc.response, page.Clients))
|
||||
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", desc, tc.size, size))
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %d\n", desc, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupsMembers(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
crepo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
prepo := ppostgres.NewRepository(database)
|
||||
|
||||
clientA := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "client-memberships",
|
||||
Credentials: mfclients.Credentials{
|
||||
Secret: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
Metadata: mfclients.Metadata{},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
clientB := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "client-memberships",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client-memberships2@example.com",
|
||||
Secret: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
Metadata: mfclients.Metadata{},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
group := mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "group-membership",
|
||||
Metadata: mfclients.Metadata{},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
policyA := policies.Policy{
|
||||
Subject: clientA.ID,
|
||||
Object: group.ID,
|
||||
Actions: []string{"g_list"},
|
||||
}
|
||||
policyB := policies.Policy{
|
||||
Subject: clientB.ID,
|
||||
Object: group.ID,
|
||||
Actions: []string{"g_list"},
|
||||
}
|
||||
|
||||
_, err := crepo.Save(context.Background(), clientA)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save client: expected %v got %s\n", nil, err))
|
||||
_, err = crepo.Save(context.Background(), clientB)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save client: expected %v got %s\n", nil, err))
|
||||
_, err = grepo.Save(context.Background(), group)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save group: expected %v got %s\n", nil, err))
|
||||
_, err = prepo.Save(context.Background(), policyA)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save policy: expected %v got %s\n", nil, err))
|
||||
_, err = prepo.Save(context.Background(), policyB)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save policy: expected %v got %s\n", nil, err))
|
||||
|
||||
cases := map[string]struct {
|
||||
ID string
|
||||
err error
|
||||
}{
|
||||
"retrieve members for existing group": {group.ID, nil},
|
||||
"retrieve members for non-existing group": {wrongID, nil},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
mp, err := crepo.Members(context.Background(), tc.ID, mfclients.Page{Total: 10, Offset: 0, Limit: 10, Status: mfclients.AllStatus, Subject: clientB.ID, Action: "g_list"})
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
if tc.ID == group.ID {
|
||||
assert.ElementsMatch(t, mp.Members, []mfclients.Client{clientA, clientB}, fmt.Sprintf("%s: expected %v got %v\n", desc, []mfclients.Client{clientA, clientB}, mp.Members))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsUpdateMetadata(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update@example.com",
|
||||
Secret: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
Metadata: mfclients.Metadata{
|
||||
"name": "enabled-client",
|
||||
},
|
||||
Tags: []string{"enabled", "tag1"},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
client2 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "disabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update@example.com",
|
||||
Secret: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
Metadata: mfclients.Metadata{
|
||||
"name": "disabled-client",
|
||||
},
|
||||
Tags: []string{"disabled", "tag1"},
|
||||
Status: mfclients.DisabledStatus,
|
||||
}
|
||||
|
||||
_, err := repo.Save(context.Background(), client1)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client with metadata: expected %v got %s\n", nil, err))
|
||||
_, err = repo.Save(context.Background(), client2)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err))
|
||||
|
||||
ucases := []struct {
|
||||
desc string
|
||||
update string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update metadata for enabled client",
|
||||
update: "metadata",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "metadata",
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update metadata for disabled client",
|
||||
update: "metadata",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "metadata",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update name for enabled client",
|
||||
update: "name",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Name: "updated name",
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update name for disabled client",
|
||||
update: "name",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Name: "updated name",
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update name and metadata for enabled client",
|
||||
update: "both",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Name: "updated name and metadata",
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "name and metadata",
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update name and metadata for a disabled client",
|
||||
update: "both",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Name: "updated name and metadata",
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "name and metadata",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update metadata for invalid client",
|
||||
update: "metadata",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "metadata",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update name for invalid client",
|
||||
update: "name",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Name: "updated name",
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update name and metadata for invalid client",
|
||||
update: "both",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Name: "updated name and metadata",
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "name and metadata",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range ucases {
|
||||
expected, err := repo.Update(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
if tc.client.Name != "" {
|
||||
assert.Equal(t, expected.Name, tc.client.Name, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, expected.Name, tc.client.Name))
|
||||
}
|
||||
if tc.client.Metadata != nil {
|
||||
assert.Equal(t, expected.Metadata, tc.client.Metadata, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, expected.Metadata, tc.client.Metadata))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsUpdateTags(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client-with-tags",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update-tags@example.com",
|
||||
Secret: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
Tags: []string{"test", "enabled"},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
client2 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "disabled-client-with-tags",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update-tags@example.com",
|
||||
Secret: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
Tags: []string{"test", "disabled"},
|
||||
Status: mfclients.DisabledStatus,
|
||||
}
|
||||
|
||||
_, err := repo.Save(context.Background(), client1)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client with tags: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client1.ID, client1.ID, fmt.Sprintf("add new client with tags: expected %v got %s\n", nil, err))
|
||||
}
|
||||
_, err = repo.Save(context.Background(), client2)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client with tags: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client2.ID, client2.ID, fmt.Sprintf("add new disabled client with tags: expected %v got %s\n", nil, err))
|
||||
}
|
||||
ucases := []struct {
|
||||
desc string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update tags for enabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Tags: []string{"updated"},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update tags for disabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Tags: []string{"updated"},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update tags for invalid client",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Tags: []string{"updated"},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range ucases {
|
||||
expected, err := repo.UpdateTags(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.client.Tags, expected.Tags, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Tags, expected.Tags))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsUpdateSecret(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update@example.com",
|
||||
Secret: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
client2 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "disabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update@example.com",
|
||||
Secret: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
Status: mfclients.DisabledStatus,
|
||||
}
|
||||
|
||||
rClient1, err := repo.Save(context.Background(), client1)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client1.ID, rClient1[0].ID, fmt.Sprintf("add new client: expected %v got %s\n", nil, err))
|
||||
}
|
||||
rClient2, err := repo.Save(context.Background(), client2)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client2.ID, rClient2[0].ID, fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err))
|
||||
}
|
||||
|
||||
ucases := []struct {
|
||||
desc string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update secret for enabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update@example.com",
|
||||
Secret: "newpassword",
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update secret for disabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update@example.com",
|
||||
Secret: "newpassword",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update secret for invalid client",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client3-update@example.com",
|
||||
Secret: "newpassword",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range ucases {
|
||||
_, err := repo.UpdateSecret(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
c, err := repo.RetrieveByID(context.Background(), tc.client.ID)
|
||||
require.Nil(t, err, fmt.Sprintf("retrieve client by id during update of secret unexpected error: %s", err))
|
||||
assert.Equal(t, tc.client.Credentials.Secret, c.Credentials.Secret, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Credentials.Secret, c.Credentials.Secret))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsUpdateOwner(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client-with-owner",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update-owner@example.com",
|
||||
Secret: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
Owner: testsutil.GenerateUUID(t, idProvider),
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
client2 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "disabled-client-with-owner",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update-owner@example.com",
|
||||
Secret: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
Owner: testsutil.GenerateUUID(t, idProvider),
|
||||
Status: mfclients.DisabledStatus,
|
||||
}
|
||||
|
||||
_, err := repo.Save(context.Background(), client1)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client with owner: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client1.ID, client1.ID, fmt.Sprintf("add new client with owner: expected %v got %s\n", nil, err))
|
||||
}
|
||||
_, err = repo.Save(context.Background(), client2)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client with owner: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client2.ID, client2.ID, fmt.Sprintf("add new disabled client with owner: expected %v got %s\n", nil, err))
|
||||
}
|
||||
ucases := []struct {
|
||||
desc string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update owner for enabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Owner: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update owner for disabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Owner: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update owner for invalid client",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Owner: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range ucases {
|
||||
expected, err := repo.UpdateOwner(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.client.Owner, expected.Owner, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Owner, expected.Owner))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsChangeStatus(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update@example.com",
|
||||
Secret: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
_, err := repo.Save(context.Background(), client1)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client: expected %v got %s\n", nil, err))
|
||||
|
||||
ucases := []struct {
|
||||
desc string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "change client status for an enabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Status: 0,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "change client status for a disabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Status: 1,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "change client status for non-existing client",
|
||||
client: mfclients.Client{
|
||||
ID: "invalid",
|
||||
Status: 2,
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range ucases {
|
||||
expected, err := repo.ChangeStatus(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.client.Status, expected.Status, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.client.Status, expected.Status))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
||||
"github.com/mainflux/mainflux/things/clients/postgres"
|
||||
tpolicies "github.com/mainflux/mainflux/things/policies"
|
||||
upolicies "github.com/mainflux/mainflux/users/policies"
|
||||
)
|
||||
@ -31,14 +32,14 @@ const (
|
||||
type service struct {
|
||||
uauth upolicies.AuthServiceClient
|
||||
policies tpolicies.Service
|
||||
clients mfclients.Repository
|
||||
clients postgres.Repository
|
||||
clientCache Cache
|
||||
idProvider mainflux.IDProvider
|
||||
grepo mfgroups.Repository
|
||||
}
|
||||
|
||||
// NewService returns a new Clients service implementation.
|
||||
func NewService(uauth upolicies.AuthServiceClient, policies tpolicies.Service, c mfclients.Repository, grepo mfgroups.Repository, tcache Cache, idp mainflux.IDProvider) Service {
|
||||
func NewService(uauth upolicies.AuthServiceClient, policies tpolicies.Service, c postgres.Repository, grepo mfgroups.Repository, tcache Cache, idp mainflux.IDProvider) Service {
|
||||
return service{
|
||||
uauth: uauth,
|
||||
policies: policies,
|
||||
@ -198,6 +199,7 @@ func (svc service) UpdateClientSecret(ctx context.Context, token, id, key string
|
||||
},
|
||||
UpdatedAt: time.Now(),
|
||||
UpdatedBy: userID,
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
return svc.clients.UpdateSecret(ctx, client)
|
||||
@ -217,6 +219,7 @@ func (svc service) UpdateClientOwner(ctx context.Context, token string, cli mfcl
|
||||
Owner: cli.Owner,
|
||||
UpdatedAt: time.Now(),
|
||||
UpdatedBy: userID,
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
return svc.clients.UpdateOwner(ctx, client)
|
||||
|
@ -1,443 +0,0 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/mainflux/mainflux/internal/postgres"
|
||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
||||
)
|
||||
|
||||
var _ mfgroups.Repository = (*grepo)(nil)
|
||||
|
||||
type grepo struct {
|
||||
db postgres.Database
|
||||
}
|
||||
|
||||
// NewRepository instantiates a PostgreSQL implementation of group
|
||||
// repository.
|
||||
func NewRepository(db postgres.Database) mfgroups.Repository {
|
||||
return &grepo{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - check parent group write access.
|
||||
func (repo grepo) Save(ctx context.Context, g mfgroups.Group) (mfgroups.Group, error) {
|
||||
q := `INSERT INTO groups (name, description, id, owner_id, metadata, created_at, updated_at, updated_by, status)
|
||||
VALUES (:name, :description, :id, :owner_id, :metadata, :created_at, :updated_at, :updated_by, :status)
|
||||
RETURNING id, name, description, owner_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, updated_at, updated_by, status;`
|
||||
if g.Parent != "" {
|
||||
q = `INSERT INTO groups (name, description, id, owner_id, parent_id, metadata, created_at, updated_at, updated_by, status)
|
||||
VALUES (:name, :description, :id, :owner_id, :parent_id, :metadata, :created_at, :updated_at, :updated_by, :status)
|
||||
RETURNING id, name, description, owner_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, updated_at, updated_by, status;`
|
||||
}
|
||||
dbg, err := toDBGroup(g)
|
||||
if err != nil {
|
||||
return mfgroups.Group{}, err
|
||||
}
|
||||
row, err := repo.db.NamedQueryContext(ctx, q, dbg)
|
||||
if err != nil {
|
||||
return mfgroups.Group{}, postgres.HandleError(err, errors.ErrCreateEntity)
|
||||
}
|
||||
|
||||
defer row.Close()
|
||||
row.Next()
|
||||
dbg = dbGroup{}
|
||||
if err := row.StructScan(&dbg); err != nil {
|
||||
return mfgroups.Group{}, err
|
||||
}
|
||||
|
||||
return toGroup(dbg)
|
||||
}
|
||||
|
||||
func (repo grepo) RetrieveByID(ctx context.Context, id string) (mfgroups.Group, error) {
|
||||
dbu := dbGroup{
|
||||
ID: id,
|
||||
}
|
||||
q := `SELECT id, name, owner_id, COALESCE(parent_id, '') AS parent_id, description, metadata, created_at, updated_at, updated_by, status FROM groups
|
||||
WHERE id = $1`
|
||||
if err := repo.db.QueryRowxContext(ctx, q, dbu.ID).StructScan(&dbu); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return mfgroups.Group{}, errors.Wrap(errors.ErrNotFound, err)
|
||||
|
||||
}
|
||||
return mfgroups.Group{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
return toGroup(dbu)
|
||||
}
|
||||
|
||||
func (repo grepo) RetrieveAll(ctx context.Context, gm mfgroups.GroupsPage) (mfgroups.GroupsPage, error) {
|
||||
var q string
|
||||
query, err := buildQuery(gm)
|
||||
if err != nil {
|
||||
return mfgroups.GroupsPage{}, err
|
||||
}
|
||||
|
||||
if gm.ID != "" {
|
||||
q = buildHierachy(gm)
|
||||
}
|
||||
if gm.ID == "" {
|
||||
q = `SELECT DISTINCT g.id, g.owner_id, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description,
|
||||
g.metadata, g.created_at, g.updated_at, g.updated_by, g.status FROM groups g`
|
||||
}
|
||||
q = fmt.Sprintf("%s %s ORDER BY g.updated_at LIMIT :limit OFFSET :offset;", q, query)
|
||||
|
||||
dbPage, err := toDBGroupPage(gm)
|
||||
if err != nil {
|
||||
return mfgroups.GroupsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
rows, err := repo.db.NamedQueryContext(ctx, q, dbPage)
|
||||
if err != nil {
|
||||
return mfgroups.GroupsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
items, err := repo.processRows(rows)
|
||||
if err != nil {
|
||||
return mfgroups.GroupsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
|
||||
cq := "SELECT COUNT(*) FROM groups g"
|
||||
if query != "" {
|
||||
cq = fmt.Sprintf(" %s %s", cq, query)
|
||||
}
|
||||
|
||||
total, err := postgres.Total(ctx, repo.db, cq, dbPage)
|
||||
if err != nil {
|
||||
return mfgroups.GroupsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
|
||||
page := gm
|
||||
page.Groups = items
|
||||
page.Total = total
|
||||
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func (repo grepo) Memberships(ctx context.Context, clientID string, gm mfgroups.GroupsPage) (mfgroups.MembershipsPage, error) {
|
||||
var q string
|
||||
query, err := buildQuery(gm)
|
||||
if err != nil {
|
||||
return mfgroups.MembershipsPage{}, err
|
||||
}
|
||||
if gm.ID != "" {
|
||||
q = buildHierachy(gm)
|
||||
}
|
||||
if gm.ID == "" {
|
||||
q = `SELECT g.id, g.owner_id, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description,
|
||||
g.metadata, g.created_at, g.updated_at, g.updated_by, g.status FROM groups g`
|
||||
}
|
||||
aq := ""
|
||||
// If not admin, the client needs to have a g_list action on the group or they are the owner.
|
||||
if gm.Subject != "" {
|
||||
aq = `AND policies.object IN (SELECT object FROM policies WHERE subject = :subject AND :action=ANY(actions)) OR g.owner_id = :subject`
|
||||
}
|
||||
q = fmt.Sprintf(`%s INNER JOIN policies ON g.id=policies.object %s AND policies.subject = :client_id %s
|
||||
ORDER BY g.updated_at LIMIT :limit OFFSET :offset;`, q, query, aq)
|
||||
|
||||
dbPage, err := toDBGroupPage(gm)
|
||||
if err != nil {
|
||||
return mfgroups.MembershipsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembership, err)
|
||||
}
|
||||
dbPage.ClientID = clientID
|
||||
rows, err := repo.db.NamedQueryContext(ctx, q, dbPage)
|
||||
if err != nil {
|
||||
return mfgroups.MembershipsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembership, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []mfgroups.Group
|
||||
for rows.Next() {
|
||||
dbg := dbGroup{}
|
||||
if err := rows.StructScan(&dbg); err != nil {
|
||||
return mfgroups.MembershipsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembership, err)
|
||||
}
|
||||
group, err := toGroup(dbg)
|
||||
if err != nil {
|
||||
return mfgroups.MembershipsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembership, err)
|
||||
}
|
||||
items = append(items, group)
|
||||
}
|
||||
|
||||
cq := fmt.Sprintf(`SELECT COUNT(*) FROM groups g INNER JOIN policies
|
||||
ON g.id=policies.object %s AND policies.subject = :client_id`, query)
|
||||
|
||||
total, err := postgres.Total(ctx, repo.db, cq, dbPage)
|
||||
if err != nil {
|
||||
return mfgroups.MembershipsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembership, err)
|
||||
}
|
||||
page := mfgroups.MembershipsPage{
|
||||
Memberships: items,
|
||||
Page: mfgroups.Page{
|
||||
Total: total,
|
||||
},
|
||||
}
|
||||
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func (repo grepo) Update(ctx context.Context, g mfgroups.Group) (mfgroups.Group, error) {
|
||||
var query []string
|
||||
var upq string
|
||||
if g.Name != "" {
|
||||
query = append(query, "name = :name,")
|
||||
}
|
||||
if g.Description != "" {
|
||||
query = append(query, "description = :description,")
|
||||
}
|
||||
if g.Metadata != nil {
|
||||
query = append(query, "metadata = :metadata,")
|
||||
}
|
||||
if len(query) > 0 {
|
||||
upq = strings.Join(query, " ")
|
||||
}
|
||||
g.Status = mfclients.EnabledStatus
|
||||
q := fmt.Sprintf(`UPDATE groups SET %s updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE owner_id = :owner_id AND id = :id AND status = :status
|
||||
RETURNING id, name, description, owner_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, updated_at, updated_by, status`, upq)
|
||||
|
||||
dbu, err := toDBGroup(g)
|
||||
if err != nil {
|
||||
return mfgroups.Group{}, errors.Wrap(errors.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
row, err := repo.db.NamedQueryContext(ctx, q, dbu)
|
||||
if err != nil {
|
||||
return mfgroups.Group{}, postgres.HandleError(err, errors.ErrUpdateEntity)
|
||||
}
|
||||
|
||||
defer row.Close()
|
||||
if ok := row.Next(); !ok {
|
||||
return mfgroups.Group{}, errors.Wrap(errors.ErrNotFound, row.Err())
|
||||
}
|
||||
dbu = dbGroup{}
|
||||
if err := row.StructScan(&dbu); err != nil {
|
||||
return mfgroups.Group{}, errors.Wrap(err, errors.ErrUpdateEntity)
|
||||
}
|
||||
return toGroup(dbu)
|
||||
}
|
||||
|
||||
func (repo grepo) ChangeStatus(ctx context.Context, group mfgroups.Group) (mfgroups.Group, error) {
|
||||
qc := `UPDATE groups SET status = :status WHERE id = :id RETURNING id, name, description, owner_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, updated_at, updated_by, status`
|
||||
|
||||
dbg, err := toDBGroup(group)
|
||||
if err != nil {
|
||||
return mfgroups.Group{}, errors.Wrap(errors.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
row, err := repo.db.NamedQueryContext(ctx, qc, dbg)
|
||||
if err != nil {
|
||||
return mfgroups.Group{}, postgres.HandleError(err, errors.ErrUpdateEntity)
|
||||
}
|
||||
|
||||
defer row.Close()
|
||||
if ok := row.Next(); !ok {
|
||||
return mfgroups.Group{}, errors.Wrap(errors.ErrNotFound, row.Err())
|
||||
}
|
||||
dbg = dbGroup{}
|
||||
if err := row.StructScan(&dbg); err != nil {
|
||||
return mfgroups.Group{}, errors.Wrap(err, errors.ErrUpdateEntity)
|
||||
}
|
||||
|
||||
return toGroup(dbg)
|
||||
|
||||
}
|
||||
|
||||
func buildHierachy(gm mfgroups.GroupsPage) string {
|
||||
query := ""
|
||||
switch {
|
||||
case gm.Direction >= 0: // ancestors
|
||||
query = `WITH RECURSIVE groups_cte as (
|
||||
SELECT id, COALESCE(parent_id, '') AS parent_id, owner_id, name, description, metadata, created_at, updated_at, updated_by, status, 0 as level from groups WHERE id = :id
|
||||
UNION SELECT x.id, COALESCE(x.parent_id, '') AS parent_id, x.owner_id, x.name, x.description, x.metadata, x.created_at, x.updated_at, x.updated_by, x.status, level - 1 from groups x
|
||||
INNER JOIN groups_cte a ON a.parent_id = x.id
|
||||
) SELECT * FROM groups_cte g`
|
||||
|
||||
case gm.Direction < 0: // descendants
|
||||
query = `WITH RECURSIVE groups_cte as (
|
||||
SELECT id, COALESCE(parent_id, '') AS parent_id, owner_id, name, description, metadata, created_at, updated_at, updated_by, status, 0 as level, CONCAT('', '', id) as path from groups WHERE id = :id
|
||||
UNION SELECT x.id, COALESCE(x.parent_id, '') AS parent_id, x.owner_id, x.name, x.description, x.metadata, x.created_at, x.updated_at, x.updated_by, x.status, level + 1, CONCAT(path, '.', x.id) as path from groups x
|
||||
INNER JOIN groups_cte d ON d.id = x.parent_id
|
||||
) SELECT * FROM groups_cte g`
|
||||
}
|
||||
return query
|
||||
}
|
||||
func buildQuery(gm mfgroups.GroupsPage) (string, error) {
|
||||
queries := []string{}
|
||||
|
||||
if gm.Name != "" {
|
||||
queries = append(queries, "g.name = :name")
|
||||
}
|
||||
if gm.Status != mfclients.AllStatus {
|
||||
queries = append(queries, "g.status = :status")
|
||||
}
|
||||
if gm.OwnerID != "" {
|
||||
queries = append(queries, "g.owner_id = :owner_id")
|
||||
}
|
||||
if gm.Tag != "" {
|
||||
queries = append(queries, ":tag = ANY(c.tags)")
|
||||
}
|
||||
if gm.Subject != "" {
|
||||
queries = append(queries, "(g.owner_id = :owner_id OR id IN (SELECT object as id FROM policies WHERE subject = :subject AND :action=ANY(actions)))")
|
||||
}
|
||||
if len(gm.Metadata) > 0 {
|
||||
queries = append(queries, "'g.metadata @> :metadata'")
|
||||
}
|
||||
if len(queries) > 0 {
|
||||
return fmt.Sprintf("WHERE %s", strings.Join(queries, " AND ")), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
type dbGroup struct {
|
||||
ID string `db:"id"`
|
||||
Parent string `db:"parent_id"`
|
||||
Owner string `db:"owner_id"`
|
||||
Name string `db:"name"`
|
||||
Description string `db:"description"`
|
||||
Level int `db:"level"`
|
||||
Path string `db:"path,omitempty"`
|
||||
Metadata []byte `db:"metadata"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt sql.NullTime `db:"updated_at,omitempty"`
|
||||
UpdatedBy *string `db:"updated_by,omitempty"`
|
||||
Status mfclients.Status `db:"status"`
|
||||
}
|
||||
|
||||
func toDBGroup(g mfgroups.Group) (dbGroup, error) {
|
||||
data := []byte("{}")
|
||||
if len(g.Metadata) > 0 {
|
||||
b, err := json.Marshal(g.Metadata)
|
||||
if err != nil {
|
||||
return dbGroup{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
data = b
|
||||
}
|
||||
var updatedAt sql.NullTime
|
||||
if !g.UpdatedAt.IsZero() {
|
||||
updatedAt = sql.NullTime{Time: g.UpdatedAt, Valid: true}
|
||||
}
|
||||
var updatedBy *string
|
||||
if g.UpdatedBy != "" {
|
||||
updatedBy = &g.UpdatedBy
|
||||
}
|
||||
return dbGroup{
|
||||
ID: g.ID,
|
||||
Name: g.Name,
|
||||
Parent: g.Parent,
|
||||
Owner: g.Owner,
|
||||
Description: g.Description,
|
||||
Metadata: data,
|
||||
Path: g.Path,
|
||||
CreatedAt: g.CreatedAt,
|
||||
UpdatedAt: updatedAt,
|
||||
UpdatedBy: updatedBy,
|
||||
Status: g.Status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toGroup(g dbGroup) (mfgroups.Group, error) {
|
||||
var metadata mfclients.Metadata
|
||||
if g.Metadata != nil {
|
||||
if err := json.Unmarshal([]byte(g.Metadata), &metadata); err != nil {
|
||||
return mfgroups.Group{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
}
|
||||
var updatedAt time.Time
|
||||
if g.UpdatedAt.Valid {
|
||||
updatedAt = g.UpdatedAt.Time
|
||||
}
|
||||
var updatedBy string
|
||||
if g.UpdatedBy != nil {
|
||||
updatedBy = *g.UpdatedBy
|
||||
}
|
||||
|
||||
return mfgroups.Group{
|
||||
ID: g.ID,
|
||||
Name: g.Name,
|
||||
Parent: g.Parent,
|
||||
Owner: g.Owner,
|
||||
Description: g.Description,
|
||||
Metadata: metadata,
|
||||
Level: g.Level,
|
||||
Path: g.Path,
|
||||
UpdatedAt: updatedAt,
|
||||
CreatedAt: g.CreatedAt,
|
||||
UpdatedBy: updatedBy,
|
||||
Status: g.Status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (gr grepo) processRows(rows *sqlx.Rows) ([]mfgroups.Group, error) {
|
||||
var items []mfgroups.Group
|
||||
for rows.Next() {
|
||||
dbg := dbGroup{}
|
||||
if err := rows.StructScan(&dbg); err != nil {
|
||||
return items, err
|
||||
}
|
||||
group, err := toGroup(dbg)
|
||||
if err != nil {
|
||||
return items, err
|
||||
}
|
||||
items = append(items, group)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func toDBGroupPage(pm mfgroups.GroupsPage) (dbGroupPage, error) {
|
||||
level := mfgroups.MaxLevel
|
||||
if pm.Level < mfgroups.MaxLevel {
|
||||
level = pm.Level
|
||||
}
|
||||
data := []byte("{}")
|
||||
if len(pm.Metadata) > 0 {
|
||||
b, err := json.Marshal(pm.Metadata)
|
||||
if err != nil {
|
||||
return dbGroupPage{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
data = b
|
||||
}
|
||||
return dbGroupPage{
|
||||
ID: pm.ID,
|
||||
Name: pm.Name,
|
||||
Metadata: data,
|
||||
Path: pm.Path,
|
||||
Level: level,
|
||||
Total: pm.Total,
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
ParentID: pm.ID,
|
||||
Owner: pm.OwnerID,
|
||||
Subject: pm.Subject,
|
||||
Action: pm.Action,
|
||||
Status: pm.Status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type dbGroupPage struct {
|
||||
ClientID string `db:"client_id"`
|
||||
ID string `db:"id"`
|
||||
Name string `db:"name"`
|
||||
ParentID string `db:"parent_id"`
|
||||
Owner string `db:"owner_id"`
|
||||
Metadata []byte `db:"metadata"`
|
||||
Path string `db:"path"`
|
||||
Level uint64 `db:"level"`
|
||||
Total uint64 `db:"total"`
|
||||
Limit uint64 `db:"limit"`
|
||||
Offset uint64 `db:"offset"`
|
||||
Subject string `db:"subject"`
|
||||
Action string `db:"action"`
|
||||
Status mfclients.Status `db:"status"`
|
||||
}
|
@ -1,600 +0,0 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux/internal/testsutil"
|
||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
||||
"github.com/mainflux/mainflux/pkg/uuid"
|
||||
cpostgres "github.com/mainflux/mainflux/things/clients/postgres"
|
||||
gpostgres "github.com/mainflux/mainflux/things/groups/postgres"
|
||||
"github.com/mainflux/mainflux/things/policies"
|
||||
ppostgres "github.com/mainflux/mainflux/things/policies/postgres"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
maxNameSize = 1024
|
||||
maxDescSize = 1024
|
||||
maxLevel = uint64(5)
|
||||
groupName = "group"
|
||||
description = "description"
|
||||
)
|
||||
|
||||
var (
|
||||
wrongID = "wrong-id"
|
||||
invalidName = strings.Repeat("m", maxNameSize+10)
|
||||
validDesc = strings.Repeat("m", 100)
|
||||
invalidDesc = strings.Repeat("m", maxDescSize+1)
|
||||
metadata = mfclients.Metadata{
|
||||
"admin": "true",
|
||||
}
|
||||
idProvider = uuid.New()
|
||||
)
|
||||
|
||||
func TestGroupSave(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
groupRepo := gpostgres.NewRepository(database)
|
||||
|
||||
usrID := testsutil.GenerateUUID(t, idProvider)
|
||||
grpID := testsutil.GenerateUUID(t, idProvider)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
group mfgroups.Group
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "create new group successfully",
|
||||
group: mfgroups.Group{
|
||||
ID: grpID,
|
||||
Name: groupName,
|
||||
Status: mfclients.EnabledStatus,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "create a new group with an existing name",
|
||||
group: mfgroups.Group{
|
||||
ID: grpID,
|
||||
Name: groupName,
|
||||
Status: mfclients.EnabledStatus,
|
||||
},
|
||||
err: errors.ErrConflict,
|
||||
},
|
||||
{
|
||||
desc: "create group with an invalid name",
|
||||
group: mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: invalidName,
|
||||
Status: mfclients.EnabledStatus,
|
||||
},
|
||||
err: errors.ErrMalformedEntity,
|
||||
},
|
||||
{
|
||||
desc: "create a group with invalid ID",
|
||||
group: mfgroups.Group{
|
||||
ID: usrID,
|
||||
Name: "withInvalidDescription",
|
||||
Description: invalidDesc,
|
||||
Status: mfclients.EnabledStatus,
|
||||
},
|
||||
err: errors.ErrMalformedEntity,
|
||||
},
|
||||
{
|
||||
desc: "create group with description",
|
||||
group: mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "withDescription",
|
||||
Description: validDesc,
|
||||
Status: mfclients.EnabledStatus,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "create group with invalid description",
|
||||
group: mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "withInvalidDescription",
|
||||
Description: invalidDesc,
|
||||
Status: mfclients.EnabledStatus,
|
||||
},
|
||||
err: errors.ErrMalformedEntity,
|
||||
},
|
||||
{
|
||||
desc: "create group with parent",
|
||||
group: mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Parent: grpID,
|
||||
Name: "withParent",
|
||||
Status: mfclients.EnabledStatus,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "create a group with an invalid parent",
|
||||
group: mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Parent: invalidName,
|
||||
Name: "withInvalidParent",
|
||||
Status: mfclients.EnabledStatus,
|
||||
},
|
||||
err: errors.ErrMalformedEntity,
|
||||
},
|
||||
{
|
||||
desc: "create a group with an owner",
|
||||
group: mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Owner: usrID,
|
||||
Name: "withOwner",
|
||||
Status: mfclients.EnabledStatus,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "create a group with an invalid owner",
|
||||
group: mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Owner: invalidName,
|
||||
Name: "withInvalidOwner",
|
||||
Status: mfclients.EnabledStatus,
|
||||
},
|
||||
err: errors.ErrMalformedEntity,
|
||||
},
|
||||
{
|
||||
desc: "create a group with metadata",
|
||||
group: mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "withMetadata",
|
||||
Metadata: metadata,
|
||||
Status: mfclients.EnabledStatus,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
_, err := groupRepo.Save(context.Background(), tc.group)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGroupRetrieveByID(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
groupRepo := gpostgres.NewRepository(database)
|
||||
|
||||
uid := testsutil.GenerateUUID(t, idProvider)
|
||||
group1 := mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: groupName + "TestGroupRetrieveByID1",
|
||||
Owner: uid,
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
_, err := groupRepo.Save(context.Background(), group1)
|
||||
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
|
||||
|
||||
retrieved, err := groupRepo.RetrieveByID(context.Background(), group1.ID)
|
||||
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
|
||||
assert.True(t, retrieved.ID == group1.ID, fmt.Sprintf("Save group, ID: expected %s got %s\n", group1.ID, retrieved.ID))
|
||||
|
||||
// Round to milliseconds as otherwise saving and retrieving from DB
|
||||
// adds rounding error.
|
||||
creationTime := time.Now().UTC().Round(time.Millisecond)
|
||||
group2 := mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: groupName + "TestGroupRetrieveByID",
|
||||
Owner: uid,
|
||||
Parent: group1.ID,
|
||||
CreatedAt: creationTime,
|
||||
UpdatedAt: creationTime,
|
||||
Description: description,
|
||||
Metadata: metadata,
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
_, err = groupRepo.Save(context.Background(), group2)
|
||||
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
|
||||
|
||||
retrieved, err = groupRepo.RetrieveByID(context.Background(), group2.ID)
|
||||
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
|
||||
assert.True(t, retrieved.ID == group2.ID, fmt.Sprintf("Save group, ID: expected %s got %s\n", group2.ID, retrieved.ID))
|
||||
assert.True(t, retrieved.CreatedAt.Equal(creationTime), fmt.Sprintf("Save group, CreatedAt: expected %s got %s\n", creationTime, retrieved.CreatedAt))
|
||||
assert.True(t, retrieved.UpdatedAt.Equal(creationTime), fmt.Sprintf("Save group, UpdatedAt: expected %s got %s\n", creationTime, retrieved.UpdatedAt))
|
||||
assert.True(t, retrieved.Parent == group1.ID, fmt.Sprintf("Save group, Level: expected %s got %s\n", group1.ID, retrieved.Parent))
|
||||
assert.True(t, retrieved.Description == description, fmt.Sprintf("Save group, Description: expected %v got %v\n", retrieved.Description, description))
|
||||
|
||||
retrieved, err = groupRepo.RetrieveByID(context.Background(), testsutil.GenerateUUID(t, idProvider))
|
||||
assert.True(t, errors.Contains(err, errors.ErrNotFound), fmt.Sprintf("Retrieve group: expected %s got %s\n", errors.ErrNotFound, err))
|
||||
}
|
||||
|
||||
func TestGroupRetrieveAll(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
groupRepo := gpostgres.NewRepository(database)
|
||||
|
||||
var nGroups = uint64(200)
|
||||
var ownerID = testsutil.GenerateUUID(t, idProvider)
|
||||
var parentID string
|
||||
for i := uint64(0); i < nGroups; i++ {
|
||||
creationTime := time.Now().UTC()
|
||||
group := mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: fmt.Sprintf("%s-%d", groupName, i),
|
||||
Description: fmt.Sprintf("%s-description-%d", groupName, i),
|
||||
CreatedAt: creationTime,
|
||||
UpdatedAt: creationTime,
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
if i == 1 {
|
||||
parentID = group.ID
|
||||
}
|
||||
if i%10 == 0 {
|
||||
group.Owner = ownerID
|
||||
group.Parent = parentID
|
||||
}
|
||||
if i%50 == 0 {
|
||||
group.Status = mfclients.DisabledStatus
|
||||
}
|
||||
_, err := groupRepo.Save(context.Background(), group)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
|
||||
parentID = group.ID
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
Size uint64
|
||||
Metadata mfgroups.GroupsPage
|
||||
}{
|
||||
"retrieve all groups": {
|
||||
Metadata: mfgroups.GroupsPage{
|
||||
Page: mfgroups.Page{
|
||||
Total: nGroups,
|
||||
Limit: nGroups,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
Level: maxLevel,
|
||||
},
|
||||
Size: nGroups,
|
||||
},
|
||||
"retrieve all groups with offset": {
|
||||
Metadata: mfgroups.GroupsPage{
|
||||
Page: mfgroups.Page{
|
||||
Total: nGroups,
|
||||
Offset: 50,
|
||||
Limit: nGroups,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
Level: maxLevel,
|
||||
},
|
||||
Size: nGroups - 50,
|
||||
},
|
||||
"retrieve all groups with limit": {
|
||||
Metadata: mfgroups.GroupsPage{
|
||||
Page: mfgroups.Page{
|
||||
Total: nGroups,
|
||||
Offset: 0,
|
||||
Limit: 50,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
Level: maxLevel,
|
||||
},
|
||||
Size: 50,
|
||||
},
|
||||
"retrieve all groups with offset and limit": {
|
||||
Metadata: mfgroups.GroupsPage{
|
||||
Page: mfgroups.Page{
|
||||
Total: nGroups,
|
||||
Offset: 50,
|
||||
Limit: 50,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
Level: maxLevel,
|
||||
},
|
||||
Size: 50,
|
||||
},
|
||||
"retrieve all groups with offset greater than limit": {
|
||||
Metadata: mfgroups.GroupsPage{
|
||||
Page: mfgroups.Page{
|
||||
Total: nGroups,
|
||||
Offset: 250,
|
||||
Limit: nGroups,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
Level: maxLevel,
|
||||
},
|
||||
Size: 0,
|
||||
},
|
||||
"retrieve all groups with owner id": {
|
||||
Metadata: mfgroups.GroupsPage{
|
||||
Page: mfgroups.Page{
|
||||
Total: nGroups,
|
||||
Limit: nGroups,
|
||||
Subject: ownerID,
|
||||
OwnerID: ownerID,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
Level: maxLevel,
|
||||
},
|
||||
Size: 20,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
page, err := groupRepo.RetrieveAll(context.Background(), tc.Metadata)
|
||||
size := len(page.Groups)
|
||||
assert.Equal(t, tc.Size, uint64(size), fmt.Sprintf("%s: expected size %d got %d\n", desc, tc.Size, size))
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %d\n", desc, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupUpdate(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
groupRepo := gpostgres.NewRepository(database)
|
||||
|
||||
uid := testsutil.GenerateUUID(t, idProvider)
|
||||
|
||||
creationTime := time.Now().UTC()
|
||||
updateTime := time.Now().UTC()
|
||||
groupID := testsutil.GenerateUUID(t, idProvider)
|
||||
|
||||
group := mfgroups.Group{
|
||||
ID: groupID,
|
||||
Name: groupName + "TestGroupUpdate",
|
||||
Owner: uid,
|
||||
CreatedAt: creationTime,
|
||||
UpdatedAt: creationTime,
|
||||
Description: description,
|
||||
Metadata: metadata,
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
updatedName := groupName + "Updated"
|
||||
updatedMetadata := mfclients.Metadata{"admin": "false"}
|
||||
updatedDescription := description + "updated"
|
||||
_, err := groupRepo.Save(context.Background(), group)
|
||||
require.Nil(t, err, fmt.Sprintf("group save got unexpected error: %s", err))
|
||||
|
||||
retrieved, err := groupRepo.RetrieveByID(context.Background(), group.ID)
|
||||
require.Nil(t, err, fmt.Sprintf("group save got unexpected error: %s", err))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
groupUpdate mfgroups.Group
|
||||
groupExpected mfgroups.Group
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update group name for existing id",
|
||||
groupUpdate: mfgroups.Group{
|
||||
ID: group.ID,
|
||||
Name: updatedName,
|
||||
UpdatedAt: updateTime,
|
||||
Owner: uid,
|
||||
},
|
||||
groupExpected: mfgroups.Group{
|
||||
Name: updatedName,
|
||||
Metadata: retrieved.Metadata,
|
||||
Description: retrieved.Description,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update group metadata for existing id",
|
||||
groupUpdate: mfgroups.Group{
|
||||
ID: group.ID,
|
||||
UpdatedAt: updateTime,
|
||||
Metadata: updatedMetadata,
|
||||
Owner: uid,
|
||||
},
|
||||
groupExpected: mfgroups.Group{
|
||||
Name: updatedName,
|
||||
UpdatedAt: updateTime,
|
||||
Metadata: updatedMetadata,
|
||||
Description: retrieved.Description,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update group description for existing id",
|
||||
groupUpdate: mfgroups.Group{
|
||||
ID: group.ID,
|
||||
UpdatedAt: updateTime,
|
||||
Description: updatedDescription,
|
||||
Owner: uid,
|
||||
},
|
||||
groupExpected: mfgroups.Group{
|
||||
Name: updatedName,
|
||||
Description: updatedDescription,
|
||||
UpdatedAt: updateTime,
|
||||
Metadata: updatedMetadata,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update group name and metadata for existing id",
|
||||
groupUpdate: mfgroups.Group{
|
||||
ID: group.ID,
|
||||
Name: updatedName,
|
||||
UpdatedAt: updateTime,
|
||||
Metadata: updatedMetadata,
|
||||
Owner: uid,
|
||||
},
|
||||
groupExpected: mfgroups.Group{
|
||||
Name: updatedName,
|
||||
UpdatedAt: updateTime,
|
||||
Metadata: updatedMetadata,
|
||||
Description: updatedDescription,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update group for invalid name",
|
||||
groupUpdate: mfgroups.Group{
|
||||
ID: group.ID,
|
||||
Owner: uid,
|
||||
Name: invalidName,
|
||||
},
|
||||
err: errors.ErrMalformedEntity,
|
||||
},
|
||||
{
|
||||
desc: "update group for invalid description",
|
||||
groupUpdate: mfgroups.Group{
|
||||
ID: group.ID,
|
||||
Owner: uid,
|
||||
Description: invalidDesc,
|
||||
},
|
||||
err: errors.ErrMalformedEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
updated, err := groupRepo.Update(context.Background(), tc.groupUpdate)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.True(t, updated.Name == tc.groupExpected.Name, fmt.Sprintf("%s:Name: expected %s got %s\n", tc.desc, tc.groupExpected.Name, updated.Name))
|
||||
assert.True(t, updated.Description == tc.groupExpected.Description, fmt.Sprintf("%s:Description: expected %s got %s\n", tc.desc, tc.groupExpected.Description, updated.Description))
|
||||
assert.True(t, updated.Metadata["admin"] == tc.groupExpected.Metadata["admin"], fmt.Sprintf("%s:Metadata: expected %d got %d\n", tc.desc, tc.groupExpected.Metadata["admin"], updated.Metadata["admin"]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsMemberships(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
crepo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
prepo := ppostgres.NewRepository(database)
|
||||
|
||||
clientA := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "client-memberships",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client-memberships1@example.com",
|
||||
Secret: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
Metadata: mfclients.Metadata{},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
clientB := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "client-memberships",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client-memberships2@example.com",
|
||||
Secret: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
Metadata: mfclients.Metadata{},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
group := mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "group-membership",
|
||||
Metadata: mfclients.Metadata{},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
policyA := policies.Policy{
|
||||
Subject: clientA.ID,
|
||||
Object: group.ID,
|
||||
Actions: []string{"g_list"},
|
||||
}
|
||||
policyB := policies.Policy{
|
||||
Subject: clientB.ID,
|
||||
Object: group.ID,
|
||||
Actions: []string{"g_list"},
|
||||
}
|
||||
|
||||
_, err := crepo.Save(context.Background(), clientA)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save client: expected %v got %s\n", nil, err))
|
||||
_, err = crepo.Save(context.Background(), clientB)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save client: expected %v got %s\n", nil, err))
|
||||
_, err = grepo.Save(context.Background(), group)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save group: expected %v got %s\n", nil, err))
|
||||
_, err = prepo.Save(context.Background(), policyA)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save policy: expected %v got %s\n", nil, err))
|
||||
_, err = prepo.Save(context.Background(), policyB)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save policy: expected %v got %s\n", nil, err))
|
||||
|
||||
cases := map[string]struct {
|
||||
ID string
|
||||
err error
|
||||
}{
|
||||
"retrieve membership for existing client": {clientA.ID, nil},
|
||||
"retrieve membership for non-existing client": {wrongID, nil},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
mp, err := grepo.Memberships(context.Background(), tc.ID, mfgroups.GroupsPage{Page: mfgroups.Page{Total: 10, Offset: 0, Limit: 10, Status: mfclients.AllStatus, Subject: clientB.ID, Action: "g_list"}})
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
if tc.ID == clientA.ID {
|
||||
assert.ElementsMatch(t, mp.Memberships, []mfgroups.Group{group}, fmt.Sprintf("%s: expected %v got %v\n", desc, []mfgroups.Group{group}, mp.Memberships))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupChangeStatus(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := gpostgres.NewRepository(database)
|
||||
|
||||
group1 := mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "active-group",
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
group2 := mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "inactive-group",
|
||||
Status: mfclients.DisabledStatus,
|
||||
}
|
||||
|
||||
group1, err := repo.Save(context.Background(), group1)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new group: expected %v got %s\n", nil, err))
|
||||
group2, err = repo.Save(context.Background(), group2)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled group: expected %v got %s\n", nil, err))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
group mfgroups.Group
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "change group status for an active group",
|
||||
group: mfgroups.Group{
|
||||
ID: group1.ID,
|
||||
Status: mfclients.EnabledStatus,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "change group status for a inactive group",
|
||||
group: mfgroups.Group{
|
||||
ID: group2.ID,
|
||||
Status: mfclients.EnabledStatus,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "change group status for an invalid group",
|
||||
group: mfgroups.Group{
|
||||
ID: "invalid",
|
||||
Status: mfclients.DisabledStatus,
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
expected, err := repo.ChangeStatus(context.Background(), tc.group)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.group.Status, expected.Status, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.group.Status, expected.Status))
|
||||
}
|
||||
}
|
||||
}
|
@ -12,9 +12,9 @@ import (
|
||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
||||
gpostgres "github.com/mainflux/mainflux/pkg/groups/postgres"
|
||||
"github.com/mainflux/mainflux/pkg/uuid"
|
||||
cpostgres "github.com/mainflux/mainflux/things/clients/postgres"
|
||||
gpostgres "github.com/mainflux/mainflux/things/groups/postgres"
|
||||
"github.com/mainflux/mainflux/things/policies"
|
||||
ppostgres "github.com/mainflux/mainflux/things/policies/postgres"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -28,7 +28,7 @@ var (
|
||||
func TestPoliciesSave(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := ppostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
grepo := gpostgres.New(database)
|
||||
|
||||
uid := testsutil.GenerateUUID(t, idProvider)
|
||||
|
||||
@ -80,7 +80,7 @@ func TestPoliciesEvaluate(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := ppostgres.NewRepository(database)
|
||||
crepo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
grepo := gpostgres.New(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
@ -167,7 +167,7 @@ func TestPoliciesRetrieve(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := ppostgres.NewRepository(database)
|
||||
crepo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
grepo := gpostgres.New(database)
|
||||
|
||||
uid := testsutil.GenerateUUID(t, idProvider)
|
||||
|
||||
@ -225,7 +225,7 @@ func TestPoliciesUpdate(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := ppostgres.NewRepository(database)
|
||||
crepo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
grepo := gpostgres.New(database)
|
||||
|
||||
client := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
@ -347,7 +347,7 @@ func TestPoliciesRetrievalAll(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := ppostgres.NewRepository(database)
|
||||
crepo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
grepo := gpostgres.New(database)
|
||||
|
||||
var nPolicies = uint64(10)
|
||||
|
||||
@ -605,7 +605,7 @@ func TestPoliciesDelete(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := ppostgres.NewRepository(database)
|
||||
crepo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
grepo := gpostgres.New(database)
|
||||
|
||||
client := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
|
@ -8,12 +8,13 @@ import (
|
||||
|
||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
"github.com/mainflux/mainflux/users/clients/postgres"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
const WrongID = "wrongID"
|
||||
|
||||
var _ mfclients.Repository = (*Repository)(nil)
|
||||
var _ postgres.Repository = (*Repository)(nil)
|
||||
|
||||
type Repository struct {
|
||||
mock.Mock
|
||||
@ -68,17 +69,16 @@ func (m *Repository) RetrieveByIdentity(ctx context.Context, identity string) (m
|
||||
return ret.Get(0).(mfclients.Client), ret.Error(1)
|
||||
}
|
||||
|
||||
func (m *Repository) Save(ctx context.Context, clients ...mfclients.Client) ([]mfclients.Client, error) {
|
||||
client := clients[0]
|
||||
func (m *Repository) Save(ctx context.Context, client mfclients.Client) (mfclients.Client, error) {
|
||||
ret := m.Called(ctx, client)
|
||||
if client.Owner == WrongID {
|
||||
return []mfclients.Client{}, errors.ErrMalformedEntity
|
||||
return mfclients.Client{}, errors.ErrMalformedEntity
|
||||
}
|
||||
if client.Credentials.Secret == "" {
|
||||
return []mfclients.Client{}, errors.ErrMalformedEntity
|
||||
return mfclients.Client{}, errors.ErrMalformedEntity
|
||||
}
|
||||
|
||||
return clients, ret.Error(1)
|
||||
return client, ret.Error(1)
|
||||
}
|
||||
|
||||
func (m *Repository) Update(ctx context.Context, client mfclients.Client) (mfclients.Client, error) {
|
||||
|
@ -5,494 +5,62 @@ package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgtype" // required for SQL access
|
||||
// required for SQL access.
|
||||
|
||||
"github.com/mainflux/mainflux/internal/postgres"
|
||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||
pgclients "github.com/mainflux/mainflux/pkg/clients/postgres"
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
"github.com/mainflux/mainflux/pkg/groups"
|
||||
)
|
||||
|
||||
var _ mfclients.Repository = (*clientRepo)(nil)
|
||||
|
||||
type clientRepo struct {
|
||||
db postgres.Database
|
||||
pgclients.ClientRepository
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
mfclients.Repository
|
||||
|
||||
// Save persists the client account. A non-nil error is returned to indicate
|
||||
// operation failure.
|
||||
Save(ctx context.Context, client mfclients.Client) (mfclients.Client, error)
|
||||
}
|
||||
|
||||
// NewRepository instantiates a PostgreSQL
|
||||
// implementation of Clients repository.
|
||||
func NewRepository(db postgres.Database) mfclients.Repository {
|
||||
func NewRepository(db postgres.Database) Repository {
|
||||
return &clientRepo{
|
||||
db: db,
|
||||
ClientRepository: pgclients.ClientRepository{DB: db},
|
||||
}
|
||||
}
|
||||
|
||||
func (clientRepo) RetrieveBySecret(ctx context.Context, key string) (mfclients.Client, error) {
|
||||
return mfclients.Client{}, nil
|
||||
}
|
||||
|
||||
func (repo clientRepo) Save(ctx context.Context, c ...mfclients.Client) ([]mfclients.Client, error) {
|
||||
func (repo clientRepo) Save(ctx context.Context, c mfclients.Client) (mfclients.Client, error) {
|
||||
q := `INSERT INTO clients (id, name, tags, owner_id, identity, secret, metadata, created_at, status, role)
|
||||
VALUES (:id, :name, :tags, :owner_id, :identity, :secret, :metadata, :created_at, :status, :role)
|
||||
RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at`
|
||||
dbc, err := toDBClient(c[0])
|
||||
dbc, err := pgclients.ToDBClient(c)
|
||||
if err != nil {
|
||||
return []mfclients.Client{}, errors.Wrap(errors.ErrCreateEntity, err)
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
row, err := repo.db.NamedQueryContext(ctx, q, dbc)
|
||||
row, err := repo.ClientRepository.DB.NamedQueryContext(ctx, q, dbc)
|
||||
if err != nil {
|
||||
return []mfclients.Client{}, postgres.HandleError(err, errors.ErrCreateEntity)
|
||||
return mfclients.Client{}, postgres.HandleError(err, errors.ErrCreateEntity)
|
||||
}
|
||||
|
||||
defer row.Close()
|
||||
row.Next()
|
||||
dbc = dbClient{}
|
||||
if err := row.StructScan(&dbc); err != nil {
|
||||
return []mfclients.Client{}, err
|
||||
}
|
||||
|
||||
client, err := toClient(dbc)
|
||||
if err != nil {
|
||||
return []mfclients.Client{}, err
|
||||
}
|
||||
|
||||
return []mfclients.Client{client}, nil
|
||||
}
|
||||
|
||||
func (repo clientRepo) RetrieveByID(ctx context.Context, id string) (mfclients.Client, error) {
|
||||
q := `SELECT id, name, tags, COALESCE(owner_id, '') AS owner_id, identity, secret, metadata, created_at, updated_at, updated_by, status
|
||||
FROM clients WHERE id = :id`
|
||||
|
||||
dbc := dbClient{
|
||||
ID: id,
|
||||
}
|
||||
|
||||
row, err := repo.db.NamedQueryContext(ctx, q, dbc)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrNotFound, err)
|
||||
}
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
defer row.Close()
|
||||
row.Next()
|
||||
dbc = dbClient{}
|
||||
if err := row.StructScan(&dbc); err != nil {
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrNotFound, err)
|
||||
}
|
||||
|
||||
return toClient(dbc)
|
||||
}
|
||||
|
||||
func (repo clientRepo) RetrieveByIdentity(ctx context.Context, identity string) (mfclients.Client, error) {
|
||||
q := `SELECT id, name, tags, COALESCE(owner_id, '') AS owner_id, identity, secret, metadata, created_at, updated_at, updated_by, status
|
||||
FROM clients WHERE identity = :identity AND status = :status`
|
||||
|
||||
dbc := dbClient{
|
||||
Identity: identity,
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
row, err := repo.db.NamedQueryContext(ctx, q, dbc)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrNotFound, err)
|
||||
}
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
defer row.Close()
|
||||
row.Next()
|
||||
dbc = dbClient{}
|
||||
if err := row.StructScan(&dbc); err != nil {
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrNotFound, err)
|
||||
}
|
||||
|
||||
return toClient(dbc)
|
||||
}
|
||||
|
||||
func (repo clientRepo) RetrieveAll(ctx context.Context, pm mfclients.Page) (mfclients.ClientsPage, error) {
|
||||
query, err := pageQuery(pm)
|
||||
if err != nil {
|
||||
return mfclients.ClientsPage{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.metadata, COALESCE(c.owner_id, '') AS owner_id, c.status,
|
||||
c.created_at, c.updated_at, COALESCE(c.updated_by, '') AS updated_by FROM clients c %s ORDER BY c.created_at LIMIT :limit OFFSET :offset;`, query)
|
||||
|
||||
dbPage, err := toDBClientsPage(pm)
|
||||
if err != nil {
|
||||
return mfclients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
rows, err := repo.db.NamedQueryContext(ctx, q, dbPage)
|
||||
if err != nil {
|
||||
return mfclients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []mfclients.Client
|
||||
for rows.Next() {
|
||||
dbc := dbClient{}
|
||||
if err := rows.StructScan(&dbc); err != nil {
|
||||
return mfclients.ClientsPage{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
c, err := toClient(dbc)
|
||||
if err != nil {
|
||||
return mfclients.ClientsPage{}, err
|
||||
}
|
||||
|
||||
items = append(items, c)
|
||||
}
|
||||
cq := fmt.Sprintf(`SELECT COUNT(*) FROM clients c %s;`, query)
|
||||
|
||||
total, err := postgres.Total(ctx, repo.db, cq, dbPage)
|
||||
if err != nil {
|
||||
return mfclients.ClientsPage{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
page := mfclients.ClientsPage{
|
||||
Clients: items,
|
||||
Page: mfclients.Page{
|
||||
Total: total,
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
},
|
||||
}
|
||||
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func (repo clientRepo) Members(ctx context.Context, groupID string, pm mfclients.Page) (mfclients.MembersPage, error) {
|
||||
emq, err := pageQuery(pm)
|
||||
if err != nil {
|
||||
return mfclients.MembersPage{}, err
|
||||
}
|
||||
|
||||
aq := ""
|
||||
// If not admin, the client needs to have a g_list action on the group or they are the owner.
|
||||
if pm.Subject != "" {
|
||||
aq = `AND (EXISTS (SELECT 1 FROM policies p WHERE p.subject = :subject AND :action=ANY(actions))
|
||||
OR EXISTS (SELECT 1 FROM groups g WHERE g.owner_id = :subject AND g.id = :group_id))
|
||||
AND c.id != :subject`
|
||||
}
|
||||
q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.metadata, c.identity, c.status,
|
||||
c.created_at, c.updated_at FROM clients c
|
||||
INNER JOIN policies ON c.id=policies.subject %s AND policies.object = :group_id %s
|
||||
ORDER BY c.created_at LIMIT :limit OFFSET :offset;`, emq, aq)
|
||||
dbPage, err := toDBClientsPage(pm)
|
||||
if err != nil {
|
||||
return mfclients.MembersPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err)
|
||||
}
|
||||
dbPage.GroupID = groupID
|
||||
rows, err := repo.db.NamedQueryContext(ctx, q, dbPage)
|
||||
if err != nil {
|
||||
return mfclients.MembersPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembers, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []mfclients.Client
|
||||
for rows.Next() {
|
||||
dbc := dbClient{}
|
||||
if err := rows.StructScan(&dbc); err != nil {
|
||||
return mfclients.MembersPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembers, err)
|
||||
}
|
||||
|
||||
c, err := toClient(dbc)
|
||||
if err != nil {
|
||||
return mfclients.MembersPage{}, err
|
||||
}
|
||||
|
||||
items = append(items, c)
|
||||
}
|
||||
cq := fmt.Sprintf(`SELECT COUNT(*) FROM clients c INNER JOIN policies ON c.id=policies.subject %s AND policies.object = :group_id`, emq)
|
||||
if pm.Subject != "" {
|
||||
cq = fmt.Sprintf("%s AND c.id != :subject", cq)
|
||||
}
|
||||
|
||||
total, err := postgres.Total(ctx, repo.db, cq, dbPage)
|
||||
if err != nil {
|
||||
return mfclients.MembersPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembers, err)
|
||||
}
|
||||
|
||||
page := mfclients.MembersPage{
|
||||
Members: items,
|
||||
Page: mfclients.Page{
|
||||
Total: total,
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
},
|
||||
}
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func (repo clientRepo) Update(ctx context.Context, client mfclients.Client) (mfclients.Client, error) {
|
||||
var query []string
|
||||
var upq string
|
||||
if client.Name != "" {
|
||||
query = append(query, "name = :name,")
|
||||
}
|
||||
if client.Metadata != nil {
|
||||
query = append(query, "metadata = :metadata,")
|
||||
}
|
||||
if len(query) > 0 {
|
||||
upq = strings.Join(query, " ")
|
||||
}
|
||||
client.Status = mfclients.EnabledStatus
|
||||
q := fmt.Sprintf(`UPDATE clients SET %s updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id AND status = :status
|
||||
RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`,
|
||||
upq)
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo clientRepo) UpdateTags(ctx context.Context, client mfclients.Client) (mfclients.Client, error) {
|
||||
client.Status = mfclients.EnabledStatus
|
||||
q := `UPDATE clients SET tags = :tags, updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id AND status = :status
|
||||
RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo clientRepo) UpdateIdentity(ctx context.Context, client mfclients.Client) (mfclients.Client, error) {
|
||||
q := `UPDATE clients SET identity = :identity, updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id AND status = :status
|
||||
RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo clientRepo) UpdateSecret(ctx context.Context, client mfclients.Client) (mfclients.Client, error) {
|
||||
q := `UPDATE clients SET secret = :secret, updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE identity = :identity AND status = :status
|
||||
RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo clientRepo) UpdateOwner(ctx context.Context, client mfclients.Client) (mfclients.Client, error) {
|
||||
q := `UPDATE clients SET owner_id = :owner_id, updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id AND status = :status
|
||||
RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo clientRepo) ChangeStatus(ctx context.Context, client mfclients.Client) (mfclients.Client, error) {
|
||||
q := `UPDATE clients SET status = :status WHERE id = :id
|
||||
RETURNING id, name, tags, identity, metadata, COALESCE(owner_id, '') AS owner_id, status, created_at, updated_at, updated_by`
|
||||
|
||||
return repo.update(ctx, client, q)
|
||||
}
|
||||
|
||||
func (repo clientRepo) update(ctx context.Context, client mfclients.Client, query string) (mfclients.Client, error) {
|
||||
dbc, err := toDBClient(client)
|
||||
if err != nil {
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrUpdateEntity, err)
|
||||
}
|
||||
row, err := repo.db.NamedQueryContext(ctx, query, dbc)
|
||||
if err != nil {
|
||||
return mfclients.Client{}, postgres.HandleError(err, errors.ErrUpdateEntity)
|
||||
}
|
||||
|
||||
defer row.Close()
|
||||
if ok := row.Next(); !ok {
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrNotFound, row.Err())
|
||||
}
|
||||
dbc = dbClient{}
|
||||
dbc = pgclients.DBClient{}
|
||||
if err := row.StructScan(&dbc); err != nil {
|
||||
return mfclients.Client{}, err
|
||||
}
|
||||
|
||||
return toClient(dbc)
|
||||
}
|
||||
|
||||
type dbClient struct {
|
||||
ID string `db:"id"`
|
||||
Name string `db:"name,omitempty"`
|
||||
Tags pgtype.TextArray `db:"tags,omitempty"`
|
||||
Identity string `db:"identity"`
|
||||
Owner *string `db:"owner_id,omitempty"` // nullable
|
||||
Secret string `db:"secret"`
|
||||
Metadata []byte `db:"metadata,omitempty"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt sql.NullTime `db:"updated_at,omitempty"`
|
||||
UpdatedBy *string `db:"updated_by,omitempty"`
|
||||
Groups []groups.Group `db:"groups,omitempty"`
|
||||
Status mfclients.Status `db:"status"`
|
||||
Role mfclients.Role `db:"role"`
|
||||
}
|
||||
|
||||
func toDBClient(c mfclients.Client) (dbClient, error) {
|
||||
data := []byte("{}")
|
||||
if len(c.Metadata) > 0 {
|
||||
b, err := json.Marshal(c.Metadata)
|
||||
if err != nil {
|
||||
return dbClient{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
data = b
|
||||
}
|
||||
var tags pgtype.TextArray
|
||||
if err := tags.Set(c.Tags); err != nil {
|
||||
return dbClient{}, err
|
||||
}
|
||||
var owner *string
|
||||
if c.Owner != "" {
|
||||
owner = &c.Owner
|
||||
}
|
||||
var updatedBy *string
|
||||
if c.UpdatedBy != "" {
|
||||
updatedBy = &c.UpdatedBy
|
||||
}
|
||||
var updatedAt sql.NullTime
|
||||
if !c.UpdatedAt.IsZero() {
|
||||
updatedAt = sql.NullTime{Time: c.UpdatedAt, Valid: true}
|
||||
}
|
||||
|
||||
return dbClient{
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
Tags: tags,
|
||||
Owner: owner,
|
||||
Identity: c.Credentials.Identity,
|
||||
Secret: c.Credentials.Secret,
|
||||
Metadata: data,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: updatedAt,
|
||||
UpdatedBy: updatedBy,
|
||||
Status: c.Status,
|
||||
Role: c.Role,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toClient(c dbClient) (mfclients.Client, error) {
|
||||
var metadata mfclients.Metadata
|
||||
if c.Metadata != nil {
|
||||
if err := json.Unmarshal([]byte(c.Metadata), &metadata); err != nil {
|
||||
return mfclients.Client{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
}
|
||||
var tags []string
|
||||
for _, e := range c.Tags.Elements {
|
||||
tags = append(tags, e.String)
|
||||
}
|
||||
var owner string
|
||||
if c.Owner != nil {
|
||||
owner = *c.Owner
|
||||
}
|
||||
var updatedBy string
|
||||
if c.UpdatedBy != nil {
|
||||
updatedBy = *c.UpdatedBy
|
||||
}
|
||||
var updatedAt time.Time
|
||||
if c.UpdatedAt.Valid {
|
||||
updatedAt = c.UpdatedAt.Time
|
||||
}
|
||||
|
||||
return mfclients.Client{
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
Tags: tags,
|
||||
Owner: owner,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: c.Identity,
|
||||
Secret: c.Secret,
|
||||
},
|
||||
Metadata: metadata,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: updatedAt,
|
||||
UpdatedBy: updatedBy,
|
||||
Status: c.Status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func pageQuery(pm mfclients.Page) (string, error) {
|
||||
mq, _, err := postgres.CreateMetadataQuery("", pm.Metadata)
|
||||
client, err := pgclients.ToClient(dbc)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
var query []string
|
||||
var emq string
|
||||
if mq != "" {
|
||||
query = append(query, mq)
|
||||
}
|
||||
if pm.Identity != "" {
|
||||
query = append(query, "c.identity = :identity")
|
||||
}
|
||||
if pm.Name != "" {
|
||||
query = append(query, "c.name = :name")
|
||||
}
|
||||
if pm.Tag != "" {
|
||||
query = append(query, ":tag = ANY(c.tags)")
|
||||
}
|
||||
if pm.Status != mfclients.AllStatus {
|
||||
query = append(query, "c.status = :status")
|
||||
}
|
||||
// For listing clients that the specified client owns but not sharedby
|
||||
if pm.Owner != "" && pm.SharedBy == "" {
|
||||
query = append(query, "c.owner_id = :owner_id")
|
||||
return mfclients.Client{}, err
|
||||
}
|
||||
|
||||
// For listing clients that the specified client owns and that are shared with the specified client
|
||||
if pm.Owner != "" && pm.SharedBy != "" {
|
||||
query = append(query, "(c.owner_id = :owner_id OR (policies.object IN (SELECT object FROM policies WHERE subject = :shared_by AND :action=ANY(actions)))) AND c.id != :shared_by")
|
||||
}
|
||||
// For listing clients that the specified client is shared with
|
||||
if pm.SharedBy != "" && pm.Owner == "" {
|
||||
query = append(query, "c.owner_id != :shared_by AND (policies.object IN (SELECT object FROM policies WHERE subject = :shared_by AND :action=ANY(actions))) AND c.id != :shared_by")
|
||||
}
|
||||
if len(query) > 0 {
|
||||
emq = fmt.Sprintf("WHERE %s", strings.Join(query, " AND "))
|
||||
if strings.Contains(emq, "policies") {
|
||||
emq = fmt.Sprintf("LEFT JOIN policies ON policies.subject = c.id %s", emq)
|
||||
}
|
||||
}
|
||||
return emq, nil
|
||||
|
||||
}
|
||||
|
||||
func toDBClientsPage(pm mfclients.Page) (dbClientsPage, error) {
|
||||
_, data, err := postgres.CreateMetadataQuery("", pm.Metadata)
|
||||
if err != nil {
|
||||
return dbClientsPage{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||
}
|
||||
return dbClientsPage{
|
||||
Name: pm.Name,
|
||||
Identity: pm.Identity,
|
||||
Metadata: data,
|
||||
Owner: pm.Owner,
|
||||
Total: pm.Total,
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
Status: pm.Status,
|
||||
Tag: pm.Tag,
|
||||
Subject: pm.Subject,
|
||||
Action: pm.Action,
|
||||
SharedBy: pm.SharedBy,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type dbClientsPage struct {
|
||||
Total uint64 `db:"total"`
|
||||
Limit uint64 `db:"limit"`
|
||||
Offset uint64 `db:"offset"`
|
||||
Name string `db:"name"`
|
||||
Owner string `db:"owner_id"`
|
||||
Identity string `db:"identity"`
|
||||
Metadata []byte `db:"metadata"`
|
||||
Tag string `db:"tag"`
|
||||
Status mfclients.Status `db:"status"`
|
||||
GroupID string `db:"group_id"`
|
||||
SharedBy string `db:"shared_by"`
|
||||
Subject string `db:"subject"`
|
||||
Action string `db:"action"`
|
||||
return client, nil
|
||||
}
|
||||
|
@ -12,14 +12,9 @@ import (
|
||||
"github.com/mainflux/mainflux/internal/testsutil"
|
||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
||||
"github.com/mainflux/mainflux/pkg/uuid"
|
||||
cpostgres "github.com/mainflux/mainflux/users/clients/postgres"
|
||||
gpostgres "github.com/mainflux/mainflux/users/groups/postgres"
|
||||
"github.com/mainflux/mainflux/users/policies"
|
||||
ppostgres "github.com/mainflux/mainflux/users/policies/postgres"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -32,8 +27,6 @@ var (
|
||||
password = "$tr0ngPassw0rd"
|
||||
clientIdentity = "client-identity@example.com"
|
||||
clientName = "client name"
|
||||
wrongName = "wrong-name"
|
||||
wrongID = "wrong-id"
|
||||
)
|
||||
|
||||
func TestClientsSave(t *testing.T) {
|
||||
@ -175,948 +168,8 @@ func TestClientsSave(t *testing.T) {
|
||||
rClient, err := repo.Save(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
rClient[0].Credentials.Secret = tc.client.Credentials.Secret
|
||||
assert.Equal(t, tc.client, rClient[0], fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client, rClient))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsRetrieveByID(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: clientName,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: clientIdentity,
|
||||
Secret: password,
|
||||
},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
clients, err := repo.Save(context.Background(), client)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
client = clients[0]
|
||||
|
||||
cases := map[string]struct {
|
||||
ID string
|
||||
err error
|
||||
}{
|
||||
"retrieve existing client": {client.ID, nil},
|
||||
"retrieve non-existing client": {wrongID, errors.ErrNotFound},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
cli, err := repo.RetrieveByID(context.Background(), tc.ID)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client.ID, cli.ID, fmt.Sprintf("retrieve client by ID : client ID : expected %s got %s\n", client.ID, cli.ID))
|
||||
assert.Equal(t, client.Name, cli.Name, fmt.Sprintf("retrieve client by ID : client Name : expected %s got %s\n", client.Name, cli.Name))
|
||||
assert.Equal(t, client.Credentials.Identity, cli.Credentials.Identity, fmt.Sprintf("retrieve client by ID : client Identity : expected %s got %s\n", client.Credentials.Identity, cli.Credentials.Identity))
|
||||
assert.Equal(t, client.Status, cli.Status, fmt.Sprintf("retrieve client by ID : client Status : expected %d got %d\n", client.Status, cli.Status))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsRetrieveByIdentity(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: clientName,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: clientIdentity,
|
||||
Secret: password,
|
||||
},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
_, err := repo.Save(context.Background(), client)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
|
||||
cases := map[string]struct {
|
||||
identity string
|
||||
err error
|
||||
}{
|
||||
"retrieve existing client": {clientIdentity, nil},
|
||||
"retrieve non-existing client": {wrongID, errors.ErrNotFound},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
_, err := repo.RetrieveByIdentity(context.Background(), tc.identity)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsRetrieveAll(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
prepo := ppostgres.NewRepository(database)
|
||||
|
||||
var nClients = uint64(200)
|
||||
var ownerID string
|
||||
|
||||
meta := mfclients.Metadata{
|
||||
"admin": "true",
|
||||
}
|
||||
wrongMeta := mfclients.Metadata{
|
||||
"admin": "false",
|
||||
}
|
||||
var expectedClients = []mfclients.Client{}
|
||||
|
||||
var sharedGroup = mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "shared-group",
|
||||
}
|
||||
_, err := grepo.Save(context.Background(), sharedGroup)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
|
||||
for i := uint64(0); i < nClients; i++ {
|
||||
identity := fmt.Sprintf("TestRetrieveAll%d@example.com", i)
|
||||
client := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: identity,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: identity,
|
||||
Secret: password,
|
||||
},
|
||||
Metadata: mfclients.Metadata{},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
if i == 1 {
|
||||
ownerID = client.ID
|
||||
}
|
||||
if i%10 == 0 {
|
||||
client.Owner = ownerID
|
||||
client.Metadata = meta
|
||||
client.Tags = []string{"Test"}
|
||||
}
|
||||
if i%50 == 0 {
|
||||
client.Status = mfclients.DisabledStatus
|
||||
}
|
||||
_, err := repo.Save(context.Background(), client)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
client.Credentials.Secret = ""
|
||||
expectedClients = append(expectedClients, client)
|
||||
var policy = policies.Policy{
|
||||
Subject: client.ID,
|
||||
Object: sharedGroup.ID,
|
||||
Actions: []string{"c_list"},
|
||||
}
|
||||
err = prepo.Save(context.Background(), policy)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
size uint64
|
||||
pm mfclients.Page
|
||||
response []mfclients.Client
|
||||
}{
|
||||
"retrieve all clients empty page": {
|
||||
pm: mfclients.Page{},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients,
|
||||
size: 200,
|
||||
},
|
||||
"retrieve all clients with limit": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: 50,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients[0:50],
|
||||
size: 50,
|
||||
},
|
||||
"retrieve all clients with offset": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 50,
|
||||
Limit: nClients,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients[50:200],
|
||||
size: 150,
|
||||
},
|
||||
"retrieve all clients with limit and offset": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 50,
|
||||
Limit: 50,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients[50:100],
|
||||
size: 50,
|
||||
},
|
||||
"retrieve all clients with limit and offset not full": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 170,
|
||||
Limit: 50,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients[170:200],
|
||||
size: 30,
|
||||
},
|
||||
"retrieve all clients by metadata": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Metadata: meta,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60],
|
||||
expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130],
|
||||
expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190],
|
||||
},
|
||||
size: 20,
|
||||
},
|
||||
"retrieve clients by wrong metadata": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Metadata: wrongMeta,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients by name": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Name: "TestRetrieveAll3@example.com",
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[3]},
|
||||
size: 1,
|
||||
},
|
||||
"retrieve clients by wrong name": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Name: wrongName,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients by owner": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Owner: ownerID,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60],
|
||||
expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130],
|
||||
expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190],
|
||||
},
|
||||
size: 19,
|
||||
},
|
||||
"retrieve clients by wrong owner": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Owner: wrongID,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients by disabled status": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Status: mfclients.DisabledStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[0], expectedClients[50], expectedClients[100], expectedClients[150]},
|
||||
size: 4,
|
||||
},
|
||||
"retrieve all clients by combined status": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: expectedClients,
|
||||
size: 200,
|
||||
},
|
||||
"retrieve clients by the wrong status": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Status: 10,
|
||||
},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients by tags": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Tag: "Test",
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60],
|
||||
expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130],
|
||||
expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190],
|
||||
},
|
||||
size: 20,
|
||||
},
|
||||
"retrieve clients by wrong tags": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
Tag: "wrongTags",
|
||||
Status: mfclients.AllStatus,
|
||||
},
|
||||
response: []mfclients.Client{},
|
||||
size: 0,
|
||||
},
|
||||
"retrieve all clients by sharedby": {
|
||||
pm: mfclients.Page{
|
||||
Offset: 0,
|
||||
Limit: nClients,
|
||||
Total: nClients,
|
||||
SharedBy: expectedClients[0].ID,
|
||||
Status: mfclients.AllStatus,
|
||||
Action: "c_list",
|
||||
},
|
||||
response: []mfclients.Client{expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60],
|
||||
expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130],
|
||||
expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190],
|
||||
},
|
||||
size: 19,
|
||||
},
|
||||
}
|
||||
for desc, tc := range cases {
|
||||
page, err := repo.RetrieveAll(context.Background(), tc.pm)
|
||||
size := uint64(len(page.Clients))
|
||||
assert.ElementsMatch(t, page.Clients, tc.response, fmt.Sprintf("%s: expected %v got %v\n", desc, tc.response, page.Clients))
|
||||
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", desc, tc.size, size))
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %d\n", desc, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupsMembers(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
crepo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
prepo := ppostgres.NewRepository(database)
|
||||
|
||||
clientA := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "client-memberships",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client-memberships1@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Metadata: mfclients.Metadata{},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
clientB := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "client-memberships",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client-memberships2@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Metadata: mfclients.Metadata{},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
group := mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "group-membership",
|
||||
Metadata: mfclients.Metadata{},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
policyA := policies.Policy{
|
||||
Subject: clientA.ID,
|
||||
Object: group.ID,
|
||||
Actions: []string{"g_list"},
|
||||
}
|
||||
policyB := policies.Policy{
|
||||
Subject: clientB.ID,
|
||||
Object: group.ID,
|
||||
Actions: []string{"g_list"},
|
||||
}
|
||||
|
||||
_, err := crepo.Save(context.Background(), clientA)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save client: expected %v got %s\n", nil, err))
|
||||
clientA.Credentials.Secret = ""
|
||||
_, err = crepo.Save(context.Background(), clientB)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save client: expected %v got %s\n", nil, err))
|
||||
clientB.Credentials.Secret = ""
|
||||
_, err = grepo.Save(context.Background(), group)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save group: expected %v got %s\n", nil, err))
|
||||
err = prepo.Save(context.Background(), policyA)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save policy: expected %v got %s\n", nil, err))
|
||||
err = prepo.Save(context.Background(), policyB)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save policy: expected %v got %s\n", nil, err))
|
||||
|
||||
cases := map[string]struct {
|
||||
ID string
|
||||
err error
|
||||
}{
|
||||
"retrieve members for existing group": {group.ID, nil},
|
||||
"retrieve members for non-existing group": {wrongID, nil},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
mp, err := crepo.Members(context.Background(), tc.ID, mfclients.Page{Total: 10, Offset: 0, Limit: 10, Status: mfclients.AllStatus, Subject: clientB.ID, Action: "g_list"})
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
if tc.ID == group.ID {
|
||||
assert.ElementsMatch(t, mp.Members, []mfclients.Client{clientA}, fmt.Sprintf("%s: expected %v got %v\n", desc, []mfclients.Client{clientA, clientB}, mp.Members))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsUpdateMetadata(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Metadata: mfclients.Metadata{
|
||||
"name": "enabled-client",
|
||||
},
|
||||
Tags: []string{"enabled", "tag1"},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
client2 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "disabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Metadata: mfclients.Metadata{
|
||||
"name": "disabled-client",
|
||||
},
|
||||
Tags: []string{"disabled", "tag1"},
|
||||
Status: mfclients.DisabledStatus,
|
||||
}
|
||||
|
||||
clients1, err := repo.Save(context.Background(), client1)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client with metadata: expected %v got %s\n", nil, err))
|
||||
clients2, err := repo.Save(context.Background(), client2)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err))
|
||||
client1 = clients1[0]
|
||||
client2 = clients2[0]
|
||||
|
||||
ucases := []struct {
|
||||
desc string
|
||||
update string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update metadata for enabled client",
|
||||
update: "metadata",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "metadata",
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update metadata for disabled client",
|
||||
update: "metadata",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "metadata",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update name for enabled client",
|
||||
update: "name",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Name: "updated name",
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update name for disabled client",
|
||||
update: "name",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Name: "updated name",
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update name and metadata for enabled client",
|
||||
update: "both",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Name: "updated name and metadata",
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "name and metadata",
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update name and metadata for a disabled client",
|
||||
update: "both",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Name: "updated name and metadata",
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "name and metadata",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update metadata for invalid client",
|
||||
update: "metadata",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "metadata",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update name for invalid client",
|
||||
update: "name",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Name: "updated name",
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update name and metadata for invalid client",
|
||||
update: "both",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Name: "updated name and metadata",
|
||||
Metadata: mfclients.Metadata{
|
||||
"update": "name and metadata",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range ucases {
|
||||
expected, err := repo.Update(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
if tc.client.Name != "" {
|
||||
assert.Equal(t, expected.Name, tc.client.Name, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, expected.Name, tc.client.Name))
|
||||
}
|
||||
if tc.client.Metadata != nil {
|
||||
assert.Equal(t, expected.Metadata, tc.client.Metadata, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, expected.Metadata, tc.client.Metadata))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsUpdateTags(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client-with-tags",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update-tags@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Tags: []string{"test", "enabled"},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
client2 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "disabled-client-with-tags",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update-tags@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Tags: []string{"test", "disabled"},
|
||||
Status: mfclients.DisabledStatus,
|
||||
}
|
||||
|
||||
clients1, err := repo.Save(context.Background(), client1)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client with tags: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client1.ID, client1.ID, fmt.Sprintf("add new client with tags: expected %v got %s\n", nil, err))
|
||||
}
|
||||
client1 = clients1[0]
|
||||
clients2, err := repo.Save(context.Background(), client2)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client with tags: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client2.ID, client2.ID, fmt.Sprintf("add new disabled client with tags: expected %v got %s\n", nil, err))
|
||||
}
|
||||
client2 = clients2[0]
|
||||
ucases := []struct {
|
||||
desc string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update tags for enabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Tags: []string{"updated"},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update tags for disabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Tags: []string{"updated"},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update tags for invalid client",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Tags: []string{"updated"},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range ucases {
|
||||
expected, err := repo.UpdateTags(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.client.Tags, expected.Tags, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Tags, expected.Tags))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsUpdateSecret(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
client2 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "disabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Status: mfclients.DisabledStatus,
|
||||
}
|
||||
|
||||
rClients1, err := repo.Save(context.Background(), client1)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client1.ID, rClients1[0].ID, fmt.Sprintf("add new client: expected %v got %s\n", nil, err))
|
||||
}
|
||||
rClients2, err := repo.Save(context.Background(), client2)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client2.ID, rClients2[0].ID, fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err))
|
||||
}
|
||||
|
||||
ucases := []struct {
|
||||
desc string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update secret for enabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update@example.com",
|
||||
Secret: "newpassword",
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update secret for disabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update@example.com",
|
||||
Secret: "newpassword",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update secret for invalid client",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client3-update@example.com",
|
||||
Secret: "newpassword",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range ucases {
|
||||
_, err := repo.UpdateSecret(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
c, err := repo.RetrieveByIdentity(context.Background(), tc.client.Credentials.Identity)
|
||||
require.Nil(t, err, fmt.Sprintf("retrieve client by id during update of secret unexpected error: %s", err))
|
||||
assert.Equal(t, tc.client.Credentials.Secret, c.Credentials.Secret, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Credentials.Secret, c.Credentials.Secret))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsUpdateIdentity(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
client2 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "disabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Status: mfclients.DisabledStatus,
|
||||
}
|
||||
|
||||
rClients1, err := repo.Save(context.Background(), client1)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client1.ID, rClients1[0].ID, fmt.Sprintf("add new client: expected %v got %s\n", nil, err))
|
||||
}
|
||||
rClients2, err := repo.Save(context.Background(), client2)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client2.ID, rClients2[0].ID, fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err))
|
||||
}
|
||||
|
||||
ucases := []struct {
|
||||
desc string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update identity for enabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-updated@example.com",
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update identity for disabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-updated@example.com",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update identity for invalid client",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client3-updated@example.com",
|
||||
},
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range ucases {
|
||||
expected, err := repo.UpdateIdentity(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.client.Credentials.Identity, expected.Credentials.Identity, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Credentials.Identity, expected.Credentials.Identity))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsUpdateOwner(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client-with-owner",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update-owner@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Owner: testsutil.GenerateUUID(t, idProvider),
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
client2 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "disabled-client-with-owner",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client2-update-owner@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Owner: testsutil.GenerateUUID(t, idProvider),
|
||||
Status: mfclients.DisabledStatus,
|
||||
}
|
||||
|
||||
clients1, err := repo.Save(context.Background(), client1)
|
||||
client1 = clients1[0]
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client with owner: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client1.ID, client1.ID, fmt.Sprintf("add new client with owner: expected %v got %s\n", nil, err))
|
||||
}
|
||||
clients2, err := repo.Save(context.Background(), client2)
|
||||
client2 = clients2[0]
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client with owner: expected %v got %s\n", nil, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, client2.ID, client2.ID, fmt.Sprintf("add new disabled client with owner: expected %v got %s\n", nil, err))
|
||||
}
|
||||
ucases := []struct {
|
||||
desc string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update owner for enabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Owner: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update owner for disabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client2.ID,
|
||||
Owner: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update owner for invalid client",
|
||||
client: mfclients.Client{
|
||||
ID: wrongID,
|
||||
Owner: testsutil.GenerateUUID(t, idProvider),
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range ucases {
|
||||
expected, err := repo.UpdateOwner(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.client.Owner, expected.Owner, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Owner, expected.Owner))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsChangeStatus(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := cpostgres.NewRepository(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
Name: "enabled-client",
|
||||
Credentials: mfclients.Credentials{
|
||||
Identity: "client1-update@example.com",
|
||||
Secret: password,
|
||||
},
|
||||
Status: mfclients.EnabledStatus,
|
||||
}
|
||||
|
||||
clients1, err := repo.Save(context.Background(), client1)
|
||||
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client: expected %v got %s\n", nil, err))
|
||||
client1 = clients1[0]
|
||||
|
||||
ucases := []struct {
|
||||
desc string
|
||||
client mfclients.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "change client status for an enabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Status: 0,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "change client status for a disabled client",
|
||||
client: mfclients.Client{
|
||||
ID: client1.ID,
|
||||
Status: 1,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "change client status for non-existing client",
|
||||
client: mfclients.Client{
|
||||
ID: "invalid",
|
||||
Status: 2,
|
||||
},
|
||||
err: errors.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range ucases {
|
||||
expected, err := repo.ChangeStatus(context.Background(), tc.client)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.client.Status, expected.Status, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.client.Status, expected.Status))
|
||||
rClient.Credentials.Secret = tc.client.Credentials.Secret
|
||||
assert.Equal(t, tc.client, rClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client, rClient))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/mainflux/mainflux/internal/apiutil"
|
||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
"github.com/mainflux/mainflux/users/clients/postgres"
|
||||
"github.com/mainflux/mainflux/users/jwt"
|
||||
"github.com/mainflux/mainflux/users/policies"
|
||||
)
|
||||
@ -50,7 +51,7 @@ type Service interface {
|
||||
}
|
||||
|
||||
type service struct {
|
||||
clients mfclients.Repository
|
||||
clients postgres.Repository
|
||||
policies policies.Repository
|
||||
idProvider mainflux.IDProvider
|
||||
hasher Hasher
|
||||
@ -60,7 +61,7 @@ type service struct {
|
||||
}
|
||||
|
||||
// NewService returns a new Clients service implementation.
|
||||
func NewService(c mfclients.Repository, p policies.Repository, t jwt.Repository, e Emailer, h Hasher, idp mainflux.IDProvider, pr *regexp.Regexp) Service {
|
||||
func NewService(c postgres.Repository, p policies.Repository, t jwt.Repository, e Emailer, h Hasher, idp mainflux.IDProvider, pr *regexp.Regexp) Service {
|
||||
return service{
|
||||
clients: c,
|
||||
policies: p,
|
||||
@ -105,7 +106,7 @@ func (svc service) RegisterClient(ctx context.Context, token string, cli mfclien
|
||||
return mfclients.Client{}, err
|
||||
}
|
||||
|
||||
return client[0], nil
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (svc service) IssueToken(ctx context.Context, identity, secret string) (jwt.Token, error) {
|
||||
|
@ -1,5 +0,0 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package postgres contains the database implementation of groups repository layer.
|
||||
package postgres
|
@ -12,9 +12,9 @@ import (
|
||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
||||
gpostgres "github.com/mainflux/mainflux/pkg/groups/postgres"
|
||||
"github.com/mainflux/mainflux/pkg/uuid"
|
||||
cpostgres "github.com/mainflux/mainflux/users/clients/postgres"
|
||||
gpostgres "github.com/mainflux/mainflux/users/groups/postgres"
|
||||
"github.com/mainflux/mainflux/users/policies"
|
||||
ppostgres "github.com/mainflux/mainflux/users/policies/postgres"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -29,7 +29,7 @@ func TestPoliciesSave(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := ppostgres.NewRepository(database)
|
||||
crepo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
grepo := gpostgres.New(database)
|
||||
|
||||
group := mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
@ -50,7 +50,7 @@ func TestPoliciesSave(t *testing.T) {
|
||||
|
||||
clients, err := crepo.Save(context.Background(), client)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
client = clients[0]
|
||||
client = clients
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
@ -89,7 +89,7 @@ func TestPoliciesEvaluate(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := ppostgres.NewRepository(database)
|
||||
crepo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
grepo := gpostgres.New(database)
|
||||
|
||||
client1 := mfclients.Client{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
@ -116,10 +116,10 @@ func TestPoliciesEvaluate(t *testing.T) {
|
||||
|
||||
clients1, err := crepo.Save(context.Background(), client1)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
client1 = clients1[0]
|
||||
client1 = clients1
|
||||
clients2, err := crepo.Save(context.Background(), client2)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
client2 = clients2[0]
|
||||
client2 = clients2
|
||||
group, err = grepo.Save(context.Background(), group)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
|
||||
@ -178,7 +178,7 @@ func TestPoliciesRetrieve(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := ppostgres.NewRepository(database)
|
||||
crepo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
grepo := gpostgres.New(database)
|
||||
|
||||
group := mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
@ -199,7 +199,7 @@ func TestPoliciesRetrieve(t *testing.T) {
|
||||
|
||||
clients, err := crepo.Save(context.Background(), client)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
client = clients[0]
|
||||
client = clients
|
||||
|
||||
policy := policies.Policy{
|
||||
OwnerID: client.ID,
|
||||
@ -234,7 +234,7 @@ func TestPoliciesUpdate(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := ppostgres.NewRepository(database)
|
||||
crepo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
grepo := gpostgres.New(database)
|
||||
|
||||
group := mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
@ -352,7 +352,7 @@ func TestPoliciesRetrievalAll(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := ppostgres.NewRepository(database)
|
||||
crepo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
grepo := gpostgres.New(database)
|
||||
|
||||
var nPolicies = uint64(10)
|
||||
|
||||
@ -377,10 +377,10 @@ func TestPoliciesRetrievalAll(t *testing.T) {
|
||||
|
||||
clientsA, err := crepo.Save(context.Background(), clientA)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
clientA = clientsA[0]
|
||||
clientA = clientsA
|
||||
clientsB, err := crepo.Save(context.Background(), clientB)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
clientB = clientsB[0]
|
||||
clientB = clientsB
|
||||
|
||||
grps := []string{}
|
||||
for i := uint64(0); i < nPolicies; i++ {
|
||||
@ -628,7 +628,7 @@ func TestPoliciesDelete(t *testing.T) {
|
||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||
repo := ppostgres.NewRepository(database)
|
||||
crepo := cpostgres.NewRepository(database)
|
||||
grepo := gpostgres.NewRepository(database)
|
||||
grepo := gpostgres.New(database)
|
||||
|
||||
group := mfgroups.Group{
|
||||
ID: testsutil.GenerateUUID(t, idProvider),
|
||||
@ -649,7 +649,7 @@ func TestPoliciesDelete(t *testing.T) {
|
||||
|
||||
clients, err := crepo.Save(context.Background(), client)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
client = clients[0]
|
||||
client = clients
|
||||
|
||||
policy := policies.Policy{
|
||||
OwnerID: client.ID,
|
||||
|
Loading…
x
Reference in New Issue
Block a user