diff --git a/cli/packages/util/tests.go b/cli/packages/util/tests.go new file mode 100644 index 0000000000..816df573e9 --- /dev/null +++ b/cli/packages/util/tests.go @@ -0,0 +1,26 @@ +package util + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/Infisical/infisical-merge/packages/models" + "github.com/spf13/cobra" +) + +func HandleSendTestSecrets(cmd *cobra.Command, secrets []models.SingleEnvironmentVariable) { + isTestMode := os.Getenv("TEST_MODE") + + if isTestMode != "true" { + return + } + + jsonOut, err := json.Marshal(secrets) + if err != nil { + HandleError(err, "Unable to marshal secrets") + } + + fmt.Fprint(cmd.OutOrStdout(), string(jsonOut)) + +} diff --git a/cli/test/export_test.go b/cli/test/export_test.go new file mode 100644 index 0000000000..8c4b49ef19 --- /dev/null +++ b/cli/test/export_test.go @@ -0,0 +1,82 @@ +package tests + +import ( + "bytes" + "encoding/json" + "fmt" + "testing" + + "github.com/Infisical/infisical-merge/packages/cmd" + "github.com/Infisical/infisical-merge/packages/models" + "github.com/stretchr/testify/assert" +) + +func ExportSecrets(t *testing.T, authToken string, projectId string, envSlug string) { + + rootCommand := cmd.NewRootCmd() + + commandOutput := new(bytes.Buffer) + rootCommand.SetOut(commandOutput) + rootCommand.SetErr(commandOutput) + + args := []string{ + "export", + } + + args = append(args, fmt.Sprintf("--token=%s", authToken)) + args = append(args, fmt.Sprintf("--projectId=%s", projectId)) + + rootCommand.SetArgs(args) + rootCommand.Execute() + + var secrets []models.SingleEnvironmentVariable + + json.Unmarshal(commandOutput.Bytes(), &secrets) + + expectedLength := len(ALL_SECRETS) - 1 // -1 because the default path is "/", and the secret in /folder will not be found. + + assert.Len(t, secrets, expectedLength) + + for _, secret := range secrets { + if secret.Key == "FOLDER-SECRET-1" { + continue + } + assert.Contains(t, ALL_SECRET_KEYS, secret.Key) + assert.Contains(t, ALL_SECRET_VALUES, secret.Value) + } + +} + +func ExportSecretsWithoutImports(t *testing.T, authToken string, projectId string, envSlug string) { + + rootCommand := cmd.NewRootCmd() + + commandOutput := new(bytes.Buffer) + rootCommand.SetOut(commandOutput) + rootCommand.SetErr(commandOutput) + + args := []string{ + "export", + } + + args = append(args, fmt.Sprintf("--token=%s", authToken)) + args = append(args, fmt.Sprintf("--projectId=%s", projectId)) + args = append(args, "--include-imports=false") + + rootCommand.SetArgs(args) + rootCommand.Execute() + + var secrets []models.SingleEnvironmentVariable + + json.Unmarshal(commandOutput.Bytes(), &secrets) + + assert.Len(t, secrets, len(DEV_SECRETS)) + + allDevSecretKeys, allDevSecretValues := getSecretKeysAndValues(DEV_SECRETS) + + for _, secret := range secrets { + assert.Contains(t, allDevSecretKeys, secret.Key) + assert.Contains(t, allDevSecretValues, secret.Value) + } + +} diff --git a/cli/test/helper.go b/cli/test/helper.go new file mode 100644 index 0000000000..912a3a71db --- /dev/null +++ b/cli/test/helper.go @@ -0,0 +1,26 @@ +package tests + +type Secret struct { + Key string + Value string +} + +func Map[T, U any](ts []T, f func(T) U) []U { + us := make([]U, len(ts)) + for i := range ts { + us[i] = f(ts[i]) + } + return us +} + +func getSecretKeysAndValues(secrets []Secret) (keys []string, values []string) { + secretKeys := []string{} + secretValues := []string{} + + for _, secret := range secrets { + secretKeys = append(secretKeys, secret.Key) + secretValues = append(secretValues, secret.Value) + } + + return secretKeys, secretValues +} diff --git a/cli/test/root_test.go b/cli/test/root_test.go new file mode 100644 index 0000000000..2fbb788fe3 --- /dev/null +++ b/cli/test/root_test.go @@ -0,0 +1,107 @@ +package tests + +import ( + "os" + "testing" + + "github.com/Infisical/infisical-merge/packages/util" +) + +var DEV_SECRETS = []Secret{ + { + Key: "TEST-SECRET-1", + Value: "test-value-1", + }, + { + Key: "TEST-SECRET-2", + Value: "test-value-2", + }, + { + Key: "TEST-SECRET-3", + Value: "test-value-3", + }, +} + +var DEV_FOLDER_SECRETS = []Secret{ + { + Key: "FOLDER-SECRET-1", + Value: "folder-value-1", + }, +} + +var STAGING_SECRETS = []Secret{ + { + Key: "STAGING-SECRET-1", + Value: "staging-value-1", + }, + { + Key: "STAGING-SECRET-2", + Value: "staging-value-2", + }, +} + +// Initialize the combined secrets array +var ALL_SECRETS = []Secret{} +var ALL_SECRET_KEYS = []string{} +var ALL_SECRET_VALUES = []string{} + +type Credentials struct { + ClientID string + ClientSecret string + ServiceToken string + ProjectID string + EnvSlug string +} + +var creds = Credentials{ + ClientID: os.Getenv("CLI_TESTS_UA_CLIENT_ID"), + ClientSecret: os.Getenv("CLI_TESTS_UA_CLIENT_SECRET"), + ServiceToken: os.Getenv("CLI_TESTS_SERVICE_TOKEN"), + ProjectID: os.Getenv("CLI_TESTS_PROJECT_ID"), + EnvSlug: os.Getenv("CLI_TESTS_ENV_SLUG"), +} + +func initialize() { + if creds.ClientID == "" || creds.ClientSecret == "" || creds.ServiceToken == "" || creds.ProjectID == "" || creds.EnvSlug == "" { + panic("Missing required environment variables") + } + + ALL_SECRETS = append(ALL_SECRETS, DEV_SECRETS...) + ALL_SECRETS = append(ALL_SECRETS, DEV_FOLDER_SECRETS...) + ALL_SECRETS = append(ALL_SECRETS, STAGING_SECRETS...) + + for _, secret := range ALL_SECRETS { + ALL_SECRET_KEYS = append(ALL_SECRET_KEYS, secret.Key) + ALL_SECRET_VALUES = append(ALL_SECRET_VALUES, secret.Value) + } +} + +func Test_RunTests(t *testing.T) { + initialize() + + res, err := util.UniversalAuthLogin(creds.ClientID, creds.ClientSecret) + if err != nil { + t.Errorf("Error: %v", err) + } + universalAuthAccessToken := res.AccessToken + + t.Run("Export secrets", func(t *testing.T) { + ExportSecrets(t, universalAuthAccessToken, creds.ProjectID, creds.EnvSlug) + ExportSecrets(t, creds.ServiceToken, creds.ProjectID, creds.EnvSlug) + }) + + t.Run("Export secrets (without imports)", func(t *testing.T) { + ExportSecretsWithoutImports(t, universalAuthAccessToken, creds.ProjectID, creds.EnvSlug) + ExportSecretsWithoutImports(t, creds.ServiceToken, creds.ProjectID, creds.EnvSlug) + }) + + t.Run("List Secrets (with imports and recursive)", func(t *testing.T) { + ListSecretsWithImportsAndRecursive(t, universalAuthAccessToken, creds.ProjectID, creds.EnvSlug) + ListSecretsWithImportsAndRecursive(t, creds.ServiceToken, creds.ProjectID, creds.EnvSlug) + }) + + t.Run("Get Secrets by Names", func(t *testing.T) { + GetSecretsByNames(t, universalAuthAccessToken, creds.ProjectID, creds.EnvSlug) + GetSecretsByNames(t, creds.ServiceToken, creds.ProjectID, creds.EnvSlug) + }) +} diff --git a/cli/test/secrets_test.go b/cli/test/secrets_test.go new file mode 100644 index 0000000000..b346a3e69f --- /dev/null +++ b/cli/test/secrets_test.go @@ -0,0 +1,93 @@ +package tests + +import ( + "bytes" + "encoding/json" + "fmt" + "testing" + + "github.com/Infisical/infisical-merge/packages/cmd" + "github.com/Infisical/infisical-merge/packages/models" + "github.com/stretchr/testify/assert" +) + +func ListSecretsWithImportsAndRecursive(t *testing.T, authToken string, projectId string, envSlug string) { + + rootCommand := cmd.NewRootCmd() + + commandOutput := new(bytes.Buffer) + rootCommand.SetOut(commandOutput) + rootCommand.SetErr(commandOutput) + + args := []string{ + "secrets", + } + args = append(args, fmt.Sprintf("--token=%s", authToken)) + args = append(args, fmt.Sprintf("--projectId=%s", projectId)) + args = append(args, fmt.Sprintf("--env=%s", envSlug)) + args = append(args, "--include-imports=true") + args = append(args, "--recursive=true") + + rootCommand.SetArgs(args) + rootCommand.Execute() + + var secrets []models.SingleEnvironmentVariable + + json.Unmarshal(commandOutput.Bytes(), &secrets) + + if len(secrets) == 0 { + t.Errorf("No secrets found") + } + + secretKeys := []string{} + secretValues := []string{} + + for _, secret := range secrets { + secretKeys = append(secretKeys, secret.Key) + secretValues = append(secretValues, secret.Value) + } + + // Secrets can have different order and potentially more secrets. but the secrets should at least contain the above secrets. + for _, key := range ALL_SECRET_KEYS { + assert.Contains(t, secretKeys, key) + } + for _, value := range ALL_SECRET_VALUES { + assert.Contains(t, secretValues, value) + } +} + +func GetSecretsByNames(t *testing.T, authToken string, projectId string, envSlug string) { + + rootCommand := cmd.NewRootCmd() + + commandOutput := new(bytes.Buffer) + rootCommand.SetOut(commandOutput) + rootCommand.SetErr(commandOutput) + + args := []string{ + "secrets", + "get", + } + + args = append(args, ALL_SECRET_KEYS...) + args = append(args, fmt.Sprintf("--token=%s", authToken)) + args = append(args, fmt.Sprintf("--projectId=%s", projectId)) + args = append(args, fmt.Sprintf("--env=%s", envSlug)) + + rootCommand.SetArgs(args) + rootCommand.Execute() + + var secrets []models.SingleEnvironmentVariable + + json.Unmarshal(commandOutput.Bytes(), &secrets) + + assert.Len(t, secrets, len(ALL_SECRETS)) + + for _, secret := range secrets { + assert.Contains(t, ALL_SECRET_KEYS, secret.Key) + + if secret.Key == "FOLDER-SECRET-1" { + assert.Equal(t, secret.Value, "*not found*") // Should not be found because recursive isn't enabled in this test, and the default path is "/" + } + } +}