mirror of
https://github.com/mainflux/mainflux.git
synced 2025-04-29 13:49:28 +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"
|
grpcserver "github.com/mainflux/mainflux/internal/server/grpc"
|
||||||
httpserver "github.com/mainflux/mainflux/internal/server/http"
|
httpserver "github.com/mainflux/mainflux/internal/server/http"
|
||||||
mflog "github.com/mainflux/mainflux/logger"
|
mflog "github.com/mainflux/mainflux/logger"
|
||||||
|
gpostgres "github.com/mainflux/mainflux/pkg/groups/postgres"
|
||||||
"github.com/mainflux/mainflux/pkg/uuid"
|
"github.com/mainflux/mainflux/pkg/uuid"
|
||||||
"github.com/mainflux/mainflux/things/clients"
|
"github.com/mainflux/mainflux/things/clients"
|
||||||
capi "github.com/mainflux/mainflux/things/clients/api"
|
capi "github.com/mainflux/mainflux/things/clients/api"
|
||||||
@ -36,7 +37,6 @@ import (
|
|||||||
ctracing "github.com/mainflux/mainflux/things/clients/tracing"
|
ctracing "github.com/mainflux/mainflux/things/clients/tracing"
|
||||||
"github.com/mainflux/mainflux/things/groups"
|
"github.com/mainflux/mainflux/things/groups"
|
||||||
gapi "github.com/mainflux/mainflux/things/groups/api"
|
gapi "github.com/mainflux/mainflux/things/groups/api"
|
||||||
gpostgres "github.com/mainflux/mainflux/things/groups/postgres"
|
|
||||||
chcache "github.com/mainflux/mainflux/things/groups/redis"
|
chcache "github.com/mainflux/mainflux/things/groups/redis"
|
||||||
gtracing "github.com/mainflux/mainflux/things/groups/tracing"
|
gtracing "github.com/mainflux/mainflux/things/groups/tracing"
|
||||||
tpolicies "github.com/mainflux/mainflux/things/policies"
|
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) {
|
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)
|
database := postgres.NewDatabase(db, dbConfig, tracer)
|
||||||
cRepo := cpostgres.NewRepository(database)
|
cRepo := cpostgres.NewRepository(database)
|
||||||
gRepo := gpostgres.NewRepository(database)
|
gRepo := gpostgres.New(database)
|
||||||
pRepo := ppostgres.NewRepository(database)
|
pRepo := ppostgres.NewRepository(database)
|
||||||
|
|
||||||
idp := uuid.New()
|
idp := uuid.New()
|
||||||
|
@ -29,16 +29,16 @@ import (
|
|||||||
httpserver "github.com/mainflux/mainflux/internal/server/http"
|
httpserver "github.com/mainflux/mainflux/internal/server/http"
|
||||||
mflog "github.com/mainflux/mainflux/logger"
|
mflog "github.com/mainflux/mainflux/logger"
|
||||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
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/pkg/uuid"
|
||||||
"github.com/mainflux/mainflux/users/clients"
|
"github.com/mainflux/mainflux/users/clients"
|
||||||
capi "github.com/mainflux/mainflux/users/clients/api"
|
capi "github.com/mainflux/mainflux/users/clients/api"
|
||||||
"github.com/mainflux/mainflux/users/clients/emailer"
|
"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"
|
ucache "github.com/mainflux/mainflux/users/clients/redis"
|
||||||
ctracing "github.com/mainflux/mainflux/users/clients/tracing"
|
ctracing "github.com/mainflux/mainflux/users/clients/tracing"
|
||||||
"github.com/mainflux/mainflux/users/groups"
|
"github.com/mainflux/mainflux/users/groups"
|
||||||
gapi "github.com/mainflux/mainflux/users/groups/api"
|
gapi "github.com/mainflux/mainflux/users/groups/api"
|
||||||
gpostgres "github.com/mainflux/mainflux/users/groups/postgres"
|
|
||||||
gcache "github.com/mainflux/mainflux/users/groups/redis"
|
gcache "github.com/mainflux/mainflux/users/groups/redis"
|
||||||
gtracing "github.com/mainflux/mainflux/users/groups/tracing"
|
gtracing "github.com/mainflux/mainflux/users/groups/tracing"
|
||||||
"github.com/mainflux/mainflux/users/hasher"
|
"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) {
|
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)
|
database := postgres.NewDatabase(db, dbConfig, tracer)
|
||||||
cRepo := cpostgres.NewRepository(database)
|
cRepo := uclients.NewRepository(database)
|
||||||
gRepo := gpostgres.NewRepository(database)
|
gRepo := gpostgres.New(database)
|
||||||
pRepo := ppostgres.NewRepository(database)
|
pRepo := ppostgres.NewRepository(database)
|
||||||
|
|
||||||
idp := uuid.New()
|
idp := uuid.New()
|
||||||
@ -250,7 +250,7 @@ func newService(ctx context.Context, db *sqlx.DB, dbConfig pgClient.Config, esCl
|
|||||||
return csvc, gsvc, psvc
|
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()
|
id, err := uuid.New().ID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -68,9 +68,6 @@ type MembersPage struct {
|
|||||||
|
|
||||||
// Repository specifies an account persistence API.
|
// Repository specifies an account persistence API.
|
||||||
type Repository interface {
|
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 retrieves client by its unique ID.
|
||||||
RetrieveByID(ctx context.Context, id string) (Client, error)
|
RetrieveByID(ctx context.Context, id string) (Client, error)
|
||||||
@ -101,8 +98,6 @@ type Repository interface {
|
|||||||
|
|
||||||
// ChangeStatus changes client status to enabled or disabled
|
// ChangeStatus changes client status to enabled or disabled
|
||||||
ChangeStatus(ctx context.Context, client Client) (Client, error)
|
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.
|
// 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"
|
"github.com/jmoiron/sqlx"
|
||||||
pgClient "github.com/mainflux/mainflux/internal/clients/postgres"
|
pgClient "github.com/mainflux/mainflux/internal/clients/postgres"
|
||||||
"github.com/mainflux/mainflux/internal/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"
|
dockertest "github.com/ory/dockertest/v3"
|
||||||
"github.com/ory/dockertest/v3/docker"
|
"github.com/ory/dockertest/v3/docker"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
@ -75,7 +75,8 @@ func TestMain(m *testing.M) {
|
|||||||
SSLKey: "",
|
SSLKey: "",
|
||||||
SSLRootCert: "",
|
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)
|
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
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -11,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/mainflux/mainflux/internal/postgres"
|
"github.com/mainflux/mainflux/internal/postgres"
|
||||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||||
"github.com/mainflux/mainflux/pkg/errors"
|
"github.com/mainflux/mainflux/pkg/errors"
|
||||||
@ -23,9 +21,9 @@ type groupRepository struct {
|
|||||||
db postgres.Database
|
db postgres.Database
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRepository instantiates a PostgreSQL implementation of group
|
// New instantiates a PostgreSQL implementation of group
|
||||||
// repository.
|
// repository.
|
||||||
func NewRepository(db postgres.Database) mfgroups.Repository {
|
func New(db postgres.Database) mfgroups.Repository {
|
||||||
return &groupRepository{
|
return &groupRepository{
|
||||||
db: db,
|
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)
|
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)
|
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;`
|
RETURNING id, name, description, owner_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, status;`
|
||||||
|
|
||||||
dbg, err := toDBGroup(g)
|
dbg, err := toDBGroup(g)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mfgroups.Group{}, err
|
return mfgroups.Group{}, err
|
||||||
@ -56,89 +53,6 @@ func (repo groupRepository) Save(ctx context.Context, g mfgroups.Group) (mfgroup
|
|||||||
return toGroup(dbg)
|
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) {
|
func (repo groupRepository) Memberships(ctx context.Context, clientID string, gm mfgroups.GroupsPage) (mfgroups.MembershipsPage, error) {
|
||||||
var q string
|
var q string
|
||||||
query, err := buildQuery(gm)
|
query, err := buildQuery(gm)
|
||||||
@ -267,6 +181,80 @@ func (repo groupRepository) ChangeStatus(ctx context.Context, group mfgroups.Gro
|
|||||||
return toGroup(dbg)
|
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 {
|
func buildHierachy(gm mfgroups.GroupsPage) string {
|
||||||
query := ""
|
query := ""
|
||||||
switch {
|
switch {
|
||||||
@ -446,3 +434,19 @@ type dbGroupPage struct {
|
|||||||
Action string `db:"action"`
|
Action string `db:"action"`
|
||||||
Status mfclients.Status `db:"status"`
|
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"
|
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||||
"github.com/mainflux/mainflux/pkg/errors"
|
"github.com/mainflux/mainflux/pkg/errors"
|
||||||
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
||||||
|
gpostgres "github.com/mainflux/mainflux/pkg/groups/postgres"
|
||||||
"github.com/mainflux/mainflux/pkg/uuid"
|
"github.com/mainflux/mainflux/pkg/uuid"
|
||||||
cpostgres "github.com/mainflux/mainflux/users/clients/postgres"
|
cpostgres "github.com/mainflux/mainflux/users/clients/postgres"
|
||||||
gpostgres "github.com/mainflux/mainflux/users/groups/postgres"
|
|
||||||
"github.com/mainflux/mainflux/users/policies"
|
"github.com/mainflux/mainflux/users/policies"
|
||||||
ppostgres "github.com/mainflux/mainflux/users/policies/postgres"
|
ppostgres "github.com/mainflux/mainflux/users/policies/postgres"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -45,7 +45,7 @@ var (
|
|||||||
|
|
||||||
func TestGroupSave(t *testing.T) {
|
func TestGroupSave(t *testing.T) {
|
||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
groupRepo := gpostgres.NewRepository(database)
|
groupRepo := gpostgres.New(database)
|
||||||
|
|
||||||
usrID := testsutil.GenerateUUID(t, idProvider)
|
usrID := testsutil.GenerateUUID(t, idProvider)
|
||||||
grpID := testsutil.GenerateUUID(t, idProvider)
|
grpID := testsutil.GenerateUUID(t, idProvider)
|
||||||
@ -173,7 +173,7 @@ func TestGroupSave(t *testing.T) {
|
|||||||
|
|
||||||
func TestGroupRetrieveByID(t *testing.T) {
|
func TestGroupRetrieveByID(t *testing.T) {
|
||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
groupRepo := gpostgres.NewRepository(database)
|
groupRepo := gpostgres.New(database)
|
||||||
|
|
||||||
uid := testsutil.GenerateUUID(t, idProvider)
|
uid := testsutil.GenerateUUID(t, idProvider)
|
||||||
group1 := mfgroups.Group{
|
group1 := mfgroups.Group{
|
||||||
@ -221,7 +221,7 @@ func TestGroupRetrieveByID(t *testing.T) {
|
|||||||
|
|
||||||
func TestGroupRetrieveAll(t *testing.T) {
|
func TestGroupRetrieveAll(t *testing.T) {
|
||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
groupRepo := gpostgres.NewRepository(database)
|
groupRepo := gpostgres.New(database)
|
||||||
|
|
||||||
var nGroups = uint64(200)
|
var nGroups = uint64(200)
|
||||||
var ownerID = testsutil.GenerateUUID(t, idProvider)
|
var ownerID = testsutil.GenerateUUID(t, idProvider)
|
||||||
@ -339,7 +339,7 @@ func TestGroupRetrieveAll(t *testing.T) {
|
|||||||
|
|
||||||
func TestGroupUpdate(t *testing.T) {
|
func TestGroupUpdate(t *testing.T) {
|
||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
groupRepo := gpostgres.NewRepository(database)
|
groupRepo := gpostgres.New(database)
|
||||||
|
|
||||||
uid := testsutil.GenerateUUID(t, idProvider)
|
uid := testsutil.GenerateUUID(t, idProvider)
|
||||||
|
|
||||||
@ -464,7 +464,7 @@ func TestGroupUpdate(t *testing.T) {
|
|||||||
func TestClientsMemberships(t *testing.T) {
|
func TestClientsMemberships(t *testing.T) {
|
||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
crepo := cpostgres.NewRepository(database)
|
crepo := cpostgres.NewRepository(database)
|
||||||
grepo := gpostgres.NewRepository(database)
|
grepo := gpostgres.New(database)
|
||||||
prepo := ppostgres.NewRepository(database)
|
prepo := ppostgres.NewRepository(database)
|
||||||
|
|
||||||
clientA := mfclients.Client{
|
clientA := mfclients.Client{
|
||||||
@ -535,7 +535,7 @@ func TestClientsMemberships(t *testing.T) {
|
|||||||
|
|
||||||
func TestGroupChangeStatus(t *testing.T) {
|
func TestGroupChangeStatus(t *testing.T) {
|
||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
repo := gpostgres.NewRepository(database)
|
repo := gpostgres.New(database)
|
||||||
|
|
||||||
group1 := mfgroups.Group{
|
group1 := mfgroups.Group{
|
||||||
ID: testsutil.GenerateUUID(t, idProvider),
|
ID: testsutil.GenerateUUID(t, idProvider),
|
@ -6,29 +6,38 @@ package postgres
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jackc/pgtype" // required for SQL access
|
// required for SQL access.
|
||||||
|
|
||||||
"github.com/mainflux/mainflux/internal/postgres"
|
"github.com/mainflux/mainflux/internal/postgres"
|
||||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
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/errors"
|
||||||
"github.com/mainflux/mainflux/pkg/groups"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ mfclients.Repository = (*clientRepo)(nil)
|
var _ mfclients.Repository = (*clientRepo)(nil)
|
||||||
|
|
||||||
type clientRepo struct {
|
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
|
// NewRepository instantiates a PostgreSQL
|
||||||
// implementation of Clients repository.
|
// implementation of Clients repository.
|
||||||
func NewRepository(db postgres.Database) mfclients.Repository {
|
func NewRepository(db postgres.Database) Repository {
|
||||||
return &clientRepo{
|
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) {
|
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 {
|
if err != nil {
|
||||||
return []mfclients.Client{}, errors.Wrap(errors.ErrCreateEntity, err)
|
return []mfclients.Client{}, errors.Wrap(errors.ErrCreateEntity, err)
|
||||||
}
|
}
|
||||||
|
var clients []mfclients.Client
|
||||||
|
|
||||||
for _, cli := range cs {
|
for _, cli := range cs {
|
||||||
q := `INSERT INTO clients (id, name, tags, owner_id, identity, secret, metadata, created_at, updated_at, updated_by, status)
|
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)
|
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`
|
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 {
|
if err != nil {
|
||||||
return []mfclients.Client{}, errors.Wrap(errors.ErrCreateEntity, err)
|
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 {
|
if err := tx.Rollback(); err != nil {
|
||||||
return []mfclients.Client{}, postgres.HandleError(err, errors.ErrCreateEntity)
|
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 {
|
if err = tx.Commit(); err != nil {
|
||||||
return []mfclients.Client{}, errors.Wrap(errors.ErrCreateEntity, err)
|
return []mfclients.Client{}, errors.Wrap(errors.ErrCreateEntity, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cs, nil
|
return clients, 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo clientRepo) RetrieveBySecret(ctx context.Context, key string) (mfclients.Client, error) {
|
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
|
FROM clients
|
||||||
WHERE secret = $1 AND status = %d`, mfclients.EnabledStatus)
|
WHERE secret = $1 AND status = %d`, mfclients.EnabledStatus)
|
||||||
|
|
||||||
dbc := dbClient{
|
dbc := pgclients.DBClient{
|
||||||
Secret: key,
|
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 {
|
if err == sql.ErrNoRows {
|
||||||
return mfclients.Client{}, errors.Wrap(errors.ErrNotFound, err)
|
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 mfclients.Client{}, errors.Wrap(errors.ErrViewEntity, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return toClient(dbc)
|
return pgclients.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"`
|
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,9 @@ import (
|
|||||||
"github.com/mainflux/mainflux/internal/testsutil"
|
"github.com/mainflux/mainflux/internal/testsutil"
|
||||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||||
"github.com/mainflux/mainflux/pkg/errors"
|
"github.com/mainflux/mainflux/pkg/errors"
|
||||||
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
|
||||||
"github.com/mainflux/mainflux/pkg/uuid"
|
"github.com/mainflux/mainflux/pkg/uuid"
|
||||||
cpostgres "github.com/mainflux/mainflux/things/clients/postgres"
|
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/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const maxNameSize = 1024
|
const maxNameSize = 1024
|
||||||
@ -29,8 +24,6 @@ var (
|
|||||||
invalidName = strings.Repeat("m", maxNameSize+10)
|
invalidName = strings.Repeat("m", maxNameSize+10)
|
||||||
clientIdentity = "client-identity@example.com"
|
clientIdentity = "client-identity@example.com"
|
||||||
clientName = "client name"
|
clientName = "client name"
|
||||||
wrongName = "wrong-name"
|
|
||||||
wrongID = "wrong-id"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestClientsSave(t *testing.T) {
|
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"
|
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||||
"github.com/mainflux/mainflux/pkg/errors"
|
"github.com/mainflux/mainflux/pkg/errors"
|
||||||
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
||||||
|
"github.com/mainflux/mainflux/things/clients/postgres"
|
||||||
tpolicies "github.com/mainflux/mainflux/things/policies"
|
tpolicies "github.com/mainflux/mainflux/things/policies"
|
||||||
upolicies "github.com/mainflux/mainflux/users/policies"
|
upolicies "github.com/mainflux/mainflux/users/policies"
|
||||||
)
|
)
|
||||||
@ -31,14 +32,14 @@ const (
|
|||||||
type service struct {
|
type service struct {
|
||||||
uauth upolicies.AuthServiceClient
|
uauth upolicies.AuthServiceClient
|
||||||
policies tpolicies.Service
|
policies tpolicies.Service
|
||||||
clients mfclients.Repository
|
clients postgres.Repository
|
||||||
clientCache Cache
|
clientCache Cache
|
||||||
idProvider mainflux.IDProvider
|
idProvider mainflux.IDProvider
|
||||||
grepo mfgroups.Repository
|
grepo mfgroups.Repository
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService returns a new Clients service implementation.
|
// 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{
|
return service{
|
||||||
uauth: uauth,
|
uauth: uauth,
|
||||||
policies: policies,
|
policies: policies,
|
||||||
@ -198,6 +199,7 @@ func (svc service) UpdateClientSecret(ctx context.Context, token, id, key string
|
|||||||
},
|
},
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
UpdatedBy: userID,
|
UpdatedBy: userID,
|
||||||
|
Status: mfclients.EnabledStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
return svc.clients.UpdateSecret(ctx, client)
|
return svc.clients.UpdateSecret(ctx, client)
|
||||||
@ -217,6 +219,7 @@ func (svc service) UpdateClientOwner(ctx context.Context, token string, cli mfcl
|
|||||||
Owner: cli.Owner,
|
Owner: cli.Owner,
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
UpdatedBy: userID,
|
UpdatedBy: userID,
|
||||||
|
Status: mfclients.EnabledStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
return svc.clients.UpdateOwner(ctx, client)
|
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"
|
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||||
"github.com/mainflux/mainflux/pkg/errors"
|
"github.com/mainflux/mainflux/pkg/errors"
|
||||||
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
||||||
|
gpostgres "github.com/mainflux/mainflux/pkg/groups/postgres"
|
||||||
"github.com/mainflux/mainflux/pkg/uuid"
|
"github.com/mainflux/mainflux/pkg/uuid"
|
||||||
cpostgres "github.com/mainflux/mainflux/things/clients/postgres"
|
cpostgres "github.com/mainflux/mainflux/things/clients/postgres"
|
||||||
gpostgres "github.com/mainflux/mainflux/things/groups/postgres"
|
|
||||||
"github.com/mainflux/mainflux/things/policies"
|
"github.com/mainflux/mainflux/things/policies"
|
||||||
ppostgres "github.com/mainflux/mainflux/things/policies/postgres"
|
ppostgres "github.com/mainflux/mainflux/things/policies/postgres"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -28,7 +28,7 @@ var (
|
|||||||
func TestPoliciesSave(t *testing.T) {
|
func TestPoliciesSave(t *testing.T) {
|
||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
repo := ppostgres.NewRepository(database)
|
repo := ppostgres.NewRepository(database)
|
||||||
grepo := gpostgres.NewRepository(database)
|
grepo := gpostgres.New(database)
|
||||||
|
|
||||||
uid := testsutil.GenerateUUID(t, idProvider)
|
uid := testsutil.GenerateUUID(t, idProvider)
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ func TestPoliciesEvaluate(t *testing.T) {
|
|||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
repo := ppostgres.NewRepository(database)
|
repo := ppostgres.NewRepository(database)
|
||||||
crepo := cpostgres.NewRepository(database)
|
crepo := cpostgres.NewRepository(database)
|
||||||
grepo := gpostgres.NewRepository(database)
|
grepo := gpostgres.New(database)
|
||||||
|
|
||||||
client1 := mfclients.Client{
|
client1 := mfclients.Client{
|
||||||
ID: testsutil.GenerateUUID(t, idProvider),
|
ID: testsutil.GenerateUUID(t, idProvider),
|
||||||
@ -167,7 +167,7 @@ func TestPoliciesRetrieve(t *testing.T) {
|
|||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
repo := ppostgres.NewRepository(database)
|
repo := ppostgres.NewRepository(database)
|
||||||
crepo := cpostgres.NewRepository(database)
|
crepo := cpostgres.NewRepository(database)
|
||||||
grepo := gpostgres.NewRepository(database)
|
grepo := gpostgres.New(database)
|
||||||
|
|
||||||
uid := testsutil.GenerateUUID(t, idProvider)
|
uid := testsutil.GenerateUUID(t, idProvider)
|
||||||
|
|
||||||
@ -225,7 +225,7 @@ func TestPoliciesUpdate(t *testing.T) {
|
|||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
repo := ppostgres.NewRepository(database)
|
repo := ppostgres.NewRepository(database)
|
||||||
crepo := cpostgres.NewRepository(database)
|
crepo := cpostgres.NewRepository(database)
|
||||||
grepo := gpostgres.NewRepository(database)
|
grepo := gpostgres.New(database)
|
||||||
|
|
||||||
client := mfclients.Client{
|
client := mfclients.Client{
|
||||||
ID: testsutil.GenerateUUID(t, idProvider),
|
ID: testsutil.GenerateUUID(t, idProvider),
|
||||||
@ -347,7 +347,7 @@ func TestPoliciesRetrievalAll(t *testing.T) {
|
|||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
repo := ppostgres.NewRepository(database)
|
repo := ppostgres.NewRepository(database)
|
||||||
crepo := cpostgres.NewRepository(database)
|
crepo := cpostgres.NewRepository(database)
|
||||||
grepo := gpostgres.NewRepository(database)
|
grepo := gpostgres.New(database)
|
||||||
|
|
||||||
var nPolicies = uint64(10)
|
var nPolicies = uint64(10)
|
||||||
|
|
||||||
@ -605,7 +605,7 @@ func TestPoliciesDelete(t *testing.T) {
|
|||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
repo := ppostgres.NewRepository(database)
|
repo := ppostgres.NewRepository(database)
|
||||||
crepo := cpostgres.NewRepository(database)
|
crepo := cpostgres.NewRepository(database)
|
||||||
grepo := gpostgres.NewRepository(database)
|
grepo := gpostgres.New(database)
|
||||||
|
|
||||||
client := mfclients.Client{
|
client := mfclients.Client{
|
||||||
ID: testsutil.GenerateUUID(t, idProvider),
|
ID: testsutil.GenerateUUID(t, idProvider),
|
||||||
|
@ -8,12 +8,13 @@ import (
|
|||||||
|
|
||||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||||
"github.com/mainflux/mainflux/pkg/errors"
|
"github.com/mainflux/mainflux/pkg/errors"
|
||||||
|
"github.com/mainflux/mainflux/users/clients/postgres"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
const WrongID = "wrongID"
|
const WrongID = "wrongID"
|
||||||
|
|
||||||
var _ mfclients.Repository = (*Repository)(nil)
|
var _ postgres.Repository = (*Repository)(nil)
|
||||||
|
|
||||||
type Repository struct {
|
type Repository struct {
|
||||||
mock.Mock
|
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)
|
return ret.Get(0).(mfclients.Client), ret.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Repository) Save(ctx context.Context, clients ...mfclients.Client) ([]mfclients.Client, error) {
|
func (m *Repository) Save(ctx context.Context, client mfclients.Client) (mfclients.Client, error) {
|
||||||
client := clients[0]
|
|
||||||
ret := m.Called(ctx, client)
|
ret := m.Called(ctx, client)
|
||||||
if client.Owner == WrongID {
|
if client.Owner == WrongID {
|
||||||
return []mfclients.Client{}, errors.ErrMalformedEntity
|
return mfclients.Client{}, errors.ErrMalformedEntity
|
||||||
}
|
}
|
||||||
if client.Credentials.Secret == "" {
|
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) {
|
func (m *Repository) Update(ctx context.Context, client mfclients.Client) (mfclients.Client, error) {
|
||||||
|
@ -5,494 +5,62 @@ package postgres
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"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"
|
"github.com/mainflux/mainflux/internal/postgres"
|
||||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
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/errors"
|
||||||
"github.com/mainflux/mainflux/pkg/groups"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ mfclients.Repository = (*clientRepo)(nil)
|
var _ mfclients.Repository = (*clientRepo)(nil)
|
||||||
|
|
||||||
type clientRepo struct {
|
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
|
// NewRepository instantiates a PostgreSQL
|
||||||
// implementation of Clients repository.
|
// implementation of Clients repository.
|
||||||
func NewRepository(db postgres.Database) mfclients.Repository {
|
func NewRepository(db postgres.Database) Repository {
|
||||||
return &clientRepo{
|
return &clientRepo{
|
||||||
db: db,
|
ClientRepository: pgclients.ClientRepository{DB: db},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (clientRepo) RetrieveBySecret(ctx context.Context, key string) (mfclients.Client, error) {
|
func (repo clientRepo) Save(ctx context.Context, c mfclients.Client) (mfclients.Client, error) {
|
||||||
return mfclients.Client{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
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)
|
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`
|
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 {
|
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 {
|
if err != nil {
|
||||||
return []mfclients.Client{}, postgres.HandleError(err, errors.ErrCreateEntity)
|
return mfclients.Client{}, postgres.HandleError(err, errors.ErrCreateEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer row.Close()
|
defer row.Close()
|
||||||
row.Next()
|
row.Next()
|
||||||
dbc = dbClient{}
|
dbc = pgclients.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{}
|
|
||||||
if err := row.StructScan(&dbc); err != nil {
|
if err := row.StructScan(&dbc); err != nil {
|
||||||
return mfclients.Client{}, err
|
return mfclients.Client{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return toClient(dbc)
|
client, err := pgclients.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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(errors.ErrViewEntity, err)
|
return mfclients.Client{}, 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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For listing clients that the specified client owns and that are shared with the specified client
|
return client, nil
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,9 @@ import (
|
|||||||
"github.com/mainflux/mainflux/internal/testsutil"
|
"github.com/mainflux/mainflux/internal/testsutil"
|
||||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||||
"github.com/mainflux/mainflux/pkg/errors"
|
"github.com/mainflux/mainflux/pkg/errors"
|
||||||
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
|
||||||
"github.com/mainflux/mainflux/pkg/uuid"
|
"github.com/mainflux/mainflux/pkg/uuid"
|
||||||
cpostgres "github.com/mainflux/mainflux/users/clients/postgres"
|
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/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -32,8 +27,6 @@ var (
|
|||||||
password = "$tr0ngPassw0rd"
|
password = "$tr0ngPassw0rd"
|
||||||
clientIdentity = "client-identity@example.com"
|
clientIdentity = "client-identity@example.com"
|
||||||
clientName = "client name"
|
clientName = "client name"
|
||||||
wrongName = "wrong-name"
|
|
||||||
wrongID = "wrong-id"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestClientsSave(t *testing.T) {
|
func TestClientsSave(t *testing.T) {
|
||||||
@ -175,948 +168,8 @@ func TestClientsSave(t *testing.T) {
|
|||||||
rClient, err := repo.Save(context.Background(), tc.client)
|
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))
|
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 err == nil {
|
||||||
rClient[0].Credentials.Secret = tc.client.Credentials.Secret
|
rClient.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))
|
assert.Equal(t, tc.client, rClient, 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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/mainflux/mainflux/internal/apiutil"
|
"github.com/mainflux/mainflux/internal/apiutil"
|
||||||
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||||
"github.com/mainflux/mainflux/pkg/errors"
|
"github.com/mainflux/mainflux/pkg/errors"
|
||||||
|
"github.com/mainflux/mainflux/users/clients/postgres"
|
||||||
"github.com/mainflux/mainflux/users/jwt"
|
"github.com/mainflux/mainflux/users/jwt"
|
||||||
"github.com/mainflux/mainflux/users/policies"
|
"github.com/mainflux/mainflux/users/policies"
|
||||||
)
|
)
|
||||||
@ -50,7 +51,7 @@ type Service interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
clients mfclients.Repository
|
clients postgres.Repository
|
||||||
policies policies.Repository
|
policies policies.Repository
|
||||||
idProvider mainflux.IDProvider
|
idProvider mainflux.IDProvider
|
||||||
hasher Hasher
|
hasher Hasher
|
||||||
@ -60,7 +61,7 @@ type service struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewService returns a new Clients service implementation.
|
// 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{
|
return service{
|
||||||
clients: c,
|
clients: c,
|
||||||
policies: p,
|
policies: p,
|
||||||
@ -105,7 +106,7 @@ func (svc service) RegisterClient(ctx context.Context, token string, cli mfclien
|
|||||||
return mfclients.Client{}, err
|
return mfclients.Client{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return client[0], nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc service) IssueToken(ctx context.Context, identity, secret string) (jwt.Token, error) {
|
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"
|
mfclients "github.com/mainflux/mainflux/pkg/clients"
|
||||||
"github.com/mainflux/mainflux/pkg/errors"
|
"github.com/mainflux/mainflux/pkg/errors"
|
||||||
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
mfgroups "github.com/mainflux/mainflux/pkg/groups"
|
||||||
|
gpostgres "github.com/mainflux/mainflux/pkg/groups/postgres"
|
||||||
"github.com/mainflux/mainflux/pkg/uuid"
|
"github.com/mainflux/mainflux/pkg/uuid"
|
||||||
cpostgres "github.com/mainflux/mainflux/users/clients/postgres"
|
cpostgres "github.com/mainflux/mainflux/users/clients/postgres"
|
||||||
gpostgres "github.com/mainflux/mainflux/users/groups/postgres"
|
|
||||||
"github.com/mainflux/mainflux/users/policies"
|
"github.com/mainflux/mainflux/users/policies"
|
||||||
ppostgres "github.com/mainflux/mainflux/users/policies/postgres"
|
ppostgres "github.com/mainflux/mainflux/users/policies/postgres"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -29,7 +29,7 @@ func TestPoliciesSave(t *testing.T) {
|
|||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
repo := ppostgres.NewRepository(database)
|
repo := ppostgres.NewRepository(database)
|
||||||
crepo := cpostgres.NewRepository(database)
|
crepo := cpostgres.NewRepository(database)
|
||||||
grepo := gpostgres.NewRepository(database)
|
grepo := gpostgres.New(database)
|
||||||
|
|
||||||
group := mfgroups.Group{
|
group := mfgroups.Group{
|
||||||
ID: testsutil.GenerateUUID(t, idProvider),
|
ID: testsutil.GenerateUUID(t, idProvider),
|
||||||
@ -50,7 +50,7 @@ func TestPoliciesSave(t *testing.T) {
|
|||||||
|
|
||||||
clients, err := crepo.Save(context.Background(), client)
|
clients, err := crepo.Save(context.Background(), client)
|
||||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||||
client = clients[0]
|
client = clients
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
desc string
|
desc string
|
||||||
@ -89,7 +89,7 @@ func TestPoliciesEvaluate(t *testing.T) {
|
|||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
repo := ppostgres.NewRepository(database)
|
repo := ppostgres.NewRepository(database)
|
||||||
crepo := cpostgres.NewRepository(database)
|
crepo := cpostgres.NewRepository(database)
|
||||||
grepo := gpostgres.NewRepository(database)
|
grepo := gpostgres.New(database)
|
||||||
|
|
||||||
client1 := mfclients.Client{
|
client1 := mfclients.Client{
|
||||||
ID: testsutil.GenerateUUID(t, idProvider),
|
ID: testsutil.GenerateUUID(t, idProvider),
|
||||||
@ -116,10 +116,10 @@ func TestPoliciesEvaluate(t *testing.T) {
|
|||||||
|
|
||||||
clients1, err := crepo.Save(context.Background(), client1)
|
clients1, err := crepo.Save(context.Background(), client1)
|
||||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||||
client1 = clients1[0]
|
client1 = clients1
|
||||||
clients2, err := crepo.Save(context.Background(), client2)
|
clients2, err := crepo.Save(context.Background(), client2)
|
||||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||||
client2 = clients2[0]
|
client2 = clients2
|
||||||
group, err = grepo.Save(context.Background(), group)
|
group, err = grepo.Save(context.Background(), group)
|
||||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
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) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
repo := ppostgres.NewRepository(database)
|
repo := ppostgres.NewRepository(database)
|
||||||
crepo := cpostgres.NewRepository(database)
|
crepo := cpostgres.NewRepository(database)
|
||||||
grepo := gpostgres.NewRepository(database)
|
grepo := gpostgres.New(database)
|
||||||
|
|
||||||
group := mfgroups.Group{
|
group := mfgroups.Group{
|
||||||
ID: testsutil.GenerateUUID(t, idProvider),
|
ID: testsutil.GenerateUUID(t, idProvider),
|
||||||
@ -199,7 +199,7 @@ func TestPoliciesRetrieve(t *testing.T) {
|
|||||||
|
|
||||||
clients, err := crepo.Save(context.Background(), client)
|
clients, err := crepo.Save(context.Background(), client)
|
||||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||||
client = clients[0]
|
client = clients
|
||||||
|
|
||||||
policy := policies.Policy{
|
policy := policies.Policy{
|
||||||
OwnerID: client.ID,
|
OwnerID: client.ID,
|
||||||
@ -234,7 +234,7 @@ func TestPoliciesUpdate(t *testing.T) {
|
|||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
repo := ppostgres.NewRepository(database)
|
repo := ppostgres.NewRepository(database)
|
||||||
crepo := cpostgres.NewRepository(database)
|
crepo := cpostgres.NewRepository(database)
|
||||||
grepo := gpostgres.NewRepository(database)
|
grepo := gpostgres.New(database)
|
||||||
|
|
||||||
group := mfgroups.Group{
|
group := mfgroups.Group{
|
||||||
ID: testsutil.GenerateUUID(t, idProvider),
|
ID: testsutil.GenerateUUID(t, idProvider),
|
||||||
@ -352,7 +352,7 @@ func TestPoliciesRetrievalAll(t *testing.T) {
|
|||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
repo := ppostgres.NewRepository(database)
|
repo := ppostgres.NewRepository(database)
|
||||||
crepo := cpostgres.NewRepository(database)
|
crepo := cpostgres.NewRepository(database)
|
||||||
grepo := gpostgres.NewRepository(database)
|
grepo := gpostgres.New(database)
|
||||||
|
|
||||||
var nPolicies = uint64(10)
|
var nPolicies = uint64(10)
|
||||||
|
|
||||||
@ -377,10 +377,10 @@ func TestPoliciesRetrievalAll(t *testing.T) {
|
|||||||
|
|
||||||
clientsA, err := crepo.Save(context.Background(), clientA)
|
clientsA, err := crepo.Save(context.Background(), clientA)
|
||||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||||
clientA = clientsA[0]
|
clientA = clientsA
|
||||||
clientsB, err := crepo.Save(context.Background(), clientB)
|
clientsB, err := crepo.Save(context.Background(), clientB)
|
||||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||||
clientB = clientsB[0]
|
clientB = clientsB
|
||||||
|
|
||||||
grps := []string{}
|
grps := []string{}
|
||||||
for i := uint64(0); i < nPolicies; i++ {
|
for i := uint64(0); i < nPolicies; i++ {
|
||||||
@ -628,7 +628,7 @@ func TestPoliciesDelete(t *testing.T) {
|
|||||||
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
t.Cleanup(func() { testsutil.CleanUpDB(t, db) })
|
||||||
repo := ppostgres.NewRepository(database)
|
repo := ppostgres.NewRepository(database)
|
||||||
crepo := cpostgres.NewRepository(database)
|
crepo := cpostgres.NewRepository(database)
|
||||||
grepo := gpostgres.NewRepository(database)
|
grepo := gpostgres.New(database)
|
||||||
|
|
||||||
group := mfgroups.Group{
|
group := mfgroups.Group{
|
||||||
ID: testsutil.GenerateUUID(t, idProvider),
|
ID: testsutil.GenerateUUID(t, idProvider),
|
||||||
@ -649,7 +649,7 @@ func TestPoliciesDelete(t *testing.T) {
|
|||||||
|
|
||||||
clients, err := crepo.Save(context.Background(), client)
|
clients, err := crepo.Save(context.Background(), client)
|
||||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||||
client = clients[0]
|
client = clients
|
||||||
|
|
||||||
policy := policies.Policy{
|
policy := policies.Policy{
|
||||||
OwnerID: client.ID,
|
OwnerID: client.ID,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user