From c828891f32735d8e848f3bc84232012870a10bbf Mon Sep 17 00:00:00 2001 From: Bujjibabukatta Date: Tue, 25 Aug 2026 17:44:26 +0530 Subject: [PATCH] fix(gh-copilot): trim enterprise/organization slugs to fix connection validation failure --- backend/plugins/gh-copilot/api/connection.go | 4 +- .../plugins/gh-copilot/api/connection_test.go | 50 +++++++++++++++++++ .../plugins/gh-copilot/models/connection.go | 2 + .../service/connection_test_helper.go | 2 +- config-ui/src/api/connection/index.ts | 2 + config-ui/src/types/connection.ts | 2 + 6 files changed, 60 insertions(+), 2 deletions(-) diff --git a/backend/plugins/gh-copilot/api/connection.go b/backend/plugins/gh-copilot/api/connection.go index fe02f16d0cb..2d3d12cdd04 100644 --- a/backend/plugins/gh-copilot/api/connection.go +++ b/backend/plugins/gh-copilot/api/connection.go @@ -18,6 +18,8 @@ limitations under the License. package api import ( + "strings" + "github.com/apache/incubator-devlake/core/errors" "github.com/apache/incubator-devlake/core/plugin" helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api" @@ -93,7 +95,7 @@ func validateConnection(connection *models.GhCopilotConnection) errors.Error { if connection == nil { return errors.BadInput.New("connection is required") } - if connection.Organization == "" && !connection.HasEnterprise() { + if strings.TrimSpace(connection.Organization) == "" && !connection.HasEnterprise() { return errors.BadInput.New("either enterprise or organization is required") } if connection.Token == "" { diff --git a/backend/plugins/gh-copilot/api/connection_test.go b/backend/plugins/gh-copilot/api/connection_test.go index aa16ff6cfbf..cfd2bc7ee6d 100644 --- a/backend/plugins/gh-copilot/api/connection_test.go +++ b/backend/plugins/gh-copilot/api/connection_test.go @@ -18,13 +18,63 @@ limitations under the License. package api import ( + "encoding/json" "testing" "github.com/stretchr/testify/assert" + helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api" "github.com/apache/incubator-devlake/plugins/gh-copilot/models" ) +func decodeBody(t *testing.T, raw string) map[string]interface{} { + t.Helper() + var body map[string]interface{} + if err := json.Unmarshal([]byte(raw), &body); err != nil { + t.Fatalf("failed to unmarshal test payload: %v", err) + } + return body +} + +func TestValidateConnection_EnterpriseSlugOnly_ViaDecode(t *testing.T) { + body := decodeBody(t, `{ + "name": "my-copilot-conn", + "endpoint": "https://api.github.com", + "organization": "", + "enterprise": "my-company", + "token": "ghp_example", + "rateLimitPerHour": 5000 + }`) + + connection := &models.GhCopilotConnection{} + assert.NoError(t, helper.Decode(body, connection, vld)) + + connection.Normalize() + assert.Equal(t, "my-company", connection.Enterprise) + assert.True(t, connection.HasEnterprise()) + + err := validateConnection(connection) + assert.NoError(t, err) +} + +func TestValidateConnection_EnterpriseSlugWithWhitespace_ViaDecode(t *testing.T) { + body := decodeBody(t, `{ + "organization": " ", + "enterprise": " my-company ", + "token": "ghp_example" + }`) + + connection := &models.GhCopilotConnection{} + assert.NoError(t, helper.Decode(body, connection, vld)) + + connection.Normalize() + assert.Equal(t, "my-company", connection.Enterprise) + assert.Equal(t, "", connection.Organization) + + err := validateConnection(connection) + assert.NoError(t, err) +} + func TestValidateConnection_Success(t *testing.T) { connection := &models.GhCopilotConnection{ GhCopilotConn: models.GhCopilotConn{ diff --git a/backend/plugins/gh-copilot/models/connection.go b/backend/plugins/gh-copilot/models/connection.go index 676d06b4f85..92c8c0712c0 100644 --- a/backend/plugins/gh-copilot/models/connection.go +++ b/backend/plugins/gh-copilot/models/connection.go @@ -118,4 +118,6 @@ func (connection *GhCopilotConnection) Normalize() { if connection.RateLimitPerHour <= 0 { connection.RateLimitPerHour = DefaultRateLimitPerHour } + connection.Organization = strings.TrimSpace(connection.Organization) + connection.Enterprise = strings.TrimSpace(connection.Enterprise) } diff --git a/backend/plugins/gh-copilot/service/connection_test_helper.go b/backend/plugins/gh-copilot/service/connection_test_helper.go index 8891476020f..41ddcfc686d 100644 --- a/backend/plugins/gh-copilot/service/connection_test_helper.go +++ b/backend/plugins/gh-copilot/service/connection_test_helper.go @@ -64,7 +64,7 @@ func TestConnection(ctx stdctx.Context, br corectx.BasicRes, connection *models. hasOrg := strings.TrimSpace(connection.Organization) != "" if !hasEnterprise && !hasOrg { - return nil, errors.BadInput.New("either enterprise or organization must be specified") + return nil, errors.BadInput.New("either enterprise or organization is required") } apiClient, err := helper.NewApiClientFromConnection(ctx, br, connection) diff --git a/config-ui/src/api/connection/index.ts b/config-ui/src/api/connection/index.ts index 2728b103ecc..a09e6450b6c 100644 --- a/config-ui/src/api/connection/index.ts +++ b/config-ui/src/api/connection/index.ts @@ -56,6 +56,7 @@ export const test = ( | 'adminApiKey' | 'organization' | 'organizationId' + | 'enterprise' | 'customHeaders' > >, @@ -79,6 +80,7 @@ export const testOld = ( | 'adminApiKey' | 'organization' | 'organizationId' + | 'enterprise' | 'customHeaders' >, ): Promise => request(`/plugins/${plugin}/test`, { method: 'post', data: payload }); diff --git a/config-ui/src/types/connection.ts b/config-ui/src/types/connection.ts index 17a3b85af0e..df04a45b5d6 100644 --- a/config-ui/src/types/connection.ts +++ b/config-ui/src/types/connection.ts @@ -38,6 +38,7 @@ export interface IConnectionAPI { rateLimitPerHour?: number; organization?: string; organizationId?: string; + enterprise?: string; customHeaders?: ICustomHeader[]; } @@ -95,5 +96,6 @@ export interface IConnection { rateLimitPerHour?: number; organization?: string; organizationId?: string; + enterprise?: string; customHeaders?: ICustomHeader[]; }