diff --git a/internal/namespaces/mnq/v1beta1/custom_nats_helpers.go b/internal/namespaces/mnq/v1beta1/custom_nats_helpers.go index 36a174c3a3..1f05f02751 100644 --- a/internal/namespaces/mnq/v1beta1/custom_nats_helpers.go +++ b/internal/namespaces/mnq/v1beta1/custom_nats_helpers.go @@ -85,7 +85,6 @@ func writeFile( func getNATSContextDir(ctx context.Context) (string, error) { xdgConfigHome := core.ExtractEnv(ctx, "XDG_CONFIG_HOME") - interactive.Println("xdgConfigHome:", xdgConfigHome) if xdgConfigHome == "" { homeDir := core.ExtractEnv(ctx, "HOME") if homeDir == "" { @@ -95,7 +94,7 @@ func getNATSContextDir(ctx context.Context) (string, error) { return filepath.Join(homeDir, ".config", "nats", "context"), nil } - return xdgConfigHome, nil + return filepath.Join(xdgConfigHome, "nats", "context"), nil } func saveNATSCredentials( diff --git a/internal/namespaces/mnq/v1beta1/custom_nats_test.go b/internal/namespaces/mnq/v1beta1/custom_nats_test.go index 7ec4fcf014..944f4a09eb 100644 --- a/internal/namespaces/mnq/v1beta1/custom_nats_test.go +++ b/internal/namespaces/mnq/v1beta1/custom_nats_test.go @@ -2,6 +2,7 @@ package mnq_test import ( "os" + "path/filepath" "reflect" "regexp" "testing" @@ -31,43 +32,11 @@ func Test_CreateContext(t *testing.T) { Replacement: "Select context using `nats context select context-placeholder`", }, ), - func(t *testing.T, ctx *core.CheckFuncCtx) { - t.Helper() - result, isSuccessResult := ctx.Result.(*core.SuccessResult) - assert.True( - t, - isSuccessResult, - "Expected result to be of type *core.SuccessResult, got %s", - reflect.TypeOf(result).String(), - ) - assert.NotNil(t, result) - expectedContextFile := result.Resource - if !mnq.FileExists(expectedContextFile) { - t.Errorf( - "Expected credentials file not found expected [%s] ", - expectedContextFile, - ) - } else { - ctx.Meta["deleteFiles"] = []string{expectedContextFile} - } - }, + checkContextFile, ), AfterFunc: core.AfterFuncCombine( deleteNATSAccount("NATS"), - func(ctx *core.AfterFuncCtx) error { - if ctx.Meta["deleteFiles"] == nil { - return nil - } - filesToDelete := ctx.Meta["deleteFiles"].([]string) - for _, file := range filesToDelete { - err := os.Remove(file) - if err != nil { - t.Errorf("Failed to delete the file : %s", err) - } - } - - return nil - }, + afterFuncDeleteFiles, ), })) } @@ -106,3 +75,127 @@ func Test_CreateContextNoInteractiveTermAndMultiAccount(t *testing.T) { AfterFunc: core.AfterFuncCombine(deleteNATSAccount("NATS"), deleteNATSAccount("NATS2")), })) } + +func beforeFuncCopyConfigToTmpHome() core.BeforeFunc { + return core.BeforeFuncWhenUpdatingCassette(func(ctx *core.BeforeFuncCtx) error { + realHomeDir, err := os.UserHomeDir() + if err != nil { + return err + } + + realConfigPath := filepath.Join(realHomeDir, ".config", "scw", "config.yaml") + if _, err := os.Stat(realConfigPath); os.IsNotExist(err) { + return nil + } + + tmpHomeDir := ctx.OverrideEnv["HOME"] + tmpConfigDir := filepath.Join(tmpHomeDir, ".config", "scw") + if err := os.MkdirAll(tmpConfigDir, 0o0755); err != nil { + return err + } + + data, err := os.ReadFile(realConfigPath) + if err != nil { + return err + } + + return os.WriteFile(filepath.Join(tmpConfigDir, "config.yaml"), data, 0o644) + }) +} + +func checkContextFile(t *testing.T, ctx *core.CheckFuncCtx) { + t.Helper() + result, isSuccessResult := ctx.Result.(*core.SuccessResult) + assert.True( + t, + isSuccessResult, + "Expected result to be of type *core.SuccessResult, got %s", + reflect.TypeOf(result).String(), + ) + assert.NotNil(t, result) + expectedContextFile := result.Resource + if !mnq.FileExists(expectedContextFile) { + t.Errorf("Expected credentials file not found expected [%s]", expectedContextFile) + } else { + ctx.Meta["deleteFiles"] = []string{expectedContextFile} + } +} + +func checkContextFileInXDGConfigHome(t *testing.T, ctx *core.CheckFuncCtx) { + t.Helper() + checkContextFile(t, ctx) + + result := ctx.Result.(*core.SuccessResult) + expectedContextFile := result.Resource + xdgConfigHome := ctx.OverrideEnv["XDG_CONFIG_HOME"] + tmpHomeDir := ctx.OverrideEnv["HOME"] + + expectedContextDir := filepath.Join(xdgConfigHome, "nats", "context") + assert.Contains( + t, + expectedContextFile, + expectedContextDir, + "Context file should be in XDG_CONFIG_HOME/nats/context, got: %s", + expectedContextFile, + ) + + tmpHomeNatsDir := filepath.Join(tmpHomeDir, ".config", "nats", "context") + _, err := os.Stat(tmpHomeNatsDir) + assert.True( + t, + os.IsNotExist(err), + "Files should not be created in HOME/.config/nats/context when XDG_CONFIG_HOME is set: %s", + tmpHomeNatsDir, + ) +} + +func afterFuncDeleteFiles(ctx *core.AfterFuncCtx) error { + if ctx.Meta["deleteFiles"] == nil { + return nil + } + filesToDelete := ctx.Meta["deleteFiles"].([]string) + for _, file := range filesToDelete { + if err := os.Remove(file); err != nil { + return err + } + } + + return nil +} + +func Test_CreateContextWithXDGConfigHome(t *testing.T) { + xdgConfigHomeDir := t.TempDir() + + t.Run("XDG_CONFIG_HOME compliance", core.Test(&core.TestConfig{ + Commands: mnq.GetCommands(), + BeforeFunc: core.BeforeFuncCombine( + beforeFuncCopyConfigToTmpHome(), + createNATSAccount("NATS"), + ), + Cmd: "scw mnq nats create-context nats-account-id={{ .NATS.ID }}", + TmpHomeDir: true, + OverrideEnv: map[string]string{ + "XDG_CONFIG_HOME": xdgConfigHomeDir, + }, + Check: core.TestCheckCombine( + core.TestCheckExitCode(0), + core.TestCheckGoldenAndReplacePatterns( + core.GoldenReplacement{ + Pattern: regexp.MustCompile(`cli[\w-]*creds[\w-]*`), + Replacement: "credential-placeholder", + }, + core.GoldenReplacement{ + Pattern: regexp.MustCompile( + "(Select context using `nats context select )cli[\\w-]*`", + ), + Replacement: "Select context using `nats context select context-placeholder`", + }, + ), + checkContextFileInXDGConfigHome, + ), + AfterFunc: core.AfterFuncCombine( + deleteNATSAccount("NATS"), + afterFuncDeleteFiles, + ), + })) +} diff --git a/internal/namespaces/mnq/v1beta1/testdata/test-create-context-with-xdg-config-home-xdgconfighome-compliance.cassette.yaml b/internal/namespaces/mnq/v1beta1/testdata/test-create-context-with-xdg-config-home-xdgconfighome-compliance.cassette.yaml new file mode 100644 index 0000000000..ebc1076535 --- /dev/null +++ b/internal/namespaces/mnq/v1beta1/testdata/test-create-context-with-xdg-config-home-xdgconfighome-compliance.cassette.yaml @@ -0,0 +1,201 @@ +--- +version: 1 +interactions: +- request: + body: '{"project_id":"46fd79d8-1a35-4548-bfb8-03df51a0ebae", "region":"fr-par", + "created_at":"2025-12-04T05:08:07.797431533Z", "updated_at":"2025-12-04T05:08:07.797431533Z", + "id":"AAFJH7WGGU2YRNIXDC5IRRBOFYMPU6D5UJLGPWCVI3VWIJQFLOX2JB75", "name":"cli-mnq-vibrant-antonelli", + "endpoint":"nats://nats.mnq.fr-par.scaleway.com:4222"}' + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.25.3; darwin; amd64) cli-e2e-test + url: https://api.scaleway.com/mnq/v1beta1/regions/fr-par/nats-accounts + method: POST + response: + body: '{"project_id":"46fd79d8-1a35-4548-bfb8-03df51a0ebae", "region":"fr-par", + "created_at":"2025-12-04T05:08:07.797431533Z", "updated_at":"2025-12-04T05:08:07.797431533Z", + "id":"AAFJH7WGGU2YRNIXDC5IRRBOFYMPU6D5UJLGPWCVI3VWIJQFLOX2JB75", "name":"cli-mnq-vibrant-antonelli", + "endpoint":"nats://nats.mnq.fr-par.scaleway.com:4222"}' + headers: + Content-Length: + - "322" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Thu, 04 Dec 2025 05:08:07 GMT + Server: + - Scaleway API Gateway (fr-par-2;edge03) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - edffd555-1943-4ed1-a5bf-76b020ab1d9b + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"access_key":"SCW8XSQX472BP8SNX6HA", "secret_key":null, "description":"", + "created_at":"2025-11-21T09:36:03.459154Z", "updated_at":"2025-11-21T09:36:03.459154Z", + "expires_at":null, "default_project_id":"46fd79d8-1a35-4548-bfb8-03df51a0ebae", + "editable":true, "deletable":true, "managed":false, "creation_ip":"51.159.46.153", + "user_id":"a52be78d-a4f2-4d4f-a4e6-8bf74589bfd8"}' + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.25.3; darwin; amd64) cli-e2e-test + url: https://api.scaleway.com/iam/v1alpha1/api-keys/SCW8XSQX472BP8SNX6HA + method: GET + response: + body: '{"access_key":"SCW8XSQX472BP8SNX6HA", "secret_key":null, "description":"", + "created_at":"2025-11-21T09:36:03.459154Z", "updated_at":"2025-11-21T09:36:03.459154Z", + "expires_at":null, "default_project_id":"46fd79d8-1a35-4548-bfb8-03df51a0ebae", + "editable":true, "deletable":true, "managed":false, "creation_ip":"51.159.46.153", + "user_id":"a52be78d-a4f2-4d4f-a4e6-8bf74589bfd8"}' + headers: + Content-Length: + - "375" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Thu, 04 Dec 2025 05:08:08 GMT + Server: + - Scaleway API Gateway (fr-par-2;edge03) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 8031e99a-c130-455c-a74b-a48a68c9c92a + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"project_id":"46fd79d8-1a35-4548-bfb8-03df51a0ebae", "region":"fr-par", + "created_at":"2025-12-04T05:08:07.797431Z", "updated_at":"2025-12-04T05:08:07.797431Z", + "id":"AAFJH7WGGU2YRNIXDC5IRRBOFYMPU6D5UJLGPWCVI3VWIJQFLOX2JB75", "name":"cli-mnq-vibrant-antonelli", + "endpoint":"nats://nats.mnq.fr-par.scaleway.com:4222"}' + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.25.3; darwin; amd64) cli-e2e-test + url: https://api.scaleway.com/mnq/v1beta1/regions/fr-par/nats-accounts/AAFJH7WGGU2YRNIXDC5IRRBOFYMPU6D5UJLGPWCVI3VWIJQFLOX2JB75 + method: GET + response: + body: '{"project_id":"46fd79d8-1a35-4548-bfb8-03df51a0ebae", "region":"fr-par", + "created_at":"2025-12-04T05:08:07.797431Z", "updated_at":"2025-12-04T05:08:07.797431Z", + "id":"AAFJH7WGGU2YRNIXDC5IRRBOFYMPU6D5UJLGPWCVI3VWIJQFLOX2JB75", "name":"cli-mnq-vibrant-antonelli", + "endpoint":"nats://nats.mnq.fr-par.scaleway.com:4222"}' + headers: + Content-Length: + - "316" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Thu, 04 Dec 2025 05:08:08 GMT + Server: + - Scaleway API Gateway (fr-par-2;edge03) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 6eeac215-6850-424b-b7c3-8ef6fda6184e + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"nats_account_id":"AAFJH7WGGU2YRNIXDC5IRRBOFYMPU6D5UJLGPWCVI3VWIJQFLOX2JB75", + "region":"fr-par", "created_at":"2025-12-04T05:08:08.325079040Z", "updated_at":"2025-12-04T05:08:08.325079290Z", + "id":"d21c9f77-e065-490e-b4fc-a2feb3e4d90f", "name":"cli-mnq-vibrant-antonellicli-creds-priceless-bhabha", + "credentials":{"name":"user.creds", "content":"-----BEGIN NATS USER JWT-----\neyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJSNDNZRVhBT1NZTExJUkFDR01WUDJMU1JDWEMyWVk2UlVQN1hQRUxDNlBJQ1pQNlRLNEtBIiwiaWF0IjoxNzY0ODI0ODg4LCJpc3MiOiJBREFXV0dMVVJLTVdYWlYyVldJWktDNkMzUUNZMjNKN1BJRU5BTDZaUlhSRzJLTUtKT0FUSEJNSSIsIm5hbWUiOiJjbGktbW5xLXZpYnJhbnQtYW50b25lbGxpY2xpLWNyZWRzLXByaWNlbGVzcy1iaGFiaGEiLCJzdWIiOiJVQkFYVzMzQTc2UFY3SUQ0UUtHWFdaUkpSQlJRT1ZHWVNCM1NRNDVQQldNUFBBT0VOMlFMSlJZVyIsIm5hdHMiOnsicHViIjp7fSwic3ViIjp7fSwic3VicyI6LTEsImRhdGEiOi0xLCJwYXlsb2FkIjotMSwiaXNzdWVyX2FjY291bnQiOiJBQUZKSDdXR0dVMllSTklYREM1SVJSQk9GWU1QVTZENVVKTEdQV0NWSTNWV0lKUUZMT1gySkI3NSIsInR5cGUiOiJ1c2VyIiwidmVyc2lvbiI6Mn19.zaqi9Sf5iBSWXbj-THzcti2GFxpim8O_MhgWsYbDIEc0M39Er-Wgyspw4APxYDsqqBQRNnKfaYSxmUbz-mPUBg\n------END + NATS USER JWT------\n\n************************* IMPORTANT *************************\nNKEY + Seed printed below can be used to sign and prove identity.\nNKEYs are sensitive + and should be treated as secrets.\n\n-----BEGIN USER NKEY SEED-----\nSUAGEIKEUA7L46FYGMVMDQSQEBIVRPJAHEEC23SEMCAR4X5VFAXQPPPC7Q\n------END + USER NKEY SEED------\n\n*************************************************************\n"}, + "checksum":"1313cedb5035d6df3ad1b43112fd6b0ba32dd1dd"}' + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.25.3; darwin; amd64) cli-e2e-test + url: https://api.scaleway.com/mnq/v1beta1/regions/fr-par/nats-credentials + method: POST + response: + body: '{"nats_account_id":"AAFJH7WGGU2YRNIXDC5IRRBOFYMPU6D5UJLGPWCVI3VWIJQFLOX2JB75", + "region":"fr-par", "created_at":"2025-12-04T05:08:08.325079040Z", "updated_at":"2025-12-04T05:08:08.325079290Z", + "id":"d21c9f77-e065-490e-b4fc-a2feb3e4d90f", "name":"cli-mnq-vibrant-antonellicli-creds-priceless-bhabha", + "credentials":{"name":"user.creds", "content":"-----BEGIN NATS USER JWT-----\neyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJSNDNZRVhBT1NZTExJUkFDR01WUDJMU1JDWEMyWVk2UlVQN1hQRUxDNlBJQ1pQNlRLNEtBIiwiaWF0IjoxNzY0ODI0ODg4LCJpc3MiOiJBREFXV0dMVVJLTVdYWlYyVldJWktDNkMzUUNZMjNKN1BJRU5BTDZaUlhSRzJLTUtKT0FUSEJNSSIsIm5hbWUiOiJjbGktbW5xLXZpYnJhbnQtYW50b25lbGxpY2xpLWNyZWRzLXByaWNlbGVzcy1iaGFiaGEiLCJzdWIiOiJVQkFYVzMzQTc2UFY3SUQ0UUtHWFdaUkpSQlJRT1ZHWVNCM1NRNDVQQldNUFBBT0VOMlFMSlJZVyIsIm5hdHMiOnsicHViIjp7fSwic3ViIjp7fSwic3VicyI6LTEsImRhdGEiOi0xLCJwYXlsb2FkIjotMSwiaXNzdWVyX2FjY291bnQiOiJBQUZKSDdXR0dVMllSTklYREM1SVJSQk9GWU1QVTZENVVKTEdQV0NWSTNWV0lKUUZMT1gySkI3NSIsInR5cGUiOiJ1c2VyIiwidmVyc2lvbiI6Mn19.zaqi9Sf5iBSWXbj-THzcti2GFxpim8O_MhgWsYbDIEc0M39Er-Wgyspw4APxYDsqqBQRNnKfaYSxmUbz-mPUBg\n------END + NATS USER JWT------\n\n************************* IMPORTANT *************************\nNKEY + Seed printed below can be used to sign and prove identity.\nNKEYs are sensitive + and should be treated as secrets.\n\n-----BEGIN USER NKEY SEED-----\nSUAGEIKEUA7L46FYGMVMDQSQEBIVRPJAHEEC23SEMCAR4X5VFAXQPPPC7Q\n------END + USER NKEY SEED------\n\n*************************************************************\n"}, + "checksum":"1313cedb5035d6df3ad1b43112fd6b0ba32dd1dd"}' + headers: + Content-Length: + - "1554" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Thu, 04 Dec 2025 05:08:08 GMT + Server: + - Scaleway API Gateway (fr-par-2;edge03) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 628cf359-ba2b-4963-9d5d-4b20dec718e4 + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.25.3; darwin; amd64) cli-e2e-test + url: https://api.scaleway.com/mnq/v1beta1/regions/fr-par/nats-accounts/AAFJH7WGGU2YRNIXDC5IRRBOFYMPU6D5UJLGPWCVI3VWIJQFLOX2JB75 + method: DELETE + response: + body: "" + headers: + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Thu, 04 Dec 2025 05:08:08 GMT + Server: + - Scaleway API Gateway (fr-par-2;edge03) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 19e34f27-9202-436b-9f8b-884e58cb49a4 + status: 204 No Content + code: 204 + duration: "" diff --git a/internal/namespaces/mnq/v1beta1/testdata/test-create-context-with-xdg-config-home-xdgconfighome-compliance.golden b/internal/namespaces/mnq/v1beta1/testdata/test-create-context-with-xdg-config-home-xdgconfighome-compliance.golden new file mode 100644 index 0000000000..fd84f10544 --- /dev/null +++ b/internal/namespaces/mnq/v1beta1/testdata/test-create-context-with-xdg-config-home-xdgconfighome-compliance.golden @@ -0,0 +1,10 @@ +🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲 +🟩🟩🟩 STDOUT️ 🟩🟩🟩️ +βœ… Nats context successfully created. + credential-placeholder nats credentials was created + Select context using `nats context select context-placeholder` +🟩🟩🟩 JSON STDOUT 🟩🟩🟩 +{ + "message": "Nats context successfully created", + "details": "credential-placeholder nats credentials was created\nSelect context using `nats context select context-placeholder`" +}