Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@
{
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
},
{
"path": "detect_secrets.filters.common.is_baseline_file",
"filename": ".secrets.baseline"
},
{
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
"min_level": 2
Expand Down Expand Up @@ -123,28 +127,28 @@
"filename": "utils/config/service_configuration_test.go",
"hashed_secret": "ddcec2f503a5d58f432a0beee3fb9544fa581f54",
"is_verified": false,
"line_number": 35
"line_number": 37
},
{
"type": "Secret Keyword",
"filename": "utils/config/service_configuration_test.go",
"hashed_secret": "7ca1cc114e7e5f955880bb96a5bf391b4dc20ab6",
"is_verified": false,
"line_number": 533
"line_number": 535
},
{
"type": "Secret Keyword",
"filename": "utils/config/service_configuration_test.go",
"hashed_secret": "11519c144be4850d95b34220a40030cbd5a36b57",
"is_verified": false,
"line_number": 628
"line_number": 630
},
{
"type": "Secret Keyword",
"filename": "utils/config/service_configuration_test.go",
"hashed_secret": "15fae91d8fa7f2c531c1cf3ddc745e1f4473c02d",
"is_verified": false,
"line_number": 635
"line_number": 637
}
],
"utils/filesystem/filehash_test.go": [
Expand Down Expand Up @@ -272,5 +276,5 @@
}
]
},
"generated_at": "2025-08-08T23:03:50Z"
"generated_at": "2025-09-26T12:33:39Z"
}
1 change: 1 addition & 0 deletions changes/20250925204435.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:gear: `[config]` Add tests for service configurations with embedded structs tagged with `mapstructure:",squash"`
15 changes: 15 additions & 0 deletions utils/config/fixtures/env-test.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# These env vars will map to a struct the embeds another struct with no mapstructure tag set (it will expect its struct name to be part of the env var)
WITH_NO_TAG_TESTBASECFG_EMBEDDED1="embedded 1"
WITH_NO_TAG_TESTBASECFG_EMBEDDED2="embedded 2"
WITH_NO_TAG_NON_EMBEDDED1="non-embedded 1"
WITH_NO_TAG_NON_EMBEDDED2="non-embedded 2"
# These env vars will map to a struct the embeds another struct with a mapstructure tag set
WITH_TAG_EMBEDDED_STRUCT_EMBEDDED1="embedded 1"
WITH_TAG_EMBEDDED_STRUCT_EMBEDDED2="embedded 2"
WITH_TAG_NON_EMBEDDED1="non-embedded 1"
WITH_TAG_NON_EMBEDDED2="non-embedded 2"
# These env vars will map to a struct the embeds another struct with the ",squash" mapstructure tag set
WITH_SQUASH_TAG_EMBEDDED1="embedded 1"
WITH_SQUASH_TAG_EMBEDDED2="embedded 2"
WITH_SQUASH_TAG_NON_EMBEDDED1="non-embedded 1"
WITH_SQUASH_TAG_NON_EMBEDDED2="non-embedded 2"
5 changes: 5 additions & 0 deletions utils/config/fixtures/flat-config-test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"embedded1": "embedded 1",
"embedded2": "embedded 2",
"non_embedded1": "non-embedded 1"
}
11 changes: 11 additions & 0 deletions utils/config/fixtures/nested-config-test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"testbasecfg": {
"embedded1": "embedded 1",
"embedded2": "embedded 2"
},
"embedded_struct": {
"embedded1": "embedded 1",
"embedded2": "embedded 2"
},
"non_embedded1": "non-embedded 1"
}
224 changes: 224 additions & 0 deletions utils/config/service_configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"context"
"errors"
"fmt"
"io/fs"
"math/rand"
"os"
"path/filepath"
Expand All @@ -16,6 +17,7 @@ import (

"github.com/go-faker/faker/v4"
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/joho/godotenv"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -776,3 +778,225 @@ func TestServiceConfigurationLoadFromEnvironment(t *testing.T) {
assert.True(t, configTest.TestConfig.Flag)
assert.False(t, configTest.TestConfig2.Flag)
}

