Skip to content

Commit

Permalink
Merge pull request #44 from EchoVault/chore/echovault-test
Browse files Browse the repository at this point in the history
Adding test coverage to EchoVault
  • Loading branch information
kelvinmwinuka authored May 28, 2024
2 parents 163cafd + 06ce6bf commit f81e947
Show file tree
Hide file tree
Showing 22 changed files with 1,942 additions and 759 deletions.
1 change: 0 additions & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ on:
branches: [ "main" ]

jobs:

build:
runs-on: ubuntu-latest
steps:
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ test-race:
env RACE=true OUT=internal/modules/admin/testdata make build-modules-test && \
env RACE=true OUT=echovault/testdata make build-modules-test && \
go clean -testcache && \
CGO_ENABLED=1 go test ./... --race
CGO_ENABLED=1 go test ./... --race

cover:
go tool cover -html=./coverage/coverage.out

1,293 changes: 669 additions & 624 deletions coverage/coverage.out

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions echovault/api_acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,8 @@ func (server *EchoVault) ACLGetUser(username string) (map[string][]string, error
}

arr := v.Array()
result := make(map[string][]string, len(arr)/2)

result := make(map[string][]string)

for i := 0; i < len(arr); i += 2 {
key := arr[i].String()
Expand All @@ -312,7 +313,7 @@ func (server *EchoVault) ACLGetUser(username string) (map[string][]string, error
result[key] = make([]string, len(value))

for j := 0; j < len(value); j++ {
result[key][i] = value[i].String()
result[key][j] = value[j].String()
}
}

Expand Down
254 changes: 254 additions & 0 deletions echovault/api_acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,257 @@
// limitations under the License.

package echovault

import (
"crypto/sha256"
"fmt"
"github.com/echovault/echovault/internal/constants"
"slices"
"strings"
"testing"
)

func TestEchoVault_ACLCat(t *testing.T) {
server := createEchoVault()

getCategoryCommands := func(category string) []string {
var commands []string
for _, command := range server.commands {
if slices.Contains(command.Categories, category) && (command.SubCommands == nil || len(command.SubCommands) == 0) {
commands = append(commands, strings.ToLower(command.Command))
continue
}
for _, subcommand := range command.SubCommands {
if slices.Contains(subcommand.Categories, category) {
commands = append(commands, strings.ToLower(fmt.Sprintf("%s|%s", command.Command, subcommand.Command)))
}
}
}
return commands
}

tests := []struct {
name string
args []string
want []string
wantErr bool
}{
{
name: "1. Get all ACL categories loaded on the server",
args: make([]string, 0),
want: []string{
constants.AdminCategory, constants.ConnectionCategory, constants.DangerousCategory,
constants.HashCategory, constants.FastCategory, constants.KeyspaceCategory, constants.ListCategory,
constants.PubSubCategory, constants.ReadCategory, constants.WriteCategory, constants.SetCategory,
constants.SortedSetCategory, constants.SlowCategory, constants.StringCategory,
},
wantErr: false,
},
{
name: "2. Get all commands within the admin category",
args: []string{constants.AdminCategory},
want: getCategoryCommands(constants.AdminCategory),
wantErr: false,
},
{
name: "3. Get all commands within the connection category",
args: []string{constants.ConnectionCategory},
want: getCategoryCommands(constants.ConnectionCategory),
wantErr: false,
},
{
name: "4. Get all the commands within the dangerous category",
args: []string{constants.DangerousCategory},
want: getCategoryCommands(constants.DangerousCategory),
wantErr: false,
},
{
name: "5. Get all the commands within the hash category",
args: []string{constants.HashCategory},
want: getCategoryCommands(constants.HashCategory),
wantErr: false,
},
{
name: "6. Get all the commands within the fast category",
args: []string{constants.FastCategory},
want: getCategoryCommands(constants.FastCategory),
wantErr: false,
},
{
name: "7. Get all the commands within the keyspace category",
args: []string{constants.KeyspaceCategory},
want: getCategoryCommands(constants.KeyspaceCategory),
wantErr: false,
},
{
name: "8. Get all the commands within the list category",
args: []string{constants.ListCategory},
want: getCategoryCommands(constants.ListCategory),
wantErr: false,
},
{
name: "9. Get all the commands within the pubsub category",
args: []string{constants.PubSubCategory},
want: getCategoryCommands(constants.PubSubCategory),
wantErr: false,
},
{
name: "10. Get all the commands within the read category",
args: []string{constants.ReadCategory},
want: getCategoryCommands(constants.ReadCategory),
wantErr: false,
},
{
name: "11. Get all the commands within the write category",
args: []string{constants.WriteCategory},
want: getCategoryCommands(constants.WriteCategory),
wantErr: false,
},
{
name: "12. Get all the commands within the set category",
args: []string{constants.SetCategory},
want: getCategoryCommands(constants.SetCategory),
wantErr: false,
},
{
name: "13. Get all the commands within the sortedset category",
args: []string{constants.SortedSetCategory},
want: getCategoryCommands(constants.SortedSetCategory),
wantErr: false,
},
{
name: "14. Get all the commands within the slow category",
args: []string{constants.SlowCategory},
want: getCategoryCommands(constants.SlowCategory),
wantErr: false,
},
{
name: "15. Get all the commands within the string category",
args: []string{constants.StringCategory},
want: getCategoryCommands(constants.StringCategory),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := server.ACLCat(tt.args...)
if (err != nil) != tt.wantErr {
t.Errorf("ACLCat() error = %v, wantErr %v", err, tt.wantErr)
return
}
if len(got) != len(tt.want) {
t.Errorf("ACLCat() got length = %d, want length %d", len(got), len(tt.want))
}
for _, item := range got {
if !slices.Contains(tt.want, item) {
t.Errorf("ACLCat() got unexpected element = %s, want %v", item, tt.want)
}
}
})
}
}

