From 62893d878aaefc8def40068e2422009057023456 Mon Sep 17 00:00:00 2001 From: Alex Luong Date: Tue, 21 Oct 2025 20:13:56 +0700 Subject: [PATCH] chore: improve destgcppubsub validation --- .../providers/destgcppubsub/destgcppubsub.go | 17 +++++++++ .../destgcppubsub/destgcppubsub_test.go | 37 ++++++++++++++++++- .../tests/destinations/gcp-pubsub.test.ts | 3 +- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/internal/destregistry/providers/destgcppubsub/destgcppubsub.go b/internal/destregistry/providers/destgcppubsub/destgcppubsub.go index 07086ee7..472be4d3 100644 --- a/internal/destregistry/providers/destgcppubsub/destgcppubsub.go +++ b/internal/destregistry/providers/destgcppubsub/destgcppubsub.go @@ -93,6 +93,23 @@ func (d *GCPPubSubDestination) resolveMetadata(ctx context.Context, destination return nil, nil, err } + // Validate service_account_json is valid JSON (if not using emulator endpoint) + serviceAccountJSON := destination.Credentials["service_account_json"] + endpoint := destination.Config["endpoint"] + + // Only validate JSON if we're not using an emulator endpoint and service_account_json is provided + if endpoint == "" && serviceAccountJSON != "" { + var jsonCheck map[string]interface{} + if err := json.Unmarshal([]byte(serviceAccountJSON), &jsonCheck); err != nil { + return nil, nil, destregistry.NewErrDestinationValidation([]destregistry.ValidationErrorDetail{ + { + Field: "credentials.service_account_json", + Type: "format", + }, + }) + } + } + return &GCPPubSubDestinationConfig{ ProjectID: destination.Config["project_id"], Topic: destination.Config["topic"], diff --git a/internal/destregistry/providers/destgcppubsub/destgcppubsub_test.go b/internal/destregistry/providers/destgcppubsub/destgcppubsub_test.go index 8df308a9..6985585d 100644 --- a/internal/destregistry/providers/destgcppubsub/destgcppubsub_test.go +++ b/internal/destregistry/providers/destgcppubsub/destgcppubsub_test.go @@ -164,7 +164,42 @@ func TestValidate(t *testing.T) { credentials: map[string]string{ "service_account_json": "not-valid-json", }, - wantErr: false, // We don't validate JSON structure anymore, Google SDK will handle it + wantErr: true, + errContains: "credentials.service_account_json", + }, + { + name: "invalid JSON in service_account_json - but passes with emulator endpoint", + config: map[string]string{ + "project_id": "my-project", + "topic": "my-topic", + "endpoint": "http://localhost:8085", + }, + credentials: map[string]string{ + "service_account_json": "not-valid-json", + }, + wantErr: false, // Emulator doesn't require valid JSON + }, + { + name: "valid service account JSON with complete structure", + config: map[string]string{ + "project_id": "my-project", + "topic": "my-topic", + }, + credentials: map[string]string{ + "service_account_json": `{ + "type": "service_account", + "project_id": "my-project", + "private_key_id": "key123", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----\n", + "client_email": "my-service@my-project.iam.gserviceaccount.com", + "client_id": "123456789", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/my-service%40my-project.iam.gserviceaccount.com" + }`, + }, + wantErr: false, }, { name: "valid with all optional fields", diff --git a/spec-sdk-tests/tests/destinations/gcp-pubsub.test.ts b/spec-sdk-tests/tests/destinations/gcp-pubsub.test.ts index 54d038d7..1e507b37 100644 --- a/spec-sdk-tests/tests/destinations/gcp-pubsub.test.ts +++ b/spec-sdk-tests/tests/destinations/gcp-pubsub.test.ts @@ -214,8 +214,7 @@ describe('GCP Pub/Sub Destinations - Contract Tests (SDK-based validation)', () } }); - // TODO: Re-enable this test once the backend validates the contents of the serviceAccountJson. - it.skip('should reject creation with invalid serviceAccountJson', async () => { + it('should reject creation with invalid serviceAccountJson', async () => { let errorThrown = false; try { await client.createDestination({