type TestBaseCfg struct {
Embedded1 string `mapstructure:"embedded1"`
Embedded2 string `mapstructure:"embedded2"`
}

func (cfg *TestBaseCfg) Validate() error {
return validation.ValidateStruct(cfg,
validation.Field(&cfg.Embedded1, validation.Required),
validation.Field(&cfg.Embedded2, validation.Required),
)
}

type TestCfgWithEmbeddedCfg struct {
TestBaseCfg
NonEmbedded1 string `mapstructure:"non_embedded1"`
NonEmbedded2 string `mapstructure:"non_embedded2"`
}

func (cfg *TestCfgWithEmbeddedCfg) Validate() error {
// Validate Embedded Structs
err := ValidateEmbedded(cfg)
if err != nil {
return err
}

return validation.ValidateStruct(cfg,
validation.Field(&cfg.NonEmbedded1, validation.Required),
)
}

type TestCfgWithEmbeddedCfgWithTag struct {
TestBaseCfg `mapstructure:"embedded_struct"`
NonEmbedded1 string `mapstructure:"non_embedded1"`
}

func (cfg *TestCfgWithEmbeddedCfgWithTag) Validate() error {
// Validate Embedded Structs
err := ValidateEmbedded(cfg)
if err != nil {
return err
}

return validation.ValidateStruct(cfg,
validation.Field(&cfg.NonEmbedded1, validation.Required),
)
}

type TestCfgWithEmbeddedCfgWithSquashTag struct {
TestBaseCfg `mapstructure:",squash"`
NonEmbedded1 string `mapstructure:"non_embedded1"`
}

func (cfg *TestCfgWithEmbeddedCfgWithSquashTag) Validate() error {
// Validate Embedded Structs
err := ValidateEmbedded(cfg)
if err != nil {
return err
}

return validation.ValidateStruct(cfg,
validation.Field(&cfg.NonEmbedded1, validation.Required),
)
}

// Config values loaded from file should be correctly mapped onto a struct that embeds another struct with no mapstructure tag set
func TestEmbeddedServiceConfigurationWithNoTagLoadFromFile(t *testing.T) {
os.Clearenv()
session := viper.New()
testEmbedded := TestCfgWithEmbeddedCfg{}
err := LoadFromEnvironment(session, "", &testEmbedded,
&TestCfgWithEmbeddedCfg{}, filepath.Join(".", "fixtures", "nested-config-test.json"))
require.NoError(t, err)
assert.NotEmpty(t, testEmbedded.NonEmbedded1)
assert.Equal(t, "non-embedded 1", testEmbedded.NonEmbedded1)
assert.Empty(t, testEmbedded.NonEmbedded2)
assert.NotEmpty(t, testEmbedded.Embedded1)
assert.Equal(t, "embedded 1", testEmbedded.Embedded1)
assert.NotEmpty(t, testEmbedded.Embedded2)
assert.Equal(t, "embedded 2", testEmbedded.Embedded2)
}

// Nested config values loaded from file should be correctly mapped onto a struct that embeds another struct with a mapstructure tag set
func TestEmbeddedServiceConfigurationWithTagLoadFromFile(t *testing.T) {
os.Clearenv()
session := viper.New()
testEmbeddedWithTag := TestCfgWithEmbeddedCfgWithTag{}
err := LoadFromEnvironment(session, "", &testEmbeddedWithTag,
&TestCfgWithEmbeddedCfgWithTag{}, filepath.Join(".", "fixtures", "nested-config-test.json"))
require.NoError(t, err)
assert.NotEmpty(t, testEmbeddedWithTag.NonEmbedded1)
assert.Equal(t, "non-embedded 1", testEmbeddedWithTag.NonEmbedded1)
assert.NotEmpty(t, testEmbeddedWithTag.Embedded1)
assert.Equal(t, "embedded 1", testEmbeddedWithTag.Embedded1)
assert.NotEmpty(t, testEmbeddedWithTag.Embedded2)
assert.Equal(t, "embedded 2", testEmbeddedWithTag.Embedded2)
}

