From 86f6b31f298589fe3869a784f29b63627dd9c471 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Tue, 7 May 2024 15:25:54 +1000 Subject: [PATCH] fix: ftltest takes project paths and can read from FTL_CONFIG (#1426) Benefits: - If an envar defines the project files, each individual test doesn't need to care where it is - This allows us to fit into set ups that switch behaviours in different environments (eg: CI vs local) --- go-runtime/ftl/ftltest/ftltest.go | 42 ++++++++++++++----- ...ject-test.toml => ftl-project-test-1.toml} | 0 .../go/wrapped/ftl-project-test-2.toml | 12 ++++++ .../testdata/go/wrapped/wrapped_test.go | 21 +++++++++- 4 files changed, 64 insertions(+), 11 deletions(-) rename integration/testdata/go/wrapped/{ftl-project-test.toml => ftl-project-test-1.toml} (100%) create mode 100644 integration/testdata/go/wrapped/ftl-project-test-2.toml diff --git a/go-runtime/ftl/ftltest/ftltest.go b/go-runtime/ftl/ftltest/ftltest.go index e1970b2d99..68708f1300 100644 --- a/go-runtime/ftl/ftltest/ftltest.go +++ b/go-runtime/ftl/ftltest/ftltest.go @@ -8,12 +8,14 @@ import ( "os" "path/filepath" "reflect" + "strings" "github.com/TBD54566975/ftl/backend/schema" cf "github.com/TBD54566975/ftl/common/configuration" "github.com/TBD54566975/ftl/go-runtime/ftl" "github.com/TBD54566975/ftl/internal/log" "github.com/TBD54566975/ftl/internal/modulecontext" + "github.com/TBD54566975/ftl/internal/slices" ) type OptionsState struct { @@ -54,26 +56,46 @@ func Context(options ...Option) context.Context { return builder.Build().ApplyToContext(ctx) } -// WithProjectFile loads config and secrets from a project file +// WithProjectFiles loads config and secrets from a project file +// +// Takes a list of paths to project files. If multiple paths are provided, they are loaded in order, with later files taking precedence. +// If no paths are provided, the list is inferred from the FTL_CONFIG environment variable. If that is not found, an error is returned. // // To be used when setting up a context for a test: // ctx := ftltest.Context( // -// ftltest.WithProjectFile("path/to/ftl-project.yaml"), +// ftltest.WithProjectFiles("path/to/ftl-project.yaml"), // ... other options // // ) -func WithProjectFile(path string) Option { +func WithProjectFiles(paths ...string) Option { // Convert to absolute path immediately in case working directory changes - path, err := filepath.Abs(path) - return func(ctx context.Context, state *OptionsState) error { + var preprocessingErr error + if len(paths) == 0 { + envValue, ok := os.LookupEnv("FTL_CONFIG") + if !ok { + preprocessingErr = fmt.Errorf("loading project files: no path provided and FTL_CONFIG environment variable not set") + } + paths = strings.Split(envValue, ",") + } + paths = slices.Map(paths, func(p string) string { + path, err := filepath.Abs(p) if err != nil { - return err + preprocessingErr = err + return "" + } + return path + }) + return func(ctx context.Context, state *OptionsState) error { + if preprocessingErr != nil { + return preprocessingErr } - if _, err := os.Stat(path); err != nil { - return fmt.Errorf("error accessing project file: %w", err) + for _, path := range paths { + if _, err := os.Stat(path); err != nil { + return fmt.Errorf("error accessing project file: %w", err) + } } - cm, err := cf.NewDefaultConfigurationManagerFromConfig(ctx, []string{path}) + cm, err := cf.NewDefaultConfigurationManagerFromConfig(ctx, paths) if err != nil { return fmt.Errorf("could not set up configs: %w", err) } @@ -85,7 +107,7 @@ func WithProjectFile(path string) Option { state.configs[name] = data } - sm, err := cf.NewDefaultSecretsManagerFromConfig(ctx, []string{path}) + sm, err := cf.NewDefaultSecretsManagerFromConfig(ctx, paths) if err != nil { return fmt.Errorf("could not set up secrets: %w", err) } diff --git a/integration/testdata/go/wrapped/ftl-project-test.toml b/integration/testdata/go/wrapped/ftl-project-test-1.toml similarity index 100% rename from integration/testdata/go/wrapped/ftl-project-test.toml rename to integration/testdata/go/wrapped/ftl-project-test-1.toml diff --git a/integration/testdata/go/wrapped/ftl-project-test-2.toml b/integration/testdata/go/wrapped/ftl-project-test-2.toml new file mode 100644 index 0000000000..38a318eee3 --- /dev/null +++ b/integration/testdata/go/wrapped/ftl-project-test-2.toml @@ -0,0 +1,12 @@ +ftl-min-version = "" + +[global] + [global.configuration] + config = "inline://ImZvb2JhciI" + [global.secrets] + secret = "inline://ImZvb2JhciI" + +[executables] + ftl = "" + +[commands] diff --git a/integration/testdata/go/wrapped/wrapped_test.go b/integration/testdata/go/wrapped/wrapped_test.go index 2317774cf7..7874ea5f1a 100644 --- a/integration/testdata/go/wrapped/wrapped_test.go +++ b/integration/testdata/go/wrapped/wrapped_test.go @@ -2,7 +2,9 @@ package wrapped import ( "context" + "fmt" "ftl/time" + "path/filepath" "testing" stdtime "time" @@ -12,6 +14,12 @@ import ( ) func TestWrapped(t *testing.T) { + absProjectPath1, err := filepath.Abs("ftl-project-test-1.toml") + assert.NoError(t, err) + absProjectPath2, err := filepath.Abs("ftl-project-test-2.toml") + assert.NoError(t, err) + t.Setenv("FTL_CONFIG", fmt.Sprintf("%s,%s", absProjectPath1, absProjectPath2)) + for _, tt := range []struct { name string options []ftltest.Option @@ -56,7 +64,7 @@ func TestWrapped(t *testing.T) { { name: "WithProjectToml", options: []ftltest.Option{ - ftltest.WithProjectFile("ftl-project-test.toml"), + ftltest.WithProjectFiles("ftl-project-test-1.toml"), ftltest.WithCallsAllowedWithinModule(), ftltest.WhenVerb(time.Time, func(ctx context.Context, req time.TimeRequest) (time.TimeResponse, error) { return time.TimeResponse{Time: stdtime.Date(2024, 1, 1, 0, 0, 0, 0, stdtime.UTC)}, nil @@ -64,6 +72,17 @@ func TestWrapped(t *testing.T) { }, configValue: "bar", secretValue: "bar", + }, { + name: "WithProjectTomlFromEnvar", + options: []ftltest.Option{ + ftltest.WithProjectFiles(), + ftltest.WithCallsAllowedWithinModule(), + ftltest.WhenVerb(time.Time, func(ctx context.Context, req time.TimeRequest) (time.TimeResponse, error) { + return time.TimeResponse{Time: stdtime.Date(2024, 1, 1, 0, 0, 0, 0, stdtime.UTC)}, nil + }), + }, + configValue: "foobar", + secretValue: "foobar", }, } { t.Run(tt.name, func(t *testing.T) {