diff --git a/pkg/cmd/secret/create/create.go b/pkg/cmd/secret/create/create.go index f96844d..3e63f39 100644 --- a/pkg/cmd/secret/create/create.go +++ b/pkg/cmd/secret/create/create.go @@ -79,6 +79,23 @@ func actionRun(opts *Options) error { if err != nil { return err } + if opts.ID != "" { + id := strings.TrimSpace(opts.ID) + if id == "" { + return fmt.Errorf("secret provider id is required; use a positional arg or --id") + } + payload["id"] = id + } else { + id, ok := payload["id"] + if !ok || id == nil { + return fmt.Errorf("secret provider id is required; use a positional arg or --id") + } + trimmedID := strings.TrimSpace(fmt.Sprint(id)) + if trimmedID == "" { + return fmt.Errorf("secret provider id is required; use a positional arg or --id") + } + payload["id"] = trimmedID + } httpClient, err := opts.Client() if err != nil { diff --git a/pkg/cmd/secret/create/create_test.go b/pkg/cmd/secret/create/create_test.go index 68afed5..0331164 100644 --- a/pkg/cmd/secret/create/create_test.go +++ b/pkg/cmd/secret/create/create_test.go @@ -3,6 +3,8 @@ package create import ( "encoding/json" "net/http" + "os" + "path/filepath" "testing" "github.com/api7/a7/internal/config" @@ -64,6 +66,92 @@ func TestCreateSecret_JSON(t *testing.T) { registry.Verify(t) } +func TestCreateSecret_FileUsesPositionalID(t *testing.T) { + ios, _, out, _ := iostreams.Test() + registry := &httpmock.Registry{} + registry.Register(http.MethodPut, "/apisix/admin/secret_providers/vault/s1", httpmock.JSONResponse(`{"id":"vault/s1","uri":"http://vault","prefix":"kv"}`)) + + file := filepath.Join(t.TempDir(), "secret.json") + if err := os.WriteFile(file, []byte(`{"uri":"http://vault","prefix":"kv","token":"tok"}`), 0644); err != nil { + t.Fatalf("failed to write temp file: %v", err) + } + + opts := &Options{ + IO: ios, + Client: func() (*http.Client, error) { return registry.GetClient(), nil }, + Config: func() (config.Config, error) { + return &mockConfig{baseURL: "http://api.local", gatewayGroup: "gg1"}, nil + }, + GatewayGroup: "gg1", + ID: "vault/s1", + File: file, + } + + if err := actionRun(opts); err != nil { + t.Fatalf("actionRun failed: %v", err) + } + + var item api.Secret + if err := json.Unmarshal(out.Bytes(), &item); err != nil { + t.Fatalf("failed to parse output: %v", err) + } + if item.ID != "vault/s1" { + t.Fatalf("expected positional id in output, got: %+v", item) + } + + registry.Verify(t) +} + +func TestCreateSecret_FileRequiresID(t *testing.T) { + ios, _, _, _ := iostreams.Test() + registry := &httpmock.Registry{} + + file := filepath.Join(t.TempDir(), "secret.json") + if err := os.WriteFile(file, []byte(`{"uri":"http://vault","prefix":"kv","token":"tok"}`), 0644); err != nil { + t.Fatalf("failed to write temp file: %v", err) + } + + err := actionRun(&Options{ + IO: ios, + Client: func() (*http.Client, error) { return registry.GetClient(), nil }, + Config: func() (config.Config, error) { + return &mockConfig{baseURL: "http://api.local", gatewayGroup: "gg1"}, nil + }, + GatewayGroup: "gg1", + File: file, + }) + if err == nil || err.Error() != "secret provider id is required; use a positional arg or --id" { + t.Fatalf("expected --id required error, got: %v", err) + } + + registry.Verify(t) +} + +func TestCreateSecret_FileRejectsWhitespaceID(t *testing.T) { + ios, _, _, _ := iostreams.Test() + registry := &httpmock.Registry{} + + file := filepath.Join(t.TempDir(), "secret.json") + if err := os.WriteFile(file, []byte(`{"id":" ","uri":"http://vault","prefix":"kv","token":"tok"}`), 0644); err != nil { + t.Fatalf("failed to write temp file: %v", err) + } + + err := actionRun(&Options{ + IO: ios, + Client: func() (*http.Client, error) { return registry.GetClient(), nil }, + Config: func() (config.Config, error) { + return &mockConfig{baseURL: "http://api.local", gatewayGroup: "gg1"}, nil + }, + GatewayGroup: "gg1", + File: file, + }) + if err == nil || err.Error() != "secret provider id is required; use a positional arg or --id" { + t.Fatalf("expected --id required error, got: %v", err) + } + + registry.Verify(t) +} + func TestCreateSecret_RequiresID(t *testing.T) { ios, _, _, _ := iostreams.Test() registry := &httpmock.Registry{} diff --git a/test/e2e/secret_test.go b/test/e2e/secret_test.go index 492ce3c..f626fdb 100644 --- a/test/e2e/secret_test.go +++ b/test/e2e/secret_test.go @@ -65,9 +65,7 @@ func TestSecret_CRUD(t *testing.T) { // Create stdout, stderr, err := runA7WithEnv(env, "secret", "create", secretID, "-f", tmpFile, "-g", gatewayGroup) - if err != nil { - t.Skip("secret create failed (vault may not be configured)") - } + require.NoError(t, err, "secret create failed: stdout=%s stderr=%s", stdout, stderr) // Get stdout, stderr, err = runA7WithEnv(env, "secret", "get", secretID, "-g", gatewayGroup) @@ -77,7 +75,9 @@ func TestSecret_CRUD(t *testing.T) { // Get JSON var secret map[string]interface{} runA7JSON(t, env, &secret, "secret", "get", secretID, "-g", gatewayGroup, "-o", "json") - assert.Equal(t, secretID, secret["id"]) + // The runtime secret provider API returns the provider-local ID; the CLI + // still uses the compound ID for addressing resources. + assert.Equal(t, "e2e-secret-crud", secret["id"]) assert.Equal(t, "https://vault.example.com", secret["uri"]) assert.Equal(t, "kv/apisix", secret["prefix"])