func TestEchoVault_ACLUsers(t *testing.T) {
server := createEchoVault()

// Set Users
users := []User{
{
Username: "user1",
Enabled: true,
NoPassword: true,
NoKeys: true,
NoCommands: true,
AddPlainPasswords: []string{},
AddHashPasswords: []string{},
IncludeCategories: []string{},
IncludeReadWriteKeys: []string{},
IncludeReadKeys: []string{},
IncludeWriteKeys: []string{},
IncludeChannels: []string{},
ExcludeChannels: []string{},
},
{
Username: "user2",
Enabled: true,
NoPassword: false,
NoKeys: false,
NoCommands: false,
AddPlainPasswords: []string{"password1", "password2"},
AddHashPasswords: []string{
func() string {
h := sha256.New()
h.Write([]byte("password1"))
return string(h.Sum(nil))
}(),
},
IncludeCategories: []string{constants.FastCategory, constants.SlowCategory, constants.HashCategory},
ExcludeCategories: []string{constants.AdminCategory, constants.DangerousCategory},
IncludeCommands: []string{"*"},
ExcludeCommands: []string{"acl|load", "acl|save"},
IncludeReadWriteKeys: []string{"user2-profile-*"},
IncludeReadKeys: []string{"user2-privileges-*"},
IncludeWriteKeys: []string{"write-key"},
IncludeChannels: []string{"posts-*"},
ExcludeChannels: []string{"actions-*"},
},
}

for _, user := range users {
ok, err := server.ACLSetUser(user)
if err != nil {
t.Errorf("ACLSetUser() err = %v", err)
}
if !ok {
t.Errorf("ACLSetUser() ok = %v", ok)
}
}

// Get users
aclUsers, err := server.ACLUsers()
if err != nil {
t.Errorf("ACLUsers() err = %v", err)
}
if len(aclUsers) != len(users)+1 {
t.Errorf("ACLUsers() got length %d, want %d", len(aclUsers), len(users)+1)
}
for _, username := range aclUsers {
if !slices.Contains([]string{"default", "user1", "user2"}, username) {
t.Errorf("ACLUsers() unexpected username = %s", username)
}
}

// Get specific user.
user, err := server.ACLGetUser("user2")
if err != nil {
t.Errorf("ACLGetUser() err = %v", err)
}
if user == nil {
t.Errorf("ACLGetUser() user is nil")
}

// Delete user
ok, err := server.ACLDelUser("user1")
if err != nil {
t.Errorf("ACLDelUser() err = %v", err)
}
if !ok {
t.Errorf("ACLDelUser() could not delete user user1")
}
aclUsers, err = server.ACLUsers()
if err != nil {
t.Errorf("ACLDelUser() err = %v", err)
}
if slices.Contains(aclUsers, "user1") {
t.Errorf("ACLDelUser() unexpected username user1")
}

// Get list of currently loaded ACL rules.
list, err := server.ACLList()
if err != nil {
t.Errorf("ACLList() err = %v", err)
}
if len(list) != 2 {
t.Errorf("ACLList() got list length %d, want %d", len(list), 2)
}
}
28 changes: 17 additions & 11 deletions echovault/api_admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,16 +139,18 @@ type SubCommandOptions struct {
// `options` - CommandListOptions.
//
// Returns: a string slice of all the loaded commands. SubCommands are represented as "command|subcommand".
func (server *EchoVault) CommandList(options CommandListOptions) ([]string, error) {
func (server *EchoVault) CommandList(options ...CommandListOptions) ([]string, error) {
cmd := []string{"COMMAND", "LIST"}

switch {
case options.ACLCAT != "":
cmd = append(cmd, []string{"FILTERBY", "ACLCAT", options.ACLCAT}...)
case options.PATTERN != "":
cmd = append(cmd, []string{"FILTERBY", "PATTERN", options.PATTERN}...)
case options.MODULE != "":
cmd = append(cmd, []string{"FILTERBY", "MODULE", options.MODULE}...)
if len(options) > 0 {
switch {
case options[0].ACLCAT != "":
cmd = append(cmd, []string{"FILTERBY", "ACLCAT", options[0].ACLCAT}...)
case options[0].PATTERN != "":
cmd = append(cmd, []string{"FILTERBY", "PATTERN", options[0].PATTERN}...)
case options[0].MODULE != "":
cmd = append(cmd, []string{"FILTERBY", "MODULE", options[0].MODULE}...)
}
}

b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
Expand All @@ -171,12 +173,16 @@ func (server *EchoVault) CommandCount() (int, error) {
}

// Save triggers a new snapshot.
func (server *EchoVault) Save() (string, error) {
//
// Returns: true if the save was started. The OK response does not confirm that the save was successfully synced to
// file. Only that the background process has started.
func (server *EchoVault) Save() (bool, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"SAVE"}), nil, false, true)
if err != nil {
return "", err
return false, err
}
return internal.ParseStringResponse(b)
res, err := internal.ParseStringResponse(b)
return strings.EqualFold(res, "ok"), err
}

// LastSave returns the unix epoch milliseconds timestamp of the last save.
Expand Down
Loading

0 comments on commit f81e947

Please sign in to comment.