Files
kubeshark/config/config_internal_test.go
Volodymyr Stoiko 536537f6a4 config: stop the default-config literal overriding the auth defaults
values.yaml is generated by serialising CreateDefaultConfig, and that literal
sets the Auth fields explicitly instead of leaving them to the struct tags.
The tag said kubeshark-admin while the literal said kubeshark-viewer, so the
chart's committed default only held until the next generate-helm-values, which
would have rewritten every ungated install from admin to read-only and 403ed
deployments that never configured authorization.

Regenerating now reproduces the committed values.yaml byte for byte, which is
what says the two sources agree rather than merely happening to look alike.

The test compares the literal against the tag instead of asserting a role name,
so it keeps holding if the default is deliberately changed and only fails when
the two drift apart.

Also documents that scripting.enabled closes the scripting API rather than only
hiding the UI, now that it is the switch demo mode used to be.
2026-08-27 13:30:37 +03:00

416 lines
17 KiB
Go

package config
import (
"fmt"
"reflect"
"testing"
)
type ConfigMock struct {
SectionMock SectionMock `yaml:"section"`
Test string `yaml:"test"`
StringField string `yaml:"string-field"`
IntField int `yaml:"int-field"`
BoolField bool `yaml:"bool-field"`
UintField uint `yaml:"uint-field"`
StringSliceField []string `yaml:"string-slice-field"`
IntSliceField []int `yaml:"int-slice-field"`
BoolSliceField []bool `yaml:"bool-slice-field"`
UintSliceField []uint `yaml:"uint-slice-field"`
}
type SectionMock struct {
Test string `yaml:"test"`
}
type FieldSetValues struct {
SetValues []string
FieldName string
FieldValue interface{}
}
func TestMergeSetFlagNoSeparator(t *testing.T) {
tests := []struct {
Name string
SetValues []string
}{
{Name: "empty value", SetValues: []string{""}},
{Name: "single char", SetValues: []string{"t"}},
{Name: "combine empty value and single char", SetValues: []string{"", "t"}},
{Name: "two values without separator", SetValues: []string{"test", "test:true"}},
{Name: "four values without separator", SetValues: []string{"test", "test:true", "testing!", "true"}},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
configMock := ConfigMock{}
configMockElemValue := reflect.ValueOf(&configMock).Elem()
err := mergeSetFlag(configMockElemValue, test.SetValues)
if err == nil {
t.Errorf("unexpected unhandled error - SetValues: %v", test.SetValues)
return
}
for i := 0; i < configMockElemValue.NumField(); i++ {
currentField := configMockElemValue.Type().Field(i)
currentFieldByName := configMockElemValue.FieldByName(currentField.Name)
if !currentFieldByName.IsZero() {
t.Errorf("unexpected value with not default value - SetValues: %v", test.SetValues)
}
}
})
}
}
func TestMergeSetFlagInvalidFlagName(t *testing.T) {
tests := []struct {
Name string
SetValues []string
}{
{Name: "invalid flag name", SetValues: []string{"invalid_flag=true"}},
{Name: "invalid flag name inside section struct", SetValues: []string{"section.invalid_flag=test"}},
{Name: "flag name is a struct", SetValues: []string{"section=test"}},
{Name: "empty flag name", SetValues: []string{"=true"}},
{Name: "four tests combined", SetValues: []string{"invalid_flag=true", "config.invalid_flag=test", "section=test", "=true"}},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
configMock := ConfigMock{}
configMockElemValue := reflect.ValueOf(&configMock).Elem()
err := mergeSetFlag(configMockElemValue, test.SetValues)
if err == nil {
t.Errorf("unexpected unhandled error - SetValues: %v", test.SetValues)
return
}
for i := 0; i < configMockElemValue.NumField(); i++ {
currentField := configMockElemValue.Type().Field(i)
currentFieldByName := configMockElemValue.FieldByName(currentField.Name)
if !currentFieldByName.IsZero() {
t.Errorf("unexpected case - SetValues: %v", test.SetValues)
}
}
})
}
}
func TestMergeSetFlagInvalidFlagValue(t *testing.T) {
tests := []struct {
Name string
SetValues []string
}{
{Name: "bool value to int field", SetValues: []string{"int-field=true"}},
{Name: "int value to bool field", SetValues: []string{"bool-field:5"}},
{Name: "int value to uint field", SetValues: []string{"uint-field=-1"}},
{Name: "bool value to int slice field", SetValues: []string{"int-slice-field=true"}},
{Name: "int value to bool slice field", SetValues: []string{"bool-slice-field=5"}},
{Name: "int value to uint slice field", SetValues: []string{"uint-slice-field=-1"}},
{Name: "int slice value to int field", SetValues: []string{"int-field=6", "int-field=66"}},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
configMock := ConfigMock{}
configMockElemValue := reflect.ValueOf(&configMock).Elem()
err := mergeSetFlag(configMockElemValue, test.SetValues)
if err == nil {
t.Errorf("unexpected unhandled error - SetValues: %v", test.SetValues)
return
}
for i := 0; i < configMockElemValue.NumField(); i++ {
currentField := configMockElemValue.Type().Field(i)
currentFieldByName := configMockElemValue.FieldByName(currentField.Name)
if !currentFieldByName.IsZero() {
t.Errorf("unexpected case - SetValues: %v", test.SetValues)
}
}
})
}
}
func TestMergeSetFlagNotSliceValues(t *testing.T) {
tests := []struct {
Name string
FieldsSetValues []FieldSetValues
}{
{Name: "string field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"}}},
{Name: "int field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6}}},
{Name: "bool field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true}}},
{Name: "uint field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)}}},
{Name: "four fields combined", FieldsSetValues: []FieldSetValues{
{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"},
{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6},
{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true},
{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)},
}},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
configMock := ConfigMock{}
configMockElemValue := reflect.ValueOf(&configMock).Elem()
var setValues []string
for _, fieldSetValues := range test.FieldsSetValues {
setValues = append(setValues, fieldSetValues.SetValues...)
}
err := mergeSetFlag(configMockElemValue, setValues)
if err != nil {
t.Errorf("unexpected error result - err: %v", err)
return
}
for _, fieldSetValues := range test.FieldsSetValues {
fieldValue := configMockElemValue.FieldByName(fieldSetValues.FieldName).Interface()
if fieldValue != fieldSetValues.FieldValue {
t.Errorf("unexpected result - expected: %v, actual: %v", fieldSetValues.FieldValue, fieldValue)
}
}
})
}
}
func TestMergeSetFlagSliceValues(t *testing.T) {
tests := []struct {
Name string
FieldsSetValues []FieldSetValues
}{
{Name: "string slice field single value", FieldsSetValues: []FieldSetValues{{SetValues: []string{"string-slice-field=test"}, FieldName: "StringSliceField", FieldValue: []string{"test"}}}},
{Name: "int slice field single value", FieldsSetValues: []FieldSetValues{{SetValues: []string{"int-slice-field=6"}, FieldName: "IntSliceField", FieldValue: []int{6}}}},
{Name: "bool slice field single value", FieldsSetValues: []FieldSetValues{{SetValues: []string{"bool-slice-field=true"}, FieldName: "BoolSliceField", FieldValue: []bool{true}}}},
{Name: "uint slice field single value", FieldsSetValues: []FieldSetValues{{SetValues: []string{"uint-slice-field=6"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6)}}}},
{Name: "four single value fields combined", FieldsSetValues: []FieldSetValues{
{SetValues: []string{"string-slice-field=test"}, FieldName: "StringSliceField", FieldValue: []string{"test"}},
{SetValues: []string{"int-slice-field=6"}, FieldName: "IntSliceField", FieldValue: []int{6}},
{SetValues: []string{"bool-slice-field=true"}, FieldName: "BoolSliceField", FieldValue: []bool{true}},
{SetValues: []string{"uint-slice-field=6"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6)}},
}},
{Name: "string slice field two values", FieldsSetValues: []FieldSetValues{{SetValues: []string{"string-slice-field=test", "string-slice-field=test2"}, FieldName: "StringSliceField", FieldValue: []string{"test", "test2"}}}},
{Name: "int slice field two values", FieldsSetValues: []FieldSetValues{{SetValues: []string{"int-slice-field=6", "int-slice-field=66"}, FieldName: "IntSliceField", FieldValue: []int{6, 66}}}},
{Name: "bool slice field two values", FieldsSetValues: []FieldSetValues{{SetValues: []string{"bool-slice-field=true", "bool-slice-field=false"}, FieldName: "BoolSliceField", FieldValue: []bool{true, false}}}},
{Name: "uint slice field two values", FieldsSetValues: []FieldSetValues{{SetValues: []string{"uint-slice-field=6", "uint-slice-field=66"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6), uint(66)}}}},
{Name: "four two values fields combined", FieldsSetValues: []FieldSetValues{
{SetValues: []string{"string-slice-field=test", "string-slice-field=test2"}, FieldName: "StringSliceField", FieldValue: []string{"test", "test2"}},
{SetValues: []string{"int-slice-field=6", "int-slice-field=66"}, FieldName: "IntSliceField", FieldValue: []int{6, 66}},
{SetValues: []string{"bool-slice-field=true", "bool-slice-field=false"}, FieldName: "BoolSliceField", FieldValue: []bool{true, false}},
{SetValues: []string{"uint-slice-field=6", "uint-slice-field=66"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6), uint(66)}},
}},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
configMock := ConfigMock{}
configMockElemValue := reflect.ValueOf(&configMock).Elem()
var setValues []string
for _, fieldSetValues := range test.FieldsSetValues {
setValues = append(setValues, fieldSetValues.SetValues...)
}
err := mergeSetFlag(configMockElemValue, setValues)
if err != nil {
t.Errorf("unexpected error result - err: %v", err)
return
}
for _, fieldSetValues := range test.FieldsSetValues {
fieldValue := configMockElemValue.FieldByName(fieldSetValues.FieldName).Interface()
if !reflect.DeepEqual(fieldValue, fieldSetValues.FieldValue) {
t.Errorf("unexpected result - expected: %v, actual: %v", fieldSetValues.FieldValue, fieldValue)
}
}
})
}
}
func TestMergeSetFlagMixValues(t *testing.T) {
tests := []struct {
Name string
FieldsSetValues []FieldSetValues
}{
{Name: "single value all fields", FieldsSetValues: []FieldSetValues{
{SetValues: []string{"string-slice-field=test"}, FieldName: "StringSliceField", FieldValue: []string{"test"}},
{SetValues: []string{"int-slice-field=6"}, FieldName: "IntSliceField", FieldValue: []int{6}},
{SetValues: []string{"bool-slice-field=true"}, FieldName: "BoolSliceField", FieldValue: []bool{true}},
{SetValues: []string{"uint-slice-field=6"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6)}},
{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"},
{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6},
{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true},
{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)},
}},
{Name: "two values slice fields and single value fields", FieldsSetValues: []FieldSetValues{
{SetValues: []string{"string-slice-field=test", "string-slice-field=test2"}, FieldName: "StringSliceField", FieldValue: []string{"test", "test2"}},
{SetValues: []string{"int-slice-field=6", "int-slice-field=66"}, FieldName: "IntSliceField", FieldValue: []int{6, 66}},
{SetValues: []string{"bool-slice-field=true", "bool-slice-field=false"}, FieldName: "BoolSliceField", FieldValue: []bool{true, false}},
{SetValues: []string{"uint-slice-field=6", "uint-slice-field=66"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6), uint(66)}},
{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"},
{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6},
{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true},
{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)},
}},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
configMock := ConfigMock{}
configMockElemValue := reflect.ValueOf(&configMock).Elem()
var setValues []string
for _, fieldSetValues := range test.FieldsSetValues {
setValues = append(setValues, fieldSetValues.SetValues...)
}
err := mergeSetFlag(configMockElemValue, setValues)
if err != nil {
t.Errorf("unexpected error result - err: %v", err)
return
}
for _, fieldSetValues := range test.FieldsSetValues {
fieldValue := configMockElemValue.FieldByName(fieldSetValues.FieldName).Interface()
if !reflect.DeepEqual(fieldValue, fieldSetValues.FieldValue) {
t.Errorf("unexpected result - expected: %v, actual: %v", fieldSetValues.FieldValue, fieldValue)
}
}
})
}
}
func TestGetParsedValueValidValue(t *testing.T) {
tests := []struct {
StringValue string
Kind reflect.Kind
ActualValue interface{}
}{
{StringValue: "test", Kind: reflect.String, ActualValue: "test"},
{StringValue: "123", Kind: reflect.String, ActualValue: "123"},
{StringValue: "true", Kind: reflect.Bool, ActualValue: true},
{StringValue: "false", Kind: reflect.Bool, ActualValue: false},
{StringValue: "6", Kind: reflect.Int, ActualValue: 6},
{StringValue: "-6", Kind: reflect.Int, ActualValue: -6},
{StringValue: "6", Kind: reflect.Int8, ActualValue: int8(6)},
{StringValue: "-6", Kind: reflect.Int8, ActualValue: int8(-6)},
{StringValue: "6", Kind: reflect.Int16, ActualValue: int16(6)},
{StringValue: "-6", Kind: reflect.Int16, ActualValue: int16(-6)},
{StringValue: "6", Kind: reflect.Int32, ActualValue: int32(6)},
{StringValue: "-6", Kind: reflect.Int32, ActualValue: int32(-6)},
{StringValue: "6", Kind: reflect.Int64, ActualValue: int64(6)},
{StringValue: "-6", Kind: reflect.Int64, ActualValue: int64(-6)},
{StringValue: "6", Kind: reflect.Uint, ActualValue: uint(6)},
{StringValue: "66", Kind: reflect.Uint, ActualValue: uint(66)},
{StringValue: "6", Kind: reflect.Uint8, ActualValue: uint8(6)},
{StringValue: "66", Kind: reflect.Uint8, ActualValue: uint8(66)},
{StringValue: "6", Kind: reflect.Uint16, ActualValue: uint16(6)},
{StringValue: "66", Kind: reflect.Uint16, ActualValue: uint16(66)},
{StringValue: "6", Kind: reflect.Uint32, ActualValue: uint32(6)},
{StringValue: "66", Kind: reflect.Uint32, ActualValue: uint32(66)},
{StringValue: "6", Kind: reflect.Uint64, ActualValue: uint64(6)},
{StringValue: "66", Kind: reflect.Uint64, ActualValue: uint64(66)},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%v %v", test.Kind, test.StringValue), func(t *testing.T) {
parsedValue, err := getParsedValue(test.Kind, test.StringValue)
if err != nil {
t.Errorf("unexpected error result - err: %v", err)
return
}
if parsedValue.Interface() != test.ActualValue {
t.Errorf("unexpected result - expected: %v, actual: %v", test.ActualValue, parsedValue)
}
})
}
}
func TestGetParsedValueInvalidValue(t *testing.T) {
tests := []struct {
StringValue string
Kind reflect.Kind
}{
{StringValue: "test", Kind: reflect.Bool},
{StringValue: "123", Kind: reflect.Bool},
{StringValue: "test", Kind: reflect.Int},
{StringValue: "true", Kind: reflect.Int},
{StringValue: "test", Kind: reflect.Int8},
{StringValue: "true", Kind: reflect.Int8},
{StringValue: "test", Kind: reflect.Int16},
{StringValue: "true", Kind: reflect.Int16},
{StringValue: "test", Kind: reflect.Int32},
{StringValue: "true", Kind: reflect.Int32},
{StringValue: "test", Kind: reflect.Int64},
{StringValue: "true", Kind: reflect.Int64},
{StringValue: "test", Kind: reflect.Uint},
{StringValue: "-6", Kind: reflect.Uint},
{StringValue: "test", Kind: reflect.Uint8},
{StringValue: "-6", Kind: reflect.Uint8},
{StringValue: "test", Kind: reflect.Uint16},
{StringValue: "-6", Kind: reflect.Uint16},
{StringValue: "test", Kind: reflect.Uint32},
{StringValue: "-6", Kind: reflect.Uint32},
{StringValue: "test", Kind: reflect.Uint64},
{StringValue: "-6", Kind: reflect.Uint64},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%v %v", test.Kind, test.StringValue), func(t *testing.T) {
parsedValue, err := getParsedValue(test.Kind, test.StringValue)
if err == nil {
t.Errorf("unexpected unhandled error - stringValue: %v, Kind: %v", test.StringValue, test.Kind)
return
}
if parsedValue != reflect.ValueOf(nil) {
t.Errorf("unexpected parsed value - parsedValue: %v", parsedValue)
}
})
}
}
// CreateDefaultConfig builds the struct literal that `kubeshark config`
// serialises into helm-chart/values.yaml, and it sets Auth fields explicitly
// rather than leaving them to the `default:` struct tags. Two sources of the
// same default drift silently: the literal wins at runtime, the tag is what a
// reader checks, and the divergence only surfaces the next time someone runs
// `make generate-helm-values`.
//
// The default role is the one worth pinning. An operator who never configured
// authorization gets it applied to every caller, so a literal that disagrees
// with the tag rewrites the chart's default posture from admin to read-only
// and 403s an install that used to work.
func TestCreateDefaultConfig_AuthDefaultsMatchStructTags(t *testing.T) {
cfg := CreateDefaultConfig()
field, ok := reflect.TypeOf(cfg.Tap.Auth).FieldByName("DefaultRole")
if !ok {
t.Fatal("AuthConfig has no DefaultRole field")
}
want := field.Tag.Get("default")
if want == "" {
t.Fatal("DefaultRole carries no default tag to compare against")
}
if cfg.Tap.Auth.DefaultRole != want {
t.Errorf("CreateDefaultConfig sets DefaultRole=%q, struct tag says %q; "+
"generate-helm-values would write the former into values.yaml",
cfg.Tap.Auth.DefaultRole, want)
}
}