// Flat config values loaded from file should be correctly mapped onto a struct that embeds another struct with the ",squash" mapstructure tag set
func TestEmbeddedServiceConfigurationWithSquashTagLoadFromFile(t *testing.T) {
os.Clearenv()
session := viper.New()
testEmbeddedWithSquashTag := TestCfgWithEmbeddedCfgWithSquashTag{}
err := LoadFromEnvironment(session, "", &testEmbeddedWithSquashTag,
&TestCfgWithEmbeddedCfgWithSquashTag{}, filepath.Join(".", "fixtures", "flat-config-test.json"))
require.NoError(t, err)
assert.NotEmpty(t, testEmbeddedWithSquashTag.NonEmbedded1)
assert.Equal(t, "non-embedded 1", testEmbeddedWithSquashTag.NonEmbedded1)
assert.NotEmpty(t, testEmbeddedWithSquashTag.Embedded1)
assert.Equal(t, "embedded 1", testEmbeddedWithSquashTag.Embedded1)
assert.NotEmpty(t, testEmbeddedWithSquashTag.Embedded2)
assert.Equal(t, "embedded 2", testEmbeddedWithSquashTag.Embedded2)
}

// Nested config values loaded from file should not be correctly mapped onto a struct that embeds another struct with the ",squash" mapstructure tag set
func TestEmbeddedServiceConfigurationWithSquashTagLoadFromNestedFile(t *testing.T) {
os.Clearenv()
session := viper.New()
testEmbeddedWithSquashTag := TestCfgWithEmbeddedCfgWithSquashTag{}
err := LoadFromEnvironment(session, "", &testEmbeddedWithSquashTag,
&TestCfgWithEmbeddedCfgWithSquashTag{}, filepath.Join(".", "fixtures", "nested-config-test.json"))
require.Error(t, err)
assert.NotEmpty(t, testEmbeddedWithSquashTag.NonEmbedded1)
assert.Equal(t, "non-embedded 1", testEmbeddedWithSquashTag.NonEmbedded1)
assert.Empty(t, testEmbeddedWithSquashTag.Embedded1)
assert.NotEqual(t, "embedded 1", testEmbeddedWithSquashTag.Embedded1)
assert.NotEqual(t, "embedded 2", testEmbeddedWithSquashTag.Embedded2)
}

// Nested config values loaded from file should be correctly mapped onto a struct that embeds another struct and should maintain any defaults not overwritten
func TestEmbeddedServiceConfigurationLoadFromFileWithDefaults(t *testing.T) {
os.Clearenv()
session := viper.New()
testEmbedded := TestCfgWithEmbeddedCfg{}
defaults := &TestCfgWithEmbeddedCfg{
TestBaseCfg: TestBaseCfg{
Embedded1: "a",
Embedded2: "b",
},
NonEmbedded1: "c",
NonEmbedded2: "d",
}
err := LoadFromEnvironment(session, "", &testEmbedded,
defaults, filepath.Join(".", "fixtures", "nested-config-test.json"))
require.NoError(t, err)
assert.NotEmpty(t, testEmbedded.NonEmbedded1)
assert.Equal(t, "non-embedded 1", testEmbedded.NonEmbedded1)
assert.NotEmpty(t, testEmbedded.NonEmbedded2)
assert.Equal(t, "d", testEmbedded.NonEmbedded2)
assert.NotEmpty(t, testEmbedded.Embedded1)
assert.Equal(t, "embedded 1", testEmbedded.Embedded1)
assert.NotEmpty(t, testEmbedded.Embedded2)
assert.Equal(t, "embedded 2", testEmbedded.Embedded2)
}

