From 2d74ffc8b2fd527277476502d346c27350145f42 Mon Sep 17 00:00:00 2001 From: VirajSalaka Date: Tue, 3 Feb 2026 23:46:24 +0530 Subject: [PATCH 1/3] Support storing properties in the gateway --- platform-api/src/internal/dto/gateway.go | 33 +++---- platform-api/src/internal/handler/gateway.go | 4 +- platform-api/src/internal/model/gateway.go | 13 +-- .../src/internal/repository/gateway.go | 88 ++++++++++++++++--- platform-api/src/internal/service/gateway.go | 12 ++- platform-api/src/resources/openapi.yaml | 15 ++++ 6 files changed, 127 insertions(+), 38 deletions(-) diff --git a/platform-api/src/internal/dto/gateway.go b/platform-api/src/internal/dto/gateway.go index ebc75d16d..3aaa4cf47 100644 --- a/platform-api/src/internal/dto/gateway.go +++ b/platform-api/src/internal/dto/gateway.go @@ -23,22 +23,24 @@ import ( // CreateGatewayRequest represents the request body for registering a new gateway type CreateGatewayRequest struct { - Name string `json:"name" binding:"required"` - DisplayName string `json:"displayName" binding:"required"` - Description string `json:"description,omitempty"` - Vhost string `json:"vhost" binding:"required"` - IsCritical bool `json:"isCritical,omitempty"` - FunctionalityType string `json:"functionalityType" binding:"required"` + Name string `json:"name" binding:"required"` + DisplayName string `json:"displayName" binding:"required"` + Description string `json:"description,omitempty"` + Vhost string `json:"vhost" binding:"required"` + IsCritical bool `json:"isCritical,omitempty"` + FunctionalityType string `json:"functionalityType" binding:"required"` + Properties map[string]interface{} `json:"properties,omitempty"` } // GatewayResponse represents a gateway in API responses type GatewayResponse struct { - ID string `json:"id"` - OrganizationID string `json:"organizationId"` - Name string `json:"name"` - DisplayName string `json:"displayName"` - Description string `json:"description,omitempty"` - Vhost string `json:"vhost"` + ID string `json:"id"` + OrganizationID string `json:"organizationId"` + Name string `json:"name"` + DisplayName string `json:"displayName"` + Description string `json:"description,omitempty"` + Properties map[string]interface{} `json:"properties,omitempty"` + Vhost string `json:"vhost"` IsCritical bool `json:"isCritical"` FunctionalityType string `json:"functionalityType"` IsActive bool `json:"isActive"` @@ -55,9 +57,10 @@ type GatewayListResponse struct { // UpdateGatewayRequest represents the request body for updating gateway details type UpdateGatewayRequest struct { - DisplayName *string `json:"displayName,omitempty"` - Description *string `json:"description,omitempty"` - IsCritical *bool `json:"isCritical,omitempty"` + DisplayName *string `json:"displayName,omitempty"` + Description *string `json:"description,omitempty"` + IsCritical *bool `json:"isCritical,omitempty"` + Properties *map[string]interface{} `json:"properties,omitempty"` } // TokenRotationResponse represents the response when rotating a gateway token diff --git a/platform-api/src/internal/handler/gateway.go b/platform-api/src/internal/handler/gateway.go index 100d2bcee..2be6c2c5f 100644 --- a/platform-api/src/internal/handler/gateway.go +++ b/platform-api/src/internal/handler/gateway.go @@ -58,7 +58,7 @@ func (h *GatewayHandler) CreateGateway(c *gin.Context) { } response, err := h.gatewayService.RegisterGateway(orgId, req.Name, req.DisplayName, req.Description, req.Vhost, - req.IsCritical, req.FunctionalityType) + req.IsCritical, req.FunctionalityType, req.Properties) if err != nil { errMsg := err.Error() @@ -214,7 +214,7 @@ func (h *GatewayHandler) UpdateGateway(c *gin.Context) { return } - gateway, err := h.gatewayService.UpdateGateway(gatewayId, orgId, req.Description, req.DisplayName, req.IsCritical) + gateway, err := h.gatewayService.UpdateGateway(gatewayId, orgId, req.Description, req.DisplayName, req.IsCritical, req.Properties) if err != nil { if errors.Is(err, constants.ErrGatewayNotFound) { utils.LogError("Gateway not found during update", err) diff --git a/platform-api/src/internal/model/gateway.go b/platform-api/src/internal/model/gateway.go index ad92dbe42..3e616e342 100644 --- a/platform-api/src/internal/model/gateway.go +++ b/platform-api/src/internal/model/gateway.go @@ -23,12 +23,13 @@ import ( // Gateway represents a registered gateway instance within an organization type Gateway struct { - ID string `json:"id" db:"uuid"` - OrganizationID string `json:"organizationId" db:"organization_uuid"` - Name string `json:"name" db:"name"` - DisplayName string `json:"displayName" db:"display_name"` - Description string `json:"description" db:"description"` - Vhost string `json:"vhost" db:"vhost"` + ID string `json:"id" db:"uuid"` + OrganizationID string `json:"organizationId" db:"organization_uuid"` + Name string `json:"name" db:"name"` + DisplayName string `json:"displayName" db:"display_name"` + Description string `json:"description" db:"description"` + Properties map[string]interface{} `json:"properties,omitempty" db:"properties"` + Vhost string `json:"vhost" db:"vhost"` IsCritical bool `json:"isCritical" db:"is_critical"` FunctionalityType string `json:"functionalityType" db:"gateway_functionality_type"` IsActive bool `json:"isActive" db:"is_active"` diff --git a/platform-api/src/internal/repository/gateway.go b/platform-api/src/internal/repository/gateway.go index 9232dee8e..a33f427b9 100644 --- a/platform-api/src/internal/repository/gateway.go +++ b/platform-api/src/internal/repository/gateway.go @@ -19,7 +19,9 @@ package repository import ( "database/sql" + "encoding/json" "errors" + "fmt" "time" "platform-api/src/internal/database" @@ -42,13 +44,25 @@ func (r *GatewayRepo) Create(gateway *model.Gateway) error { gateway.UpdatedAt = time.Now() gateway.IsActive = false // Set default value to false at registration + // Serialize properties to JSON + var propertiesJSON string + if gateway.Properties != nil { + jsonBytes, err := json.Marshal(gateway.Properties) + if err != nil { + return fmt.Errorf("failed to marshal properties: %w", err) + } + propertiesJSON = string(jsonBytes) + } else { + propertiesJSON = "{}" + } + query := ` - INSERT INTO gateways (uuid, organization_uuid, name, display_name, description, vhost, is_critical, + INSERT INTO gateways (uuid, organization_uuid, name, display_name, description, properties, vhost, is_critical, gateway_functionality_type, is_active, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ` _, err := r.db.Exec(r.db.Rebind(query), gateway.ID, gateway.OrganizationID, gateway.Name, gateway.DisplayName, - gateway.Description, gateway.Vhost, gateway.IsCritical, gateway.FunctionalityType, gateway.IsActive, + gateway.Description, propertiesJSON, gateway.Vhost, gateway.IsCritical, gateway.FunctionalityType, gateway.IsActive, gateway.CreatedAt, gateway.UpdatedAt) return err } @@ -56,14 +70,15 @@ func (r *GatewayRepo) Create(gateway *model.Gateway) error { // GetByUUID retrieves a gateway by ID func (r *GatewayRepo) GetByUUID(gatewayId string) (*model.Gateway, error) { gateway := &model.Gateway{} + var propertiesJSON string query := ` - SELECT uuid, organization_uuid, name, display_name, description, vhost, is_critical, gateway_functionality_type, is_active, + SELECT uuid, organization_uuid, name, display_name, description, properties, vhost, is_critical, gateway_functionality_type, is_active, created_at, updated_at FROM gateways WHERE uuid = ? ` err := r.db.QueryRow(r.db.Rebind(query), gatewayId).Scan( - &gateway.ID, &gateway.OrganizationID, &gateway.Name, &gateway.DisplayName, &gateway.Description, &gateway.Vhost, + &gateway.ID, &gateway.OrganizationID, &gateway.Name, &gateway.DisplayName, &gateway.Description, &propertiesJSON, &gateway.Vhost, &gateway.IsCritical, &gateway.FunctionalityType, &gateway.IsActive, &gateway.CreatedAt, &gateway.UpdatedAt) if err != nil { if errors.Is(err, sql.ErrNoRows) { @@ -71,13 +86,21 @@ func (r *GatewayRepo) GetByUUID(gatewayId string) (*model.Gateway, error) { } return nil, err } + + // Deserialize properties from JSON + if propertiesJSON != "" && propertiesJSON != "{}" { + if err := json.Unmarshal([]byte(propertiesJSON), &gateway.Properties); err != nil { + return nil, fmt.Errorf("failed to unmarshal properties: %w", err) + } + } + return gateway, nil } // GetByOrganizationID retrieves all gateways for an organization func (r *GatewayRepo) GetByOrganizationID(orgID string) ([]*model.Gateway, error) { query := ` - SELECT uuid, organization_uuid, name, display_name, description, vhost, is_critical, gateway_functionality_type, is_active, + SELECT uuid, organization_uuid, name, display_name, description, properties, vhost, is_critical, gateway_functionality_type, is_active, created_at, updated_at FROM gateways WHERE organization_uuid = ? @@ -92,12 +115,21 @@ func (r *GatewayRepo) GetByOrganizationID(orgID string) ([]*model.Gateway, error var gateways []*model.Gateway for rows.Next() { gateway := &model.Gateway{} + var propertiesJSON string err := rows.Scan( - &gateway.ID, &gateway.OrganizationID, &gateway.Name, &gateway.DisplayName, &gateway.Description, &gateway.Vhost, + &gateway.ID, &gateway.OrganizationID, &gateway.Name, &gateway.DisplayName, &gateway.Description, &propertiesJSON, &gateway.Vhost, &gateway.IsCritical, &gateway.FunctionalityType, &gateway.IsActive, &gateway.CreatedAt, &gateway.UpdatedAt) if err != nil { return nil, err } + + // Deserialize properties from JSON + if propertiesJSON != "" && propertiesJSON != "{}" { + if err := json.Unmarshal([]byte(propertiesJSON), &gateway.Properties); err != nil { + return nil, fmt.Errorf("failed to unmarshal properties: %w", err) + } + } + gateways = append(gateways, gateway) } return gateways, nil @@ -106,14 +138,15 @@ func (r *GatewayRepo) GetByOrganizationID(orgID string) ([]*model.Gateway, error // GetByNameAndOrgID checks if a gateway with the given name exists within an organization func (r *GatewayRepo) GetByNameAndOrgID(name, orgID string) (*model.Gateway, error) { gateway := &model.Gateway{} + var propertiesJSON string query := ` - SELECT uuid, organization_uuid, name, display_name, description, vhost, is_critical, gateway_functionality_type, is_active, + SELECT uuid, organization_uuid, name, display_name, description, properties, vhost, is_critical, gateway_functionality_type, is_active, created_at, updated_at FROM gateways WHERE name = ? AND organization_uuid = ? ` err := r.db.QueryRow(r.db.Rebind(query), name, orgID).Scan( - &gateway.ID, &gateway.OrganizationID, &gateway.Name, &gateway.DisplayName, &gateway.Description, &gateway.Vhost, + &gateway.ID, &gateway.OrganizationID, &gateway.Name, &gateway.DisplayName, &gateway.Description, &propertiesJSON, &gateway.Vhost, &gateway.IsCritical, &gateway.FunctionalityType, &gateway.IsActive, &gateway.CreatedAt, &gateway.UpdatedAt) if err != nil { if errors.Is(err, sql.ErrNoRows) { @@ -121,13 +154,21 @@ func (r *GatewayRepo) GetByNameAndOrgID(name, orgID string) (*model.Gateway, err } return nil, err } + + // Deserialize properties from JSON + if propertiesJSON != "" && propertiesJSON != "{}" { + if err := json.Unmarshal([]byte(propertiesJSON), &gateway.Properties); err != nil { + return nil, fmt.Errorf("failed to unmarshal properties: %w", err) + } + } + return gateway, nil } // List retrieves all gateways func (r *GatewayRepo) List() ([]*model.Gateway, error) { query := ` - SELECT uuid, organization_uuid, name, display_name, description, vhost, is_critical, gateway_functionality_type, is_active, + SELECT uuid, organization_uuid, name, display_name, description, properties, vhost, is_critical, gateway_functionality_type, is_active, created_at, updated_at FROM gateways ORDER BY created_at DESC @@ -141,12 +182,21 @@ func (r *GatewayRepo) List() ([]*model.Gateway, error) { var gateways []*model.Gateway for rows.Next() { gateway := &model.Gateway{} + var propertiesJSON string err := rows.Scan( - &gateway.ID, &gateway.OrganizationID, &gateway.Name, &gateway.DisplayName, &gateway.Description, &gateway.Vhost, + &gateway.ID, &gateway.OrganizationID, &gateway.Name, &gateway.DisplayName, &gateway.Description, &propertiesJSON, &gateway.Vhost, &gateway.IsCritical, &gateway.FunctionalityType, &gateway.IsActive, &gateway.CreatedAt, &gateway.UpdatedAt) if err != nil { return nil, err } + + // Deserialize properties from JSON + if propertiesJSON != "" && propertiesJSON != "{}" { + if err := json.Unmarshal([]byte(propertiesJSON), &gateway.Properties); err != nil { + return nil, fmt.Errorf("failed to unmarshal properties: %w", err) + } + } + gateways = append(gateways, gateway) } return gateways, nil @@ -192,12 +242,24 @@ func (r *GatewayRepo) Delete(gatewayID, organizationID string) error { // UpdateGateway updates gateway details func (r *GatewayRepo) UpdateGateway(gateway *model.Gateway) error { + // Serialize properties to JSON + var propertiesJSON string + if gateway.Properties != nil { + jsonBytes, err := json.Marshal(gateway.Properties) + if err != nil { + return fmt.Errorf("failed to marshal properties: %w", err) + } + propertiesJSON = string(jsonBytes) + } else { + propertiesJSON = "{}" + } + query := ` UPDATE gateways - SET display_name = ?, description = ?, is_critical = ?, updated_at = ? + SET display_name = ?, description = ?, is_critical = ?, properties = ?, updated_at = ? WHERE uuid = ? ` - _, err := r.db.Exec(r.db.Rebind(query), gateway.DisplayName, gateway.Description, gateway.IsCritical, gateway.UpdatedAt, gateway.ID) + _, err := r.db.Exec(r.db.Rebind(query), gateway.DisplayName, gateway.Description, gateway.IsCritical, propertiesJSON, gateway.UpdatedAt, gateway.ID) return err } diff --git a/platform-api/src/internal/service/gateway.go b/platform-api/src/internal/service/gateway.go index addde533b..750ed6ad9 100644 --- a/platform-api/src/internal/service/gateway.go +++ b/platform-api/src/internal/service/gateway.go @@ -56,7 +56,7 @@ func NewGatewayService(gatewayRepo repository.GatewayRepository, orgRepo reposit // RegisterGateway registers a new gateway with organization validation func (s *GatewayService) RegisterGateway(orgID, name, displayName, description, vhost string, isCritical bool, - functionalityType string) (*dto.GatewayResponse, error) { + functionalityType string, properties map[string]interface{}) (*dto.GatewayResponse, error) { // 1. Validate inputs if err := s.validateGatewayInput(orgID, name, displayName, vhost, functionalityType); err != nil { return nil, err @@ -90,6 +90,7 @@ func (s *GatewayService) RegisterGateway(orgID, name, displayName, description, Name: name, DisplayName: displayName, Description: description, + Properties: properties, Vhost: vhost, IsCritical: isCritical, FunctionalityType: functionalityType, @@ -141,6 +142,7 @@ func (s *GatewayService) RegisterGateway(orgID, name, displayName, description, Name: gateway.Name, DisplayName: gateway.DisplayName, Description: gateway.Description, + Properties: gateway.Properties, Vhost: gateway.Vhost, IsCritical: gateway.IsCritical, FunctionalityType: gateway.FunctionalityType, @@ -177,6 +179,7 @@ func (s *GatewayService) ListGateways(orgID *string) (*dto.GatewayListResponse, Name: gw.Name, DisplayName: gw.DisplayName, Description: gw.Description, + Properties: gw.Properties, Vhost: gw.Vhost, IsCritical: gw.IsCritical, FunctionalityType: gw.FunctionalityType, @@ -226,6 +229,7 @@ func (s *GatewayService) GetGateway(gatewayId, orgId string) (*dto.GatewayRespon Name: gateway.Name, DisplayName: gateway.DisplayName, Description: gateway.Description, + Properties: gateway.Properties, Vhost: gateway.Vhost, IsCritical: gateway.IsCritical, FunctionalityType: gateway.FunctionalityType, @@ -239,7 +243,7 @@ func (s *GatewayService) GetGateway(gatewayId, orgId string) (*dto.GatewayRespon // UpdateGateway updates gateway details func (s *GatewayService) UpdateGateway(gatewayId, orgId string, description, displayName *string, - isCritical *bool) (*dto.GatewayResponse, error) { + isCritical *bool, properties *map[string]interface{}) (*dto.GatewayResponse, error) { // Get existing gateway gateway, err := s.gatewayRepo.GetByUUID(gatewayId) if err != nil { @@ -261,6 +265,9 @@ func (s *GatewayService) UpdateGateway(gatewayId, orgId string, description, dis if isCritical != nil { gateway.IsCritical = *isCritical } + if properties != nil { + gateway.Properties = *properties + } gateway.UpdatedAt = time.Now() err = s.gatewayRepo.UpdateGateway(gateway) @@ -274,6 +281,7 @@ func (s *GatewayService) UpdateGateway(gatewayId, orgId string, description, dis Name: gateway.Name, DisplayName: gateway.DisplayName, Description: gateway.Description, + Properties: gateway.Properties, Vhost: gateway.Vhost, IsCritical: gateway.IsCritical, FunctionalityType: gateway.FunctionalityType, diff --git a/platform-api/src/resources/openapi.yaml b/platform-api/src/resources/openapi.yaml index 432611ba4..1a3917f2f 100644 --- a/platform-api/src/resources/openapi.yaml +++ b/platform-api/src/resources/openapi.yaml @@ -2580,6 +2580,11 @@ components: description: Type of gateway functionality default: regular example: "regular" + properties: + type: object + additionalProperties: true + description: Custom key-value properties for the gateway + example: {"region": "us-west", "tier": "premium"} GatewayResponse: type: object @@ -2606,6 +2611,11 @@ components: type: string description: Description of the gateway example: "Production gateway for handling API traffic" + properties: + type: object + additionalProperties: true + description: Custom key-value properties for the gateway + example: {"region": "us-west", "tier": "premium"} vhost: type: string description: Virtual host (domain name) for the gateway @@ -2651,6 +2661,11 @@ components: type: boolean description: Whether the gateway is critical for production example: true + properties: + type: object + additionalProperties: true + description: Custom key-value properties for the gateway + example: {"region": "us-west", "tier": "premium"} Pagination: type: object From 351456fba63fa4cac409349827afd16093e2d9d8 Mon Sep 17 00:00:00 2001 From: VirajSalaka Date: Wed, 4 Feb 2026 17:29:01 +0530 Subject: [PATCH 2/3] Update GetAPIGatewaysWithDetails to load properties from table --- platform-api/src/internal/model/gateway.go | 1 + platform-api/src/internal/repository/api.go | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/platform-api/src/internal/model/gateway.go b/platform-api/src/internal/model/gateway.go index 3e616e342..fd2b05442 100644 --- a/platform-api/src/internal/model/gateway.go +++ b/platform-api/src/internal/model/gateway.go @@ -78,6 +78,7 @@ type APIGatewayWithDetails struct { Name string `json:"name" db:"name"` DisplayName string `json:"displayName" db:"display_name"` Description string `json:"description" db:"description"` + Properties map[string]interface{} `json:"properties,omitempty" db:"properties"` Vhost string `json:"vhost" db:"vhost"` IsCritical bool `json:"isCritical" db:"is_critical"` FunctionalityType string `json:"functionalityType" db:"functionality_type"` diff --git a/platform-api/src/internal/repository/api.go b/platform-api/src/internal/repository/api.go index e53442395..2a30c85a1 100644 --- a/platform-api/src/internal/repository/api.go +++ b/platform-api/src/internal/repository/api.go @@ -1709,6 +1709,7 @@ func (r *APIRepo) GetAPIGatewaysWithDetails(apiUUID, orgUUID string) ([]*model.A g.name, g.display_name, g.description, + g.properties, g.vhost, g.is_critical, g.gateway_functionality_type as functionality_type, @@ -1736,6 +1737,7 @@ func (r *APIRepo) GetAPIGatewaysWithDetails(apiUUID, orgUUID string) ([]*model.A var gateways []*model.APIGatewayWithDetails for rows.Next() { gateway := &model.APIGatewayWithDetails{} + var propertiesJSON string var deployedAt sql.NullTime var deploymentId sql.NullString @@ -1745,6 +1747,7 @@ func (r *APIRepo) GetAPIGatewaysWithDetails(apiUUID, orgUUID string) ([]*model.A &gateway.Name, &gateway.DisplayName, &gateway.Description, + &propertiesJSON, &gateway.Vhost, &gateway.IsCritical, &gateway.FunctionalityType, @@ -1761,6 +1764,12 @@ func (r *APIRepo) GetAPIGatewaysWithDetails(apiUUID, orgUUID string) ([]*model.A return nil, err } + if propertiesJSON != "" && propertiesJSON != "{}" { + if err := json.Unmarshal([]byte(propertiesJSON), &gateway.Properties); err != nil { + return nil, fmt.Errorf("failed to unmarshal gateway properties: %w", err) + } + } + if deploymentId.Valid { gateway.DeploymentID = &deploymentId.String } From d10b3424b1829d58bd170e61a884f3e76207e435 Mon Sep 17 00:00:00 2001 From: VirajSalaka Date: Wed, 4 Feb 2026 17:29:50 +0530 Subject: [PATCH 3/3] Add unit tests --- .../service/gateway_properties_test.go | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 platform-api/src/internal/service/gateway_properties_test.go diff --git a/platform-api/src/internal/service/gateway_properties_test.go b/platform-api/src/internal/service/gateway_properties_test.go new file mode 100644 index 000000000..669470382 --- /dev/null +++ b/platform-api/src/internal/service/gateway_properties_test.go @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package service + +import ( + "reflect" + "testing" + + "platform-api/src/internal/constants" + "platform-api/src/internal/model" + "platform-api/src/internal/repository" +) + +type mockGatewayRepository struct { + repository.GatewayRepository + + getByNameResult *model.Gateway + getByNameError error + getByUUIDResult *model.Gateway + getByUUIDError error + createError error + createTokenError error + updateError error + + createdGateway *model.Gateway + createdToken *model.GatewayToken + updatedGateway *model.Gateway +} + +func (m *mockGatewayRepository) GetByNameAndOrgID(name, orgID string) (*model.Gateway, error) { + return m.getByNameResult, m.getByNameError +} + +func (m *mockGatewayRepository) Create(gateway *model.Gateway) error { + m.createdGateway = gateway + return m.createError +} + +func (m *mockGatewayRepository) CreateToken(token *model.GatewayToken) error { + m.createdToken = token + return m.createTokenError +} + +func (m *mockGatewayRepository) GetByUUID(gatewayId string) (*model.Gateway, error) { + return m.getByUUIDResult, m.getByUUIDError +} + +func (m *mockGatewayRepository) UpdateGateway(gateway *model.Gateway) error { + m.updatedGateway = gateway + return m.updateError +} + +type mockOrganizationRepository struct { + repository.OrganizationRepository + + org *model.Organization + err error +} + +func (m *mockOrganizationRepository) GetOrganizationByUUID(orgId string) (*model.Organization, error) { + return m.org, m.err +} + +func TestRegisterGatewayProperties(t *testing.T) { + orgID := "123e4567-e89b-12d3-a456-426614174000" + properties := map[string]interface{}{ + "region": "us-west", + "tier": "premium", + } + + mockGatewayRepo := &mockGatewayRepository{} + mockOrgRepo := &mockOrganizationRepository{ + org: &model.Organization{ID: orgID}, + } + + service := &GatewayService{ + gatewayRepo: mockGatewayRepo, + orgRepo: mockOrgRepo, + } + + response, err := service.RegisterGateway( + orgID, + "prod-gateway-01", + "Production Gateway", + "Gateway for prod traffic", + "api.example.com", + true, + constants.GatewayFunctionalityTypeRegular, + properties, + ) + if err != nil { + t.Fatalf("RegisterGateway() error = %v", err) + } + + if response == nil { + t.Fatalf("RegisterGateway() returned nil response") + } + + if !reflect.DeepEqual(response.Properties, properties) { + t.Errorf("RegisterGateway() response properties = %v, want %v", response.Properties, properties) + } + + if mockGatewayRepo.createdGateway == nil { + t.Fatalf("Create() was not called") + } + + if !reflect.DeepEqual(mockGatewayRepo.createdGateway.Properties, properties) { + t.Errorf("Create() gateway properties = %v, want %v", mockGatewayRepo.createdGateway.Properties, properties) + } + + if mockGatewayRepo.createdToken == nil { + t.Fatalf("CreateToken() was not called") + } +} + +func TestUpdateGatewayProperties(t *testing.T) { + orgID := "org-1" + gatewayID := "gateway-1" + + baseGateway := &model.Gateway{ + ID: gatewayID, + OrganizationID: orgID, + DisplayName: "Old Gateway", + Description: "Old description", + Properties: map[string]interface{}{ + "region": "us-east", + "tier": "free", + }, + } + + t.Run("keeps properties when nil", func(t *testing.T) { + mockGatewayRepo := &mockGatewayRepository{ + getByUUIDResult: baseGateway, + } + + service := &GatewayService{ + gatewayRepo: mockGatewayRepo, + } + + newDescription := "New description" + response, err := service.UpdateGateway(gatewayID, orgID, &newDescription, nil, nil, nil) + if err != nil { + t.Fatalf("UpdateGateway() error = %v", err) + } + + if !reflect.DeepEqual(response.Properties, baseGateway.Properties) { + t.Errorf("UpdateGateway() response properties = %v, want %v", response.Properties, baseGateway.Properties) + } + + if mockGatewayRepo.updatedGateway == nil { + t.Fatalf("UpdateGateway() did not call UpdateGateway repository method") + } + + if !reflect.DeepEqual(mockGatewayRepo.updatedGateway.Properties, baseGateway.Properties) { + t.Errorf("UpdateGateway() stored properties = %v, want %v", mockGatewayRepo.updatedGateway.Properties, baseGateway.Properties) + } + }) + + t.Run("updates properties when provided", func(t *testing.T) { + freshGateway := *baseGateway + freshGateway.Properties = map[string]interface{}{ + "region": "us-east", + "tier": "free", + } + + mockGatewayRepo := &mockGatewayRepository{ + getByUUIDResult: &freshGateway, + } + + service := &GatewayService{ + gatewayRepo: mockGatewayRepo, + } + + newProperties := map[string]interface{}{ + "region": "us-west", + "tier": "premium", + } + + response, err := service.UpdateGateway(gatewayID, orgID, nil, nil, nil, &newProperties) + if err != nil { + t.Fatalf("UpdateGateway() error = %v", err) + } + + if !reflect.DeepEqual(response.Properties, newProperties) { + t.Errorf("UpdateGateway() response properties = %v, want %v", response.Properties, newProperties) + } + + if mockGatewayRepo.updatedGateway == nil { + t.Fatalf("UpdateGateway() did not call UpdateGateway repository method") + } + + if !reflect.DeepEqual(mockGatewayRepo.updatedGateway.Properties, newProperties) { + t.Errorf("UpdateGateway() stored properties = %v, want %v", mockGatewayRepo.updatedGateway.Properties, newProperties) + } + }) +}