NOISSUE - Update Auth service tests (#3300)

Signed-off-by: dusan <borovcanindusan1@gmail.com>
This commit is contained in:
Dušan Borovčanin
2025-12-24 18:44:26 +01:00
committed by GitHub
parent 6a5d28c65a
commit 6b6bab79c6
5 changed files with 2153 additions and 2 deletions
+724
View File
@@ -0,0 +1,724 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package pats
import (
"encoding/json"
"testing"
"time"
apiutil "github.com/absmach/supermq/api/http/util"
"github.com/absmach/supermq/auth"
"github.com/stretchr/testify/assert"
)
var valid = "valid"
func TestCreatePatReqValidate(t *testing.T) {
cases := []struct {
desc string
req createPatReq
err error
}{
{
desc: "valid request",
req: createPatReq{
token: valid,
Name: "test-pat",
Description: "test description",
Duration: 24 * time.Hour,
},
err: nil,
},
{
desc: "empty token",
req: createPatReq{
token: "",
Name: "test-pat",
Description: "test description",
Duration: 24 * time.Hour,
},
err: apiutil.ErrBearerToken,
},
{
desc: "empty name",
req: createPatReq{
token: valid,
Name: "",
Description: "test description",
Duration: 24 * time.Hour,
},
err: apiutil.ErrMissingName,
},
{
desc: "whitespace only name",
req: createPatReq{
token: valid,
Name: " ",
Description: "test description",
Duration: 24 * time.Hour,
},
err: apiutil.ErrMissingName,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.req.validate()
assert.Equal(t, tc.err, err, "validate() error = %v, expected %v", err, tc.err)
})
}
}
func TestCreatePatReqUnmarshalJSON(t *testing.T) {
cases := []struct {
desc string
data string
expected createPatReq
err bool
}{
{
desc: "valid JSON with duration",
data: `{"name":"test-pat","description":"test desc","duration":"24h"}`,
expected: createPatReq{
Name: "test-pat",
Description: "test desc",
Duration: 24 * time.Hour,
},
err: false,
},
{
desc: "invalid duration format",
data: `{"name":"test-pat","description":"test desc","duration":"invalid"}`,
err: true,
},
{
desc: "invalid JSON",
data: `{invalid json}`,
err: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
var req createPatReq
err := json.Unmarshal([]byte(tc.data), &req)
if tc.err {
assert.Error(t, err, "UnmarshalJSON() should return error")
} else {
assert.NoError(t, err, "UnmarshalJSON() should not return error")
assert.Equal(t, tc.expected.Name, req.Name)
assert.Equal(t, tc.expected.Description, req.Description)
assert.Equal(t, tc.expected.Duration, req.Duration)
}
})
}
}
func TestRetrievePatReqValidate(t *testing.T) {
cases := []struct {
desc string
req retrievePatReq
err error
}{
{
desc: "valid request",
req: retrievePatReq{
token: valid,
id: "pat-id",
},
err: nil,
},
{
desc: "empty token",
req: retrievePatReq{
token: "",
id: "pat-id",
},
err: apiutil.ErrBearerToken,
},
{
desc: "empty id",
req: retrievePatReq{
token: valid,
id: "",
},
err: apiutil.ErrMissingPATID,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.req.validate()
assert.Equal(t, tc.err, err, "validate() error = %v, expected %v", err, tc.err)
})
}
}
func TestUpdatePatNameReqValidate(t *testing.T) {
cases := []struct {
desc string
req updatePatNameReq
err error
}{
{
desc: "valid request",
req: updatePatNameReq{
token: valid,
id: "pat-id",
Name: "new-name",
},
err: nil,
},
{
desc: "empty token",
req: updatePatNameReq{
token: "",
id: "pat-id",
Name: "new-name",
},
err: apiutil.ErrBearerToken,
},
{
desc: "empty id",
req: updatePatNameReq{
token: valid,
id: "",
Name: "new-name",
},
err: apiutil.ErrMissingPATID,
},
{
desc: "empty name",
req: updatePatNameReq{
token: valid,
id: "pat-id",
Name: "",
},
err: apiutil.ErrMissingName,
},
{
desc: "whitespace only name",
req: updatePatNameReq{
token: valid,
id: "pat-id",
Name: " ",
},
err: apiutil.ErrMissingName,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.req.validate()
assert.Equal(t, tc.err, err, "validate() error = %v, expected %v", err, tc.err)
})
}
}
func TestUpdatePatDescriptionReqValidate(t *testing.T) {
cases := []struct {
desc string
req updatePatDescriptionReq
err error
}{
{
desc: "valid request",
req: updatePatDescriptionReq{
token: valid,
id: "pat-id",
Description: "new description",
},
err: nil,
},
{
desc: "empty token",
req: updatePatDescriptionReq{
token: "",
id: "pat-id",
Description: "new description",
},
err: apiutil.ErrBearerToken,
},
{
desc: "empty id",
req: updatePatDescriptionReq{
token: valid,
id: "",
Description: "new description",
},
err: apiutil.ErrMissingPATID,
},
{
desc: "empty description",
req: updatePatDescriptionReq{
token: valid,
id: "pat-id",
Description: "",
},
err: apiutil.ErrMissingDescription,
},
{
desc: "whitespace only description",
req: updatePatDescriptionReq{
token: valid,
id: "pat-id",
Description: " ",
},
err: apiutil.ErrMissingDescription,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.req.validate()
assert.Equal(t, tc.err, err, "validate() error = %v, expected %v", err, tc.err)
})
}
}
func TestListPatsReqValidate(t *testing.T) {
cases := []struct {
desc string
req listPatsReq
err error
}{
{
desc: "valid request",
req: listPatsReq{
token: valid,
offset: 0,
limit: 10,
},
err: nil,
},
{
desc: "empty token",
req: listPatsReq{
token: "",
offset: 0,
limit: 10,
},
err: apiutil.ErrBearerToken,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.req.validate()
assert.Equal(t, tc.err, err, "validate() error = %v, expected %v", err, tc.err)
})
}
}
func TestDeletePatReqValidate(t *testing.T) {
cases := []struct {
desc string
req deletePatReq
err error
}{
{
desc: "valid request",
req: deletePatReq{
token: valid,
id: "pat-id",
},
err: nil,
},
{
desc: "empty token",
req: deletePatReq{
token: "",
id: "pat-id",
},
err: apiutil.ErrBearerToken,
},
{
desc: "empty id",
req: deletePatReq{
token: valid,
id: "",
},
err: apiutil.ErrMissingPATID,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.req.validate()
assert.Equal(t, tc.err, err, "validate() error = %v, expected %v", err, tc.err)
})
}
}
func TestResetPatSecretReqValidate(t *testing.T) {
cases := []struct {
desc string
req resetPatSecretReq
err error
}{
{
desc: "valid request",
req: resetPatSecretReq{
token: valid,
id: "pat-id",
Duration: 24 * time.Hour,
},
err: nil,
},
{
desc: "empty token",
req: resetPatSecretReq{
token: "",
id: "pat-id",
Duration: 24 * time.Hour,
},
err: apiutil.ErrBearerToken,
},
{
desc: "empty id",
req: resetPatSecretReq{
token: valid,
id: "",
Duration: 24 * time.Hour,
},
err: apiutil.ErrMissingPATID,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.req.validate()
assert.Equal(t, tc.err, err, "validate() error = %v, expected %v", err, tc.err)
})
}
}
func TestResetPatSecretReqUnmarshalJSON(t *testing.T) {
cases := []struct {
desc string
data string
expected resetPatSecretReq
err bool
}{
{
desc: "valid JSON with duration",
data: `{"duration":"48h"}`,
expected: resetPatSecretReq{
Duration: 48 * time.Hour,
},
err: false,
},
{
desc: "invalid duration format",
data: `{"duration":"invalid"}`,
err: true,
},
{
desc: "invalid JSON",
data: `{invalid}`,
err: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
var req resetPatSecretReq
err := json.Unmarshal([]byte(tc.data), &req)
if tc.err {
assert.Error(t, err, "UnmarshalJSON() should return error")
} else {
assert.NoError(t, err, "UnmarshalJSON() should not return error")
assert.Equal(t, tc.expected.Duration, req.Duration)
}
})
}
}
func TestRevokePatSecretReqValidate(t *testing.T) {
cases := []struct {
desc string
req revokePatSecretReq
err error
}{
{
desc: "valid request",
req: revokePatSecretReq{
token: valid,
id: "pat-id",
},
err: nil,
},
{
desc: "empty token",
req: revokePatSecretReq{
token: "",
id: "pat-id",
},
err: apiutil.ErrBearerToken,
},
{
desc: "empty id",
req: revokePatSecretReq{
token: valid,
id: "",
},
err: apiutil.ErrMissingPATID,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.req.validate()
assert.Equal(t, tc.err, err, "validate() error = %v, expected %v", err, tc.err)
})
}
}
func TestClearAllPATReqValidate(t *testing.T) {
cases := []struct {
desc string
req clearAllPATReq
err error
}{
{
desc: "valid request",
req: clearAllPATReq{
token: valid,
},
err: nil,
},
{
desc: "empty token",
req: clearAllPATReq{
token: "",
},
err: apiutil.ErrBearerToken,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.req.validate()
assert.Equal(t, tc.err, err, "validate() error = %v, expected %v", err, tc.err)
})
}
}
func TestAddScopeReqValidate(t *testing.T) {
validScope := auth.Scope{
OptionalDomainID: "domain1",
EntityType: auth.GroupsType,
EntityID: "entity1",
Operation: auth.CreateOp,
}
invalidScope := auth.Scope{
OptionalDomainID: "",
EntityType: auth.GroupsType,
EntityID: "",
Operation: auth.CreateOp,
}
cases := []struct {
desc string
req addScopeReq
err error
}{
{
desc: "valid request",
req: addScopeReq{
token: valid,
id: "pat-id",
Scopes: []auth.Scope{validScope},
},
err: nil,
},
{
desc: "empty token",
req: addScopeReq{
token: "",
id: "pat-id",
Scopes: []auth.Scope{validScope},
},
err: apiutil.ErrBearerToken,
},
{
desc: "empty id",
req: addScopeReq{
token: valid,
id: "",
Scopes: []auth.Scope{validScope},
},
err: apiutil.ErrMissingPATID,
},
{
desc: "empty scopes",
req: addScopeReq{
token: valid,
id: "pat-id",
Scopes: []auth.Scope{},
},
err: apiutil.ErrValidation,
},
{
desc: "invalid scope",
req: addScopeReq{
token: valid,
id: "pat-id",
Scopes: []auth.Scope{invalidScope},
},
err: apiutil.ErrValidation,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.req.validate()
if tc.err != nil {
assert.Error(t, err, "validate() should return error")
} else {
assert.NoError(t, err, "validate() should not return error")
}
})
}
}
func TestRemoveScopeReqValidate(t *testing.T) {
cases := []struct {
desc string
req removeScopeReq
err error
}{
{
desc: "valid request",
req: removeScopeReq{
token: valid,
id: "pat-id",
ScopesID: []string{"scope1", "scope2"},
},
err: nil,
},
{
desc: "empty token",
req: removeScopeReq{
token: "",
id: "pat-id",
ScopesID: []string{"scope1"},
},
err: apiutil.ErrBearerToken,
},
{
desc: "empty id",
req: removeScopeReq{
token: valid,
id: "",
ScopesID: []string{"scope1"},
},
err: apiutil.ErrMissingPATID,
},
{
desc: "empty scopes list",
req: removeScopeReq{
token: valid,
id: "pat-id",
ScopesID: []string{},
},
err: apiutil.ErrValidation,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.req.validate()
assert.Equal(t, tc.err, err, "validate() error = %v, expected %v", err, tc.err)
})
}
}
func TestClearAllScopeReqValidate(t *testing.T) {
cases := []struct {
desc string
req clearAllScopeReq
err error
}{
{
desc: "valid request",
req: clearAllScopeReq{
token: valid,
id: "pat-id",
},
err: nil,
},
{
desc: "empty token",
req: clearAllScopeReq{
token: "",
id: "pat-id",
},
err: apiutil.ErrBearerToken,
},
{
desc: "empty id",
req: clearAllScopeReq{
token: valid,
id: "",
},
err: apiutil.ErrMissingPATID,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.req.validate()
assert.Equal(t, tc.err, err, "validate() error = %v, expected %v", err, tc.err)
})
}
}
func TestListScopesReqValidate(t *testing.T) {
cases := []struct {
desc string
req listScopesReq
err error
}{
{
desc: "valid request",
req: listScopesReq{
token: valid,
offset: 0,
limit: 10,
patID: "pat-id",
},
err: nil,
},
{
desc: "empty token",
req: listScopesReq{
token: "",
offset: 0,
limit: 10,
patID: "pat-id",
},
err: apiutil.ErrBearerToken,
},
{
desc: "empty patID",
req: listScopesReq{
token: valid,
offset: 0,
limit: 10,
patID: "",
},
err: apiutil.ErrMissingPATID,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.req.validate()
assert.Equal(t, tc.err, err, "validate() error = %v, expected %v", err, tc.err)
})
}
}
+193 -2
View File
@@ -12,6 +12,195 @@ import (
"github.com/stretchr/testify/assert"
)
func TestKeyTypeString(t *testing.T) {
cases := []struct {
desc string
keyType auth.KeyType
expected string
}{
{
desc: "Access key type",
keyType: auth.AccessKey,
expected: "access",
},
{
desc: "Refresh key type",
keyType: auth.RefreshKey,
expected: "refresh",
},
{
desc: "Recovery key type",
keyType: auth.RecoveryKey,
expected: "recovery",
},
{
desc: "API key type",
keyType: auth.APIKey,
expected: "API",
},
{
desc: "Personal access token type",
keyType: auth.PersonalAccessToken,
expected: "pat",
},
{
desc: "Invitation key type",
keyType: auth.InvitationKey,
expected: "unknown",
},
{
desc: "Unknown key type",
keyType: auth.KeyType(100),
expected: "unknown",
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got := tc.keyType.String()
assert.Equal(t, tc.expected, got, "String() = %v, expected %v", got, tc.expected)
})
}
}
func TestKeyTypeValidate(t *testing.T) {
cases := []struct {
desc string
keyType auth.KeyType
expected bool
}{
{
desc: "Valid access key",
keyType: auth.AccessKey,
expected: true,
},
{
desc: "Valid refresh key",
keyType: auth.RefreshKey,
expected: true,
},
{
desc: "Valid recovery key",
keyType: auth.RecoveryKey,
expected: true,
},
{
desc: "Valid API key",
keyType: auth.APIKey,
expected: true,
},
{
desc: "Valid personal access token",
keyType: auth.PersonalAccessToken,
expected: true,
},
{
desc: "Valid invitation key",
keyType: auth.InvitationKey,
expected: true,
},
{
desc: "Invalid key type (too large)",
keyType: auth.KeyType(100),
expected: false,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got := tc.keyType.Validate()
assert.Equal(t, tc.expected, got, "Validate() = %v, expected %v", got, tc.expected)
})
}
}
func TestRoleString(t *testing.T) {
cases := []struct {
desc string
role auth.Role
expected string
}{
{
desc: "User role",
role: auth.UserRole,
expected: "user",
},
{
desc: "Admin role",
role: auth.AdminRole,
expected: "admin",
},
{
desc: "Unknown role",
role: auth.Role(100),
expected: "unknown",
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got := tc.role.String()
assert.Equal(t, tc.expected, got, "String() = %v, expected %v", got, tc.expected)
})
}
}
func TestRoleValidate(t *testing.T) {
cases := []struct {
desc string
role auth.Role
expected bool
}{
{
desc: "Valid user role",
role: auth.UserRole,
expected: true,
},
{
desc: "Valid admin role",
role: auth.AdminRole,
expected: true,
},
{
desc: "Invalid role (zero)",
role: auth.Role(0),
expected: false,
},
{
desc: "Invalid role (too large)",
role: auth.Role(100),
expected: false,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got := tc.role.Validate()
assert.Equal(t, tc.expected, got, "Validate() = %v, expected %v", got, tc.expected)
})
}
}
func TestKeyString(t *testing.T) {
key := auth.Key{
ID: "test-id",
Type: auth.APIKey,
Issuer: "test-issuer",
Subject: "test-subject",
Role: auth.UserRole,
IssuedAt: time.Now().UTC().Round(time.Second),
ExpiresAt: time.Now().UTC().Add(24 * time.Hour).Round(time.Second),
}
str := key.String()
assert.NotEmpty(t, str, "String() should return non-empty string")
assert.Contains(t, str, "test-id", "String() should contain ID")
assert.Contains(t, str, "test-issuer", "String() should contain Issuer")
assert.Contains(t, str, "test-subject", "String() should contain Subject")
assert.Contains(t, str, "API", "String() should contain Type")
assert.Contains(t, str, "user", "String() should contain Role")
}
func TestExpired(t *testing.T) {
exp := time.Now().Add(5 * time.Minute)
exp1 := time.Now()
@@ -54,7 +243,9 @@ func TestExpired(t *testing.T) {
}
for _, tc := range cases {
res := tc.key.Expired()
assert.Equal(t, tc.expired, res, fmt.Sprintf("%s: expected %t got %t\n", tc.desc, tc.expired, res))
t.Run(tc.desc, func(t *testing.T) {
res := tc.key.Expired()
assert.Equal(t, tc.expired, res, fmt.Sprintf("%s: expected %t got %t\n", tc.desc, tc.expired, res))
})
}
}
+615
View File
@@ -0,0 +1,615 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package auth_test
import (
"testing"
"github.com/absmach/supermq/auth"
"github.com/stretchr/testify/assert"
)
func TestOperationString(t *testing.T) {
cases := []struct {
desc string
op auth.Operation
expected string
}{
{
desc: "Create operation",
op: auth.CreateOp,
expected: "create",
},
{
desc: "Read operation",
op: auth.ReadOp,
expected: "read",
},
{
desc: "List operation",
op: auth.ListOp,
expected: "list",
},
{
desc: "Update operation",
op: auth.UpdateOp,
expected: "update",
},
{
desc: "Delete operation",
op: auth.DeleteOp,
expected: "delete",
},
{
desc: "Share operation",
op: auth.ShareOp,
expected: "share",
},
{
desc: "Unshare operation",
op: auth.UnshareOp,
expected: "unshare",
},
{
desc: "Publish operation",
op: auth.PublishOp,
expected: "publish",
},
{
desc: "Subscribe operation",
op: auth.SubscribeOp,
expected: "subscribe",
},
{
desc: "Unknown operation",
op: auth.Operation(100),
expected: "unknown operation type 100",
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got := tc.op.String()
assert.Equal(t, tc.expected, got, "String() = %v, expected %v", got, tc.expected)
})
}
}
func TestOperationValidString(t *testing.T) {
cases := []struct {
desc string
op auth.Operation
expected string
err bool
}{
{
desc: "Valid create operation",
op: auth.CreateOp,
expected: "create",
err: false,
},
{
desc: "Valid read operation",
op: auth.ReadOp,
expected: "read",
err: false,
},
{
desc: "Invalid operation",
op: auth.Operation(100),
expected: "",
err: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got, err := tc.op.ValidString()
if tc.err {
assert.Error(t, err, "ValidString() should return error")
} else {
assert.NoError(t, err, "ValidString() should not return error")
assert.Equal(t, tc.expected, got, "ValidString() = %v, expected %v", got, tc.expected)
}
})
}
}
func TestParseOperation(t *testing.T) {
cases := []struct {
desc string
op string
expected auth.Operation
err bool
}{
{
desc: "Parse create",
op: "create",
expected: auth.CreateOp,
err: false,
},
{
desc: "Parse read",
op: "read",
expected: auth.ReadOp,
err: false,
},
{
desc: "Parse list",
op: "list",
expected: auth.ListOp,
err: false,
},
{
desc: "Parse update",
op: "update",
expected: auth.UpdateOp,
err: false,
},
{
desc: "Parse delete",
op: "delete",
expected: auth.DeleteOp,
err: false,
},
{
desc: "Parse share",
op: "share",
expected: auth.ShareOp,
err: false,
},
{
desc: "Parse unshare",
op: "unshare",
expected: auth.UnshareOp,
err: false,
},
{
desc: "Parse publish",
op: "publish",
expected: auth.PublishOp,
err: false,
},
{
desc: "Parse subscribe",
op: "subscribe",
expected: auth.SubscribeOp,
err: false,
},
{
desc: "Parse unknown operation",
op: "unknown",
expected: auth.Operation(0),
err: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got, err := auth.ParseOperation(tc.op)
if tc.err {
assert.Error(t, err, "ParseOperation() should return error")
} else {
assert.NoError(t, err, "ParseOperation() should not return error")
assert.Equal(t, tc.expected, got, "ParseOperation() = %v, expected %v", got, tc.expected)
}
})
}
}
func TestOperationMarshalJSON(t *testing.T) {
cases := []struct {
desc string
op auth.Operation
expected []byte
err error
}{
{
desc: "Marshal create",
op: auth.CreateOp,
expected: []byte(`"create"`),
err: nil,
},
{
desc: "Marshal read",
op: auth.ReadOp,
expected: []byte(`"read"`),
err: nil,
},
{
desc: "Marshal delete",
op: auth.DeleteOp,
expected: []byte(`"delete"`),
err: nil,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got, err := tc.op.MarshalJSON()
assert.Equal(t, tc.err, err, "MarshalJSON() error = %v, expected %v", err, tc.err)
assert.Equal(t, tc.expected, got, "MarshalJSON() = %v, expected %v", got, tc.expected)
})
}
}
func TestOperationUnmarshalJSON(t *testing.T) {
cases := []struct {
desc string
data []byte
expected auth.Operation
err bool
}{
{
desc: "Unmarshal create",
data: []byte(`"create"`),
expected: auth.CreateOp,
err: false,
},
{
desc: "Unmarshal read",
data: []byte(`"read"`),
expected: auth.ReadOp,
err: false,
},
{
desc: "Unmarshal unknown",
data: []byte(`"unknown"`),
expected: auth.Operation(0),
err: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
var op auth.Operation
err := op.UnmarshalJSON(tc.data)
if tc.err {
assert.Error(t, err, "UnmarshalJSON() should return error")
} else {
assert.NoError(t, err, "UnmarshalJSON() should not return error")
assert.Equal(t, tc.expected, op, "UnmarshalJSON() = %v, expected %v", op, tc.expected)
}
})
}
}
func TestOperationMarshalText(t *testing.T) {
cases := []struct {
desc string
op auth.Operation
expected []byte
err error
}{
{
desc: "Marshal create as text",
op: auth.CreateOp,
expected: []byte("create"),
err: nil,
},
{
desc: "Marshal read as text",
op: auth.ReadOp,
expected: []byte("read"),
err: nil,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got, err := tc.op.MarshalText()
assert.Equal(t, tc.err, err, "MarshalText() error = %v, expected %v", err, tc.err)
assert.Equal(t, tc.expected, got, "MarshalText() = %v, expected %v", got, tc.expected)
})
}
}
func TestOperationUnmarshalText(t *testing.T) {
cases := []struct {
desc string
data []byte
expected auth.Operation
err bool
}{
{
desc: "Unmarshal create from text",
data: []byte("create"),
expected: auth.CreateOp,
err: false,
},
{
desc: "Unmarshal read from text",
data: []byte("read"),
expected: auth.ReadOp,
err: false,
},
{
desc: "Unmarshal unknown from text",
data: []byte("unknown"),
expected: auth.Operation(0),
err: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
var op auth.Operation
err := op.UnmarshalText(tc.data)
if tc.err {
assert.Error(t, err, "UnmarshalText() should return error")
} else {
assert.NoError(t, err, "UnmarshalText() should not return error")
assert.Equal(t, tc.expected, op, "UnmarshalText() = %v, expected %v", op, tc.expected)
}
})
}
}
func TestEntityTypeString(t *testing.T) {
cases := []struct {
desc string
et auth.EntityType
expected string
}{
{
desc: "Groups entity type",
et: auth.GroupsType,
expected: "groups",
},
{
desc: "Channels entity type",
et: auth.ChannelsType,
expected: "channels",
},
{
desc: "Clients entity type",
et: auth.ClientsType,
expected: "clients",
},
{
desc: "Domains entity type",
et: auth.DomainsType,
expected: "domains",
},
{
desc: "Users entity type",
et: auth.UsersType,
expected: "users",
},
{
desc: "Dashboard entity type",
et: auth.DashboardType,
expected: "dashboards",
},
{
desc: "Messages entity type",
et: auth.MessagesType,
expected: "messages",
},
{
desc: "Unknown entity type",
et: auth.EntityType(100),
expected: "unknown domain entity type 100",
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got := tc.et.String()
assert.Equal(t, tc.expected, got, "String() = %v, expected %v", got, tc.expected)
})
}
}
func TestParseEntityType(t *testing.T) {
cases := []struct {
desc string
et string
expected auth.EntityType
err bool
}{
{
desc: "Parse groups",
et: "groups",
expected: auth.GroupsType,
err: false,
},
{
desc: "Parse channels",
et: "channels",
expected: auth.ChannelsType,
err: false,
},
{
desc: "Parse clients",
et: "clients",
expected: auth.ClientsType,
err: false,
},
{
desc: "Parse domains",
et: "domains",
expected: auth.DomainsType,
err: false,
},
{
desc: "Parse users",
et: "users",
expected: auth.UsersType,
err: false,
},
{
desc: "Parse dashboards",
et: "dashboards",
expected: auth.DashboardType,
err: false,
},
{
desc: "Parse unknown entity type",
et: "unknown",
expected: auth.EntityType(0),
err: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got, err := auth.ParseEntityType(tc.et)
if tc.err {
assert.Error(t, err, "ParseEntityType() should return error")
} else {
assert.NoError(t, err, "ParseEntityType() should not return error")
assert.Equal(t, tc.expected, got, "ParseEntityType() = %v, expected %v", got, tc.expected)
}
})
}
}
func TestEntityTypeMarshalJSON(t *testing.T) {
cases := []struct {
desc string
et auth.EntityType
expected []byte
err error
}{
{
desc: "Marshal groups",
et: auth.GroupsType,
expected: []byte(`"groups"`),
err: nil,
},
{
desc: "Marshal channels",
et: auth.ChannelsType,
expected: []byte(`"channels"`),
err: nil,
},
{
desc: "Marshal clients",
et: auth.ClientsType,
expected: []byte(`"clients"`),
err: nil,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got, err := tc.et.MarshalJSON()
assert.Equal(t, tc.err, err, "MarshalJSON() error = %v, expected %v", err, tc.err)
assert.Equal(t, tc.expected, got, "MarshalJSON() = %v, expected %v", got, tc.expected)
})
}
}
func TestEntityTypeUnmarshalJSON(t *testing.T) {
cases := []struct {
desc string
data []byte
expected auth.EntityType
err bool
}{
{
desc: "Unmarshal groups",
data: []byte(`"groups"`),
expected: auth.GroupsType,
err: false,
},
{
desc: "Unmarshal channels",
data: []byte(`"channels"`),
expected: auth.ChannelsType,
err: false,
},
{
desc: "Unmarshal unknown",
data: []byte(`"unknown"`),
expected: auth.EntityType(0),
err: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
var et auth.EntityType
err := et.UnmarshalJSON(tc.data)
if tc.err {
assert.Error(t, err, "UnmarshalJSON() should return error")
} else {
assert.NoError(t, err, "UnmarshalJSON() should not return error")
assert.Equal(t, tc.expected, et, "UnmarshalJSON() = %v, expected %v", et, tc.expected)
}
})
}
}
func TestEntityTypeMarshalText(t *testing.T) {
cases := []struct {
desc string
et auth.EntityType
expected []byte
err error
}{
{
desc: "Marshal groups as text",
et: auth.GroupsType,
expected: []byte("groups"),
err: nil,
},
{
desc: "Marshal channels as text",
et: auth.ChannelsType,
expected: []byte("channels"),
err: nil,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got, err := tc.et.MarshalText()
assert.Equal(t, tc.err, err, "MarshalText() error = %v, expected %v", err, tc.err)
assert.Equal(t, tc.expected, got, "MarshalText() = %v, expected %v", got, tc.expected)
})
}
}
func TestEntityTypeUnmarshalText(t *testing.T) {
cases := []struct {
desc string
data []byte
expected auth.EntityType
err bool
}{
{
desc: "Unmarshal groups from text",
data: []byte("groups"),
expected: auth.GroupsType,
err: false,
},
{
desc: "Unmarshal channels from text",
data: []byte("channels"),
expected: auth.ChannelsType,
err: false,
},
{
desc: "Unmarshal unknown from text",
data: []byte("unknown"),
expected: auth.EntityType(0),
err: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
var et auth.EntityType
err := et.UnmarshalText(tc.data)
if tc.err {
assert.Error(t, err, "UnmarshalText() should return error")
} else {
assert.NoError(t, err, "UnmarshalText() should not return error")
assert.Equal(t, tc.expected, et, "UnmarshalText() = %v, expected %v", et, tc.expected)
}
})
}
}
+369
View File
@@ -0,0 +1,369 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package auth_test
import (
"testing"
"time"
apiutil "github.com/absmach/supermq/api/http/util"
"github.com/absmach/supermq/auth"
"github.com/stretchr/testify/assert"
)
func TestScopeAuthorized(t *testing.T) {
cases := []struct {
desc string
scope *auth.Scope
entityType auth.EntityType
optionalDomainID string
operation auth.Operation
entityID string
expected bool
}{
{
desc: "Authorized with matching entity type, domain, operation and entity ID",
scope: &auth.Scope{
EntityType: auth.GroupsType,
OptionalDomainID: "domain1",
Operation: auth.CreateOp,
EntityID: "entity1",
},
entityType: auth.GroupsType,
optionalDomainID: "domain1",
operation: auth.CreateOp,
entityID: "entity1",
expected: true,
},
{
desc: "Authorized with wildcard entity ID",
scope: &auth.Scope{
EntityType: auth.GroupsType,
OptionalDomainID: "domain1",
Operation: auth.CreateOp,
EntityID: "*",
},
entityType: auth.GroupsType,
optionalDomainID: "domain1",
operation: auth.CreateOp,
entityID: "any-entity",
expected: true,
},
{
desc: "Authorized without domain ID",
scope: &auth.Scope{
EntityType: auth.UsersType,
OptionalDomainID: "",
Operation: auth.ReadOp,
EntityID: "user1",
},
entityType: auth.UsersType,
optionalDomainID: "",
operation: auth.ReadOp,
entityID: "user1",
expected: true,
},
{
desc: "Not authorized with different entity type",
scope: &auth.Scope{
EntityType: auth.GroupsType,
OptionalDomainID: "domain1",
Operation: auth.CreateOp,
EntityID: "entity1",
},
entityType: auth.ChannelsType,
optionalDomainID: "domain1",
operation: auth.CreateOp,
entityID: "entity1",
expected: false,
},
{
desc: "Not authorized with different domain ID",
scope: &auth.Scope{
EntityType: auth.GroupsType,
OptionalDomainID: "domain1",
Operation: auth.CreateOp,
EntityID: "entity1",
},
entityType: auth.GroupsType,
optionalDomainID: "domain2",
operation: auth.CreateOp,
entityID: "entity1",
expected: false,
},
{
desc: "Not authorized with different operation",
scope: &auth.Scope{
EntityType: auth.GroupsType,
OptionalDomainID: "domain1",
Operation: auth.CreateOp,
EntityID: "entity1",
},
entityType: auth.GroupsType,
optionalDomainID: "domain1",
operation: auth.DeleteOp,
entityID: "entity1",
expected: false,
},
{
desc: "Not authorized with different entity ID",
scope: &auth.Scope{
EntityType: auth.GroupsType,
OptionalDomainID: "domain1",
Operation: auth.CreateOp,
EntityID: "entity1",
},
entityType: auth.GroupsType,
optionalDomainID: "domain1",
operation: auth.CreateOp,
entityID: "entity2",
expected: false,
},
{
desc: "Not authorized with nil scope",
scope: nil,
entityType: auth.GroupsType,
optionalDomainID: "domain1",
operation: auth.CreateOp,
entityID: "entity1",
expected: false,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
result := tc.scope.Authorized(tc.entityType, tc.optionalDomainID, tc.operation, tc.entityID)
assert.Equal(t, tc.expected, result, "Authorized() = %v, expected %v", result, tc.expected)
})
}
}
func TestScopeValidate(t *testing.T) {
cases := []struct {
desc string
scope *auth.Scope
err error
}{
{
desc: "Valid scope for groups with domain ID",
scope: &auth.Scope{
EntityType: auth.GroupsType,
OptionalDomainID: "domain1",
Operation: auth.CreateOp,
EntityID: "entity1",
},
err: nil,
},
{
desc: "Valid scope for channels with domain ID",
scope: &auth.Scope{
EntityType: auth.ChannelsType,
OptionalDomainID: "domain1",
Operation: auth.ReadOp,
EntityID: "channel1",
},
err: nil,
},
{
desc: "Valid scope for clients with domain ID",
scope: &auth.Scope{
EntityType: auth.ClientsType,
OptionalDomainID: "domain1",
Operation: auth.UpdateOp,
EntityID: "client1",
},
err: nil,
},
{
desc: "Valid scope for users without domain ID",
scope: &auth.Scope{
EntityType: auth.UsersType,
OptionalDomainID: "",
Operation: auth.DeleteOp,
EntityID: "user1",
},
err: nil,
},
{
desc: "Valid scope for domains without domain ID",
scope: &auth.Scope{
EntityType: auth.DomainsType,
OptionalDomainID: "",
Operation: auth.ListOp,
EntityID: "domain1",
},
err: nil,
},
{
desc: "Valid scope with wildcard entity ID",
scope: &auth.Scope{
EntityType: auth.GroupsType,
OptionalDomainID: "domain1",
Operation: auth.CreateOp,
EntityID: "*",
},
err: nil,
},
{
desc: "Invalid nil scope",
scope: nil,
err: assert.AnError, // Will be checked with Contains
},
{
desc: "Invalid scope without entity ID",
scope: &auth.Scope{
EntityType: auth.GroupsType,
OptionalDomainID: "domain1",
Operation: auth.CreateOp,
EntityID: "",
},
err: apiutil.ErrMissingEntityID,
},
{
desc: "Invalid scope for groups without domain ID",
scope: &auth.Scope{
EntityType: auth.GroupsType,
OptionalDomainID: "",
Operation: auth.CreateOp,
EntityID: "entity1",
},
err: apiutil.ErrMissingDomainID,
},
{
desc: "Invalid scope for channels without domain ID",
scope: &auth.Scope{
EntityType: auth.ChannelsType,
OptionalDomainID: "",
Operation: auth.CreateOp,
EntityID: "channel1",
},
err: apiutil.ErrMissingDomainID,
},
{
desc: "Invalid scope for clients without domain ID",
scope: &auth.Scope{
EntityType: auth.ClientsType,
OptionalDomainID: "",
Operation: auth.CreateOp,
EntityID: "client1",
},
err: apiutil.ErrMissingDomainID,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.scope.Validate()
if tc.err != nil {
assert.Error(t, err, "Validate() should return error")
if tc.err != assert.AnError {
assert.Equal(t, tc.err, err, "Validate() error = %v, expected %v", err, tc.err)
}
} else {
assert.NoError(t, err, "Validate() should not return error")
}
})
}
}
func TestPATValidate(t *testing.T) {
cases := []struct {
desc string
pat *auth.PAT
err bool
}{
{
desc: "Valid PAT",
pat: &auth.PAT{
ID: "pat-id",
User: "user-id",
Name: "test-pat",
Description: "test description",
},
err: false,
},
{
desc: "Invalid nil PAT",
pat: nil,
err: true,
},
{
desc: "Invalid PAT without name",
pat: &auth.PAT{
ID: "pat-id",
User: "user-id",
Name: "",
Description: "test description",
},
err: true,
},
{
desc: "Invalid PAT without user",
pat: &auth.PAT{
ID: "pat-id",
User: "",
Name: "test-pat",
Description: "test description",
},
err: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.pat.Validate()
if tc.err {
assert.Error(t, err, "Validate() should return error")
} else {
assert.NoError(t, err, "Validate() should not return error")
}
})
}
}
func TestPATMarshalUnmarshalBinary(t *testing.T) {
pat := auth.PAT{
ID: "pat-id",
User: "user-id",
Name: "test-pat",
Description: "test description",
Secret: "secret",
IssuedAt: time.Now().UTC().Round(time.Second),
ExpiresAt: time.Now().UTC().Add(24 * time.Hour).Round(time.Second),
Status: auth.ActiveStatus,
}
// Marshal
data, err := pat.MarshalBinary()
assert.NoError(t, err, "MarshalBinary() should not return error")
assert.NotNil(t, data, "MarshalBinary() should return data")
// Unmarshal
var newPAT auth.PAT
err = newPAT.UnmarshalBinary(data)
assert.NoError(t, err, "UnmarshalBinary() should not return error")
assert.Equal(t, pat.ID, newPAT.ID, "ID mismatch")
assert.Equal(t, pat.User, newPAT.User, "User mismatch")
assert.Equal(t, pat.Name, newPAT.Name, "Name mismatch")
assert.Equal(t, pat.Description, newPAT.Description, "Description mismatch")
assert.Equal(t, pat.Secret, newPAT.Secret, "Secret mismatch")
assert.Equal(t, pat.Status, newPAT.Status, "Status mismatch")
}
func TestPATString(t *testing.T) {
pat := &auth.PAT{
ID: "pat-id",
User: "user-id",
Name: "test-pat",
Description: "test description",
Status: auth.ActiveStatus,
}
str := pat.String()
assert.NotEmpty(t, str, "String() should return non-empty string")
assert.Contains(t, str, "pat-id", "String() should contain ID")
assert.Contains(t, str, "user-id", "String() should contain User")
assert.Contains(t, str, "test-pat", "String() should contain Name")
}
+252
View File
@@ -0,0 +1,252 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package auth_test
import (
"testing"
"github.com/absmach/supermq/auth"
svcerr "github.com/absmach/supermq/pkg/errors/service"
"github.com/stretchr/testify/assert"
)
func TestStatusString(t *testing.T) {
cases := []struct {
desc string
status auth.Status
expected string
}{
{
desc: "Active",
status: auth.ActiveStatus,
expected: "active",
},
{
desc: "Revoked",
status: auth.RevokedStatus,
expected: "revoked",
},
{
desc: "Expired",
status: auth.ExpiredStatus,
expected: "expired",
},
{
desc: "All",
status: auth.AllStatus,
expected: "all",
},
{
desc: "Unknown",
status: auth.Status(100),
expected: "unknown",
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got := tc.status.String()
assert.Equal(t, tc.expected, got, "String() = %v, expected %v", got, tc.expected)
})
}
}
func TestToStatus(t *testing.T) {
cases := []struct {
desc string
status string
expected auth.Status
err error
}{
{
desc: "Active",
status: "active",
expected: auth.ActiveStatus,
err: nil,
},
{
desc: "Empty string defaults to Active",
status: "",
expected: auth.ActiveStatus,
err: nil,
},
{
desc: "Revoked",
status: "revoked",
expected: auth.RevokedStatus,
err: nil,
},
{
desc: "Expired",
status: "expired",
expected: auth.ExpiredStatus,
err: nil,
},
{
desc: "All",
status: "all",
expected: auth.AllStatus,
err: nil,
},
{
desc: "Unknown",
status: "unknown",
expected: auth.Status(0),
err: svcerr.ErrInvalidStatus,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got, err := auth.ToStatus(tc.status)
assert.Equal(t, tc.err, err, "ToStatus() error = %v, expected %v", err, tc.err)
assert.Equal(t, tc.expected, got, "ToStatus() = %v, expected %v", got, tc.expected)
})
}
}
func TestStatusMarshalJSON(t *testing.T) {
cases := []struct {
desc string
expected []byte
status auth.Status
err error
}{
{
desc: "Active",
expected: []byte(`"active"`),
status: auth.ActiveStatus,
err: nil,
},
{
desc: "Revoked",
expected: []byte(`"revoked"`),
status: auth.RevokedStatus,
err: nil,
},
{
desc: "Expired",
expected: []byte(`"expired"`),
status: auth.ExpiredStatus,
err: nil,
},
{
desc: "All",
expected: []byte(`"all"`),
status: auth.AllStatus,
err: nil,
},
{
desc: "Unknown",
expected: []byte(`"unknown"`),
status: auth.Status(100),
err: nil,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got, err := tc.status.MarshalJSON()
assert.Equal(t, tc.err, err, "MarshalJSON() error = %v, expected %v", err, tc.err)
assert.Equal(t, tc.expected, got, "MarshalJSON() = %v, expected %v", got, tc.expected)
})
}
}
func TestStatusUnmarshalJSON(t *testing.T) {
cases := []struct {
desc string
expected auth.Status
status []byte
err error
}{
{
desc: "Active",
expected: auth.ActiveStatus,
status: []byte(`"active"`),
err: nil,
},
{
desc: "Revoked",
expected: auth.RevokedStatus,
status: []byte(`"revoked"`),
err: nil,
},
{
desc: "Expired",
expected: auth.ExpiredStatus,
status: []byte(`"expired"`),
err: nil,
},
{
desc: "All",
expected: auth.AllStatus,
status: []byte(`"all"`),
err: nil,
},
{
desc: "Unknown",
expected: auth.Status(0),
status: []byte(`"unknown"`),
err: svcerr.ErrInvalidStatus,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
var s auth.Status
err := s.UnmarshalJSON(tc.status)
assert.Equal(t, tc.err, err, "UnmarshalJSON() error = %v, expected %v", err, tc.err)
assert.Equal(t, tc.expected, s, "UnmarshalJSON() = %v, expected %v", s, tc.expected)
})
}
}
func TestPATMarshalJSON(t *testing.T) {
cases := []struct {
desc string
pat auth.PAT
expected string
err error
}{
{
desc: "Active PAT",
pat: auth.PAT{
ID: "test-id",
Name: "test-pat",
Status: auth.ActiveStatus,
},
expected: `"status":"active"`,
err: nil,
},
{
desc: "Revoked PAT",
pat: auth.PAT{
ID: "test-id",
Name: "test-pat",
Status: auth.RevokedStatus,
},
expected: `"status":"revoked"`,
err: nil,
},
{
desc: "Expired PAT",
pat: auth.PAT{
ID: "test-id",
Name: "test-pat",
Status: auth.ExpiredStatus,
},
expected: `"status":"expired"`,
err: nil,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got, err := tc.pat.MarshalJSON()
assert.Equal(t, tc.err, err, "MarshalJSON() error = %v, expected %v", err, tc.err)
assert.Contains(t, string(got), tc.expected, "MarshalJSON() should contain %v", tc.expected)
})
}
}