Skip to content

Commit

Permalink
feat(test): Add unit tests
Browse files Browse the repository at this point in the history
Signed-off-by: Bence Csati <csatib02@gmail.com>
  • Loading branch information
csatib02 committed Sep 1, 2024
1 parent c88cb98 commit e84c8df
Show file tree
Hide file tree
Showing 11 changed files with 296 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Kube Pod Autocomplete is a Go-based backend service designed to enhance the user

- Add caching.
- Add search pods by label/ns/phase endpoint as a possible use-case.
- Add Unit-tests.
- Check URL path usage to determine resourceType.
- Add e2e-tests.
- Consider moving main.go to cmd.
- Consider adding garden config to simplify testing.
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ require (
github.com/samber/slog-multi v1.2.1
github.com/samber/slog-syslog v1.0.0
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
k8s.io/api v0.31.0
k8s.io/apimachinery v0.31.0
k8s.io/client-go v0.31.0
sigs.k8s.io/controller-runtime v0.19.0
)

require (
Expand Down Expand Up @@ -52,6 +54,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/samber/lo v1.38.1 // indirect
Expand Down Expand Up @@ -81,7 +84,6 @@ require (
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/controller-runtime v0.19.0
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
Expand Down
74 changes: 74 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package config

import (
"os"
"testing"

"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)

func TestLoadConfig(t *testing.T) {
tests := []struct {
name string
envVars map[string]string
wantConfig *Config
wantError error
}{
{
name: "Default values",
envVars: map[string]string{},
wantConfig: &Config{
ListenAddress: defaultListenAddress,
TrustedProxies: nil,
Mode: defaultMode,
LogLevel: "info",
JSONLog: false,
LogServerAddress: "",
},
wantError: nil,
},
{
name: "Custom values",
envVars: map[string]string{
"KPA_LISTEN_ADDRESS": "127.0.0.1:9090",
"KPA_TRUSTED_PROXIES": "192.168.1.1,192.168.1.2",
"KPA_MODE": gin.ReleaseMode,
"KPA_LOG_LEVEL": "debug",
"KPA_JSON_LOG": "true",
"KPA_LOG_SERVER": "logserver.local",
},
wantConfig: &Config{
ListenAddress: "127.0.0.1:9090",
TrustedProxies: []string{"192.168.1.1", "192.168.1.2"},
Mode: gin.ReleaseMode,
LogLevel: "debug",
JSONLog: true,
LogServerAddress: "logserver.local",
},
wantError: nil,
},
}

for _, tt := range tests {
ttp := tt
t.Run(ttp.name, func(t *testing.T) {
for envKey, envVal := range ttp.envVars {
os.Setenv(envKey, envVal)
}
t.Cleanup(func() {
os.Clearenv()
})

config, err := LoadConfig()
if err != nil {
assert.EqualError(t, ttp.wantError, err.Error(), "Unexpected error message")
}

if ttp.wantConfig != nil {
assert.Equal(t, ttp.wantConfig, config, "Unexpected config")
}

})

Check failure on line 72 in internal/config/config_test.go

View workflow job for this annotation

GitHub Actions / Lint

unnecessary trailing newline (whitespace)
}
}
48 changes: 48 additions & 0 deletions internal/handlers/autocomplete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package handlers

import (
"errors"
"testing"

"github.com/stretchr/testify/assert"
)

func TestValidateRequestedFilters(t *testing.T) {
tests := []struct {
name string
inputFilters []string
wantFilters []string
wantError error
}{
{
name: "Valid filters",
inputFilters: []string{"namespace", "phase", "labels"},
wantFilters: []string{"namespace", "phase", "labels"},
wantError: nil,
},
{
name: "Mixed valid and empty filters",
inputFilters: []string{"namespace", "", "phase", " ", "labels"},
wantFilters: []string{"namespace", "phase", "labels"},
wantError: nil,
},
{
name: "All empty filters",
inputFilters: []string{"", " ", "\t", "\n"},
wantFilters: nil,
wantError: errors.New("no valid filters provided"),
},
}

for _, tt := range tests {
ttp := tt
t.Run(ttp.name, func(t *testing.T) {
filters, err := validateRequestedFilters(ttp.inputFilters)
if err != nil {
assert.EqualError(t, ttp.wantError, err.Error(), "Unexpected error message")
}

assert.Equal(t, ttp.wantFilters, filters, "Unexpected filters")
})
}
}
2 changes: 1 addition & 1 deletion internal/k8s/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func NewClient() (*Client, error) {
return &Client{clientset: clientset}, nil
}

func (c *Client) ListResource(ctx context.Context, resource model.Resource) (model.Resource, error) {
func (c *Client) ListResource(ctx context.Context, resource model.Resources) (model.Resources, error) {
switch resource.(type) {
case model.ResourceType:
return c.listPods(ctx)
Expand Down
6 changes: 3 additions & 3 deletions internal/services/autocomplete/autocomplete.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (s *Service) GetAutocompleteSuggestions(ctx context.Context, req model.Auto
}

// extractSuggestions extracts suggestions from the given pods based on the requested filters
func (s *Service) extractSuggestions(resources model.Resource, filters *map[string]model.FieldFilter) (*model.AutocompleteSuggestions, error) {
func (s *Service) extractSuggestions(resources model.Resources, filters *map[string]model.FieldFilter) (*model.AutocompleteSuggestions, error) {
suggestions := make([]model.Suggestion, 0, len(*filters))
for fieldName, fieldFilter := range *filters {
extractedData := fieldFilter.Extractor.Extract(resources)
Expand Down Expand Up @@ -87,10 +87,10 @@ func (s *Service) extractSuggestions(resources model.Resource, filters *map[stri
return &model.AutocompleteSuggestions{Suggestions: suggestions}, nil
}

func (s *Service) processMapSuggestion(suggestions *[]model.Suggestion, filter string, mapData *map[string][]string) {
func (s *Service) processMapSuggestion(suggestions *[]model.Suggestion, filterName string, mapData *map[string][]string) {
for key, value := range *mapData {
*suggestions = append(*suggestions, model.Suggestion{
Key: fmt.Sprintf("%s:%s", filter, key),
Key: fmt.Sprintf("%s:%s", filterName, key),
Values: value,
})
}
Expand Down
45 changes: 45 additions & 0 deletions internal/services/autocomplete/autocomplete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package autocomplete

import (
"testing"

"github.com/csatib02/kube-pod-autocomplete/internal/services/autocomplete/model"
"github.com/stretchr/testify/assert"
)

var serviceTest = Service{}

func TestProcessMapSuggestion(t *testing.T) {
tests := []struct {
name string
filterName string
mapData map[string][]string
inputSuggestions []model.Suggestion
wantSuggestions []model.Suggestion
}{
{
name: "Valid Map Data",
filterName: "labels",
mapData: map[string][]string{"app": {"nginx", "redis"}},
inputSuggestions: []model.Suggestion{},
wantSuggestions: []model.Suggestion{
{Key: "labels:app", Values: []string{"nginx", "redis"}},
},
},
{
name: "Empty Map Data",
filterName: "labels",
mapData: map[string][]string{},
inputSuggestions: []model.Suggestion{},
wantSuggestions: []model.Suggestion{},
},
}

for _, tt := range tests {
ttp := tt
t.Run(ttp.name, func(t *testing.T) {
serviceTest.processMapSuggestion(&ttp.inputSuggestions, ttp.filterName, &ttp.mapData)
assert.Equal(t, ttp.wantSuggestions, ttp.inputSuggestions)
})
}
}
103 changes: 103 additions & 0 deletions internal/services/autocomplete/filter/filteroptions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package filter

import (
"testing"

"github.com/csatib02/kube-pod-autocomplete/internal/services/autocomplete/model"
"github.com/stretchr/testify/assert"
)

var optionsTest = Options{}

func TestRemoveDuplicateValues(t *testing.T) {
tests := []struct {
name string
inputsuggestions []model.Suggestion
wantSuggestions []model.Suggestion
}{
{
name: "No duplicates",
inputsuggestions: []model.Suggestion{
{Key: "namespace", Values: []string{"value1", "value2"}},
{Key: "label", Values: []string{"value3", "value4"}},
},
wantSuggestions: []model.Suggestion{
{Key: "namespace", Values: []string{"value1", "value2"}},
{Key: "label", Values: []string{"value3", "value4"}},
},
},
{
name: "With duplicates",
inputsuggestions: []model.Suggestion{
{Key: "namespace", Values: []string{"value1", "value2", "value1"}},
{Key: "label", Values: []string{"value3", "value4", "value3"}},
},
wantSuggestions: []model.Suggestion{
{Key: "namespace", Values: []string{"value1", "value2"}},
{Key: "label", Values: []string{"value3", "value4"}},
},
},
{
name: "All duplicates",
inputsuggestions: []model.Suggestion{
{Key: "namespace", Values: []string{"value1", "value1", "value1"}},
},
wantSuggestions: []model.Suggestion{
{Key: "namespace", Values: []string{"value1"}},
},
},
}

for _, tt := range tests {
ttp := tt
t.Run(ttp.name, func(t *testing.T) {
optionsTest.RemoveDuplicateValues(&ttp.inputsuggestions)
assert.Equal(t, ttp.wantSuggestions, ttp.inputsuggestions)
})
}
}

func TestRemoveIgnoredKeys(t *testing.T) {
tests := []struct {
name string
inputsuggestions []model.Suggestion
wantSuggestions []model.Suggestion
}{
{
name: "No ignored keys",
inputsuggestions: []model.Suggestion{
{Key: "namespace", Values: []string{"value1", "value2"}},
{Key: "label", Values: []string{"value3", "value4"}},
},
wantSuggestions: []model.Suggestion{
{Key: "namespace", Values: []string{"value1", "value2"}},
{Key: "label", Values: []string{"value3", "value4"}},
},
},
{
name: "With ignored keys",
inputsuggestions: []model.Suggestion{
{Key: "annotations:kubectl.kubernetes.io/last-applied-configuration", Values: []string{"value1"}},
{Key: "label", Values: []string{"value2"}},
},
wantSuggestions: []model.Suggestion{
{Key: "label", Values: []string{"value2"}},
},
},
{
name: "All ignored keys",
inputsuggestions: []model.Suggestion{
{Key: "annotations:kubectl.kubernetes.io/last-applied-configuration", Values: []string{"value1"}},
},
wantSuggestions: []model.Suggestion{},
},
}

for _, tt := range tests {
ttp := tt
t.Run(ttp.name, func(t *testing.T) {
optionsTest.RemoveIgnoredKeys(&ttp.inputsuggestions)
assert.Equal(t, ttp.wantSuggestions, ttp.inputsuggestions)
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
var supportedFilters = map[string]model.FieldFilter{
"namespace": {
Type: model.ListFilter,
Extractor: model.ListExtractor(func(resource model.Resource) interface{} {
podResource := resource.(model.PodResource)
Extractor: model.ListExtractor(func(resource model.Resources) interface{} {
podResource := resource.(model.PodResources)
result := make([]string, 0, len(podResource.Items))
for _, pod := range podResource.Items {
result = append(result, pod.Namespace)
Expand All @@ -18,8 +18,8 @@ var supportedFilters = map[string]model.FieldFilter{
},
"phase": {
Type: model.ListFilter,
Extractor: model.ListExtractor(func(resource model.Resource) interface{} {
podResource := resource.(model.PodResource)
Extractor: model.ListExtractor(func(resource model.Resources) interface{} {
podResource := resource.(model.PodResources)
result := make([]string, 0, len(podResource.Items))
for _, pod := range podResource.Items {
result = append(result, string(pod.Status.Phase))
Expand All @@ -29,8 +29,8 @@ var supportedFilters = map[string]model.FieldFilter{
},
"labels": {
Type: model.MapFilter,
Extractor: model.MapExtractor(func(resource model.Resource) interface{} {
podResource := resource.(model.PodResource)
Extractor: model.MapExtractor(func(resource model.Resources) interface{} {
podResource := resource.(model.PodResources)
result := make(map[string][]string)
for _, pod := range podResource.Items {
for key, value := range pod.Labels {
Expand All @@ -42,8 +42,8 @@ var supportedFilters = map[string]model.FieldFilter{
},
"annotations": {
Type: model.MapFilter,
Extractor: model.MapExtractor(func(resource model.Resource) interface{} {
podResource := resource.(model.PodResource)
Extractor: model.MapExtractor(func(resource model.Resources) interface{} {
podResource := resource.(model.PodResources)
result := make(map[string][]string)
for _, pod := range podResource.Items {
for key, value := range pod.Annotations {
Expand All @@ -58,7 +58,7 @@ var supportedFilters = map[string]model.FieldFilter{
// GetFilters returns the supported filters based on the requested filters
// if called with empty requestedFilters or nil, it returns all supported filters
func GetFilters(requestedFilters *[]string) *map[string]model.FieldFilter {
if len(*requestedFilters) == 0 && requestedFilters == nil {
if requestedFilters == nil || len(*requestedFilters) == 0 {
return &supportedFilters
}

Expand Down
Loading

0 comments on commit e84c8df

Please sign in to comment.