package conf_test import ( "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "code.bcarlin.net/go/conf" ) func TestJSONFiles(t *testing.T) { t.Parallel() runTestSuite(t, "json") } func TestTOMLFiles(t *testing.T) { t.Parallel() runTestSuite(t, "toml") } func TestHCLFiles(t *testing.T) { t.Parallel() runTestSuite(t, "hcl") } func TestUnknownFiles(t *testing.T) { t.Parallel() t.Run("LoadFile", func(t *testing.T) { t.Parallel() s := struct{}{} err := conf.LoadFile("test_data/valid.unknown", &s) if assert.Error(t, err) { assert.ErrorIs(t, err, conf.ErrUnsupportedFileType) } }) t.Run("SaveFile", func(t *testing.T) { t.Parallel() s := struct{}{} err := conf.SaveFile("test.unknown", &s) if assert.Error(t, err) { assert.ErrorIs(t, err, conf.ErrUnsupportedFileType) } assert.NoFileExists(t, "test.unknown") }) t.Run("LoadAndUpdateFile", func(t *testing.T) { t.Parallel() s := struct{}{} err := conf.LoadAndUpdateFile("test.unknown", &s) if assert.Error(t, err) { assert.ErrorIs(t, err, conf.ErrUnsupportedFileType) } assert.NoFileExists(t, "test.unknown") }) } func runTestSuite(t *testing.T, ext string) { t.Helper() testLoadFile(t, ext) testLoadFiles(t, ext) testSaveFile(t, ext) testLoadAndUpdateFile(t, ext) } type testconf struct { inUpdate func() String string `hcl:"String,optional"` Invariant string `hcl:"Invariant,optional"` Int int `hcl:"Int,optional"` } func (t testconf) Update() { if t.inUpdate != nil { t.inUpdate() } } func testLoadFile(t *testing.T, ext string) { t.Helper() t.Run("LoadFile", func(t *testing.T) { t.Parallel() t.Run("with a valid file", func(t *testing.T) { t.Parallel() c := testconf{ String: "default string", Int: 1, Invariant: "should not change", } file := "test_data/valid." + ext err := conf.LoadFile(file, &c) require.NoError(t, err) assert.Equal(t, "config string", c.String) assert.Equal(t, 42, c.Int) assert.Equal(t, "should not change", c.Invariant) }) t.Run("with an invalid file", func(t *testing.T) { t.Parallel() c := testconf{ String: "default string", Int: 1, Invariant: "should not change", } file := "test_data/invalid." + ext err := conf.LoadFile(file, &c) require.Error(t, err) assert.Equal(t, "default string", c.String) assert.Equal(t, 1, c.Int) assert.Equal(t, "should not change", c.Invariant) }) t.Run("with a non existent file", func(t *testing.T) { t.Parallel() c := testconf{ String: "default string", Int: 1, Invariant: "should not change", } file := "does-not-exist." + ext err := conf.LoadFile(file, &c) require.Error(t, err) assert.Equal(t, "default string", c.String) assert.Equal(t, 1, c.Int) assert.Equal(t, "should not change", c.Invariant) }) }) } func testLoadFiles(t *testing.T, ext string) { t.Helper() t.Run("LoadFiles", func(t *testing.T) { t.Parallel() t.Run("with two valid files with different options", func(t *testing.T) { t.Parallel() c := testconf{} err := conf.LoadFiles(&c, "test_data/part1."+ext, "test_data/part2."+ext) require.NoError(t, err) assert.Equal(t, "foo", c.String) assert.Equal(t, 42, c.Int) }) t.Run("with two valid files with the same option", func(t *testing.T) { t.Parallel() c := testconf{} err := conf.LoadFiles(&c, "test_data/same1."+ext, "test_data/same2."+ext) require.NoError(t, err) assert.Equal(t, "bar", c.String) }) t.Run("with one non-existing and one existing file", func(t *testing.T) { t.Parallel() c := testconf{} err := conf.LoadFiles(&c, "does-not-exist."+ext, "test_data/valid."+ext) require.NoError(t, err) assert.Equal(t, "config string", c.String) }) t.Run("with one invalid and one valid file", func(t *testing.T) { t.Parallel() c := testconf{} err := conf.LoadFiles(&c, "test_data/invalid."+ext, "test_data/valid."+ext) require.Error(t, err) assert.Equal(t, "", c.String) }) }) } func testSaveFile(t *testing.T, ext string) { t.Helper() t.Run("SaveFile", func(t *testing.T) { t.Parallel() t.Run("with a valid path", func(t *testing.T) { t.Parallel() c := testconf{ String: "default string", Invariant: "should not change", Int: 1, } tmpDir := t.TempDir() file := filepath.Join(tmpDir, "test."+ext) err := conf.SaveFile(file, &c) require.NoError(t, err) require.FileExists(t, file) expected, err := os.ReadFile("test_data/full." + ext) require.NoError(t, err) got, err := os.ReadFile(file) require.NoError(t, err) assert.Equal(t, string(expected), string(got)) }) t.Run("with a valid path and invalid data", func(t *testing.T) { t.Parallel() tmpDir := t.TempDir() file := filepath.Join(tmpDir, "test."+ext) err := conf.SaveFile(file, func() error { return nil }) require.Error(t, err) assert.NoFileExists(t, file) }) t.Run("with an invalid path", func(t *testing.T) { t.Parallel() c := testconf{ String: "default string", Invariant: "should not change", Int: 1, } file := "cannot/write/here." + ext err := conf.SaveFile(file, &c) require.Error(t, err) assert.NoFileExists(t, file) }) }) } func testLoadAndUpdateFile(t *testing.T, ext string) { t.Helper() t.Run("LoadAndUpdateFile", func(t *testing.T) { t.Parallel() t.Run("when the target file does not exist", func(t *testing.T) { t.Parallel() updated := false c := testconf{ String: "default string", Int: 1, Invariant: "should not change", inUpdate: func() { updated = true }, } tmpDir := t.TempDir() file := filepath.Join(tmpDir, "does-not-exist."+ext) err := conf.LoadAndUpdateFile(file, &c) require.NoError(t, err) require.FileExists(t, file) var c2 testconf err = conf.LoadFile(file, &c2) require.NoError(t, err) assert.Equal(t, c.String, c2.String) assert.Equal(t, c.Int, c2.Int) assert.Equal(t, c.Invariant, c2.Invariant) assert.False(t, updated) }) t.Run("when the path cannot be written", func(t *testing.T) { t.Parallel() updated := false c := testconf{ String: "default string", Int: 1, Invariant: "should not change", inUpdate: func() { updated = true }, } tmpDir := t.TempDir() file := filepath.Join(tmpDir, "does-not-exist", "test."+ext) err := conf.LoadAndUpdateFile(file, &c) require.Error(t, err) assert.NoFileExists(t, file) assert.False(t, updated) }) t.Run("when the config file is invalid", func(t *testing.T) { t.Parallel() updated := false c := testconf{ String: "default string", Int: 1, Invariant: "should not change", inUpdate: func() { updated = true }, } tmpDir := t.TempDir() file := filepath.Join(tmpDir, "test."+ext) content, err := os.ReadFile("test_data/invalid." + ext) require.NoError(t, err) err = os.WriteFile(file, content, 0o600) require.NoError(t, err) err = conf.LoadAndUpdateFile("test_data/invalid."+ext, &c) require.Error(t, err) assert.False(t, updated) }) t.Run("when the config file is valid", func(t *testing.T) { t.Parallel() updated := false c := testconf{ String: "default string", Int: 1, Invariant: "should not change", inUpdate: func() { updated = true }, } tmpDir := t.TempDir() file := filepath.Join(tmpDir, "test."+ext) content, err := os.ReadFile("test_data/valid." + ext) require.NoError(t, err) err = os.WriteFile(file, content, 0o600) require.NoError(t, err) err = conf.LoadAndUpdateFile(file, &c) require.NoError(t, err) var c2 testconf err = conf.LoadFile(file, &c2) require.NoError(t, err) assert.Equal(t, "config string", c2.String) assert.Equal(t, 42, c2.Int) assert.Equal(t, "should not change", c2.Invariant) assert.True(t, updated) }) t.Run("when the config file is missing options", func(t *testing.T) { t.Parallel() updated := false c := testconf{ String: "default string", Int: 1, Invariant: "should not change", inUpdate: func() { updated = true }, } tmpDir := t.TempDir() file := filepath.Join(tmpDir, "test."+ext) content, err := os.ReadFile("test_data/valid." + ext) require.NoError(t, err) err = os.WriteFile(file, content, 0o600) require.NoError(t, err) err = conf.LoadAndUpdateFile(file, &c) require.NoError(t, err) newContent, err := os.ReadFile(file) require.NoError(t, err) assert.Contains(t, string(newContent), "Invariant") assert.True(t, updated) }) t.Run("when the config contains unknown options", func(t *testing.T) { t.Parallel() updated := false c := testconf{ String: "default string", Int: 1, Invariant: "should not change", inUpdate: func() { updated = true }, } tmpDir := t.TempDir() file := filepath.Join(tmpDir, "test."+ext) content, err := os.ReadFile("test_data/unknown." + ext) require.NoError(t, err) err = os.WriteFile(file, content, 0o600) require.NoError(t, err) err = conf.LoadAndUpdateFile(file, &c) require.NoError(t, err) newContent, err := os.ReadFile(file) require.NoError(t, err) assert.NotContains(t, string(newContent), "Unknown") assert.True(t, updated) }) }) }