// Config values loaded from env vars should be correctly mapped onto a struct that embeds another struct with no mapstructure tag set
func TestEmbeddedServiceConfigurationWithNoTagLoadFromEnvironment(t *testing.T) {
os.Clearenv()
session := viper.New()
testEmbedded := TestCfgWithEmbeddedCfg{}
err := loadEnvIntoEnvironment(t, filepath.Join(".", "fixtures", "env-test.env"))
require.NoError(t, err)

err = LoadFromEnvironment(session, "WITH_NO_TAG", &testEmbedded,
&TestCfgWithEmbeddedCfg{}, "")
require.NoError(t, err)
assert.NotEmpty(t, testEmbedded.NonEmbedded1)
assert.Equal(t, "non-embedded 1", testEmbedded.NonEmbedded1)
assert.NotEmpty(t, testEmbedded.Embedded1)
assert.Equal(t, "embedded 1", testEmbedded.Embedded1)
assert.NotEmpty(t, testEmbedded.Embedded2)
assert.Equal(t, "embedded 2", testEmbedded.Embedded2)
}

// Config values loaded from env vars should be correctly mapped onto a struct that embeds another struct with a mapstructure tag set
func TestEmbeddedServiceConfigurationWithTagLoadFromEnvironment(t *testing.T) {
os.Clearenv()
session := viper.New()
testEmbeddedWithTag := TestCfgWithEmbeddedCfgWithTag{}
err := loadEnvIntoEnvironment(t, filepath.Join(".", "fixtures", "env-test.env"))
require.NoError(t, err)

err = LoadFromEnvironment(session, "WITH_TAG", &testEmbeddedWithTag,
&TestCfgWithEmbeddedCfgWithTag{}, "")
require.NoError(t, err)
assert.NotEmpty(t, testEmbeddedWithTag.NonEmbedded1)
assert.Equal(t, "non-embedded 1", testEmbeddedWithTag.NonEmbedded1)
assert.NotEmpty(t, testEmbeddedWithTag.Embedded1)
assert.Equal(t, "embedded 1", testEmbeddedWithTag.Embedded1)
assert.NotEmpty(t, testEmbeddedWithTag.Embedded2)
assert.Equal(t, "embedded 2", testEmbeddedWithTag.Embedded2)
}

// Flat config values loaded from env vars should be correctly mapped onto a struct that embeds another struct with the ",squash" mapstructure tag set
func TestEmbeddedServiceConfigurationWithSquashTagLoadFromEnvironment(t *testing.T) {
os.Clearenv()
session := viper.New()
testEmbeddedWithSquashTag := TestCfgWithEmbeddedCfgWithSquashTag{}
err := loadEnvIntoEnvironment(t, filepath.Join(".", "fixtures", "env-test.env"))
require.NoError(t, err)

err = LoadFromEnvironment(session, "WITH_SQUASH_TAG", &testEmbeddedWithSquashTag,
&TestCfgWithEmbeddedCfgWithSquashTag{}, "")
require.NoError(t, err)
assert.NotEmpty(t, testEmbeddedWithSquashTag.NonEmbedded1)
assert.Equal(t, "non-embedded 1", testEmbeddedWithSquashTag.NonEmbedded1)
assert.NotEmpty(t, testEmbeddedWithSquashTag.Embedded1)
assert.Equal(t, "embedded 1", testEmbeddedWithSquashTag.Embedded1)
assert.NotEmpty(t, testEmbeddedWithSquashTag.Embedded2)
assert.Equal(t, "embedded 2", testEmbeddedWithSquashTag.Embedded2)
}

func loadEnvIntoEnvironment(t *testing.T, envPath string) (err error) {
t.Helper()
_, err = fs.Stat(os.DirFS("."), envPath)
require.NoError(t, err)

err = godotenv.Load(envPath)
require.NoError(t, err)

return
}
Loading
Loading