diff --git a/internal/provider/function_resource.go b/internal/provider/function_resource.go index 00dd481..2a2b59a 100644 --- a/internal/provider/function_resource.go +++ b/internal/provider/function_resource.go @@ -6,9 +6,6 @@ import ( "net/http" "regexp" - "github.com/segmentio/terraform-provider-segment/internal/provider/docs" - "github.com/segmentio/terraform-provider-segment/internal/provider/models" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -16,8 +13,12 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" + tftypes "github.com/hashicorp/terraform-plugin-framework/types" "github.com/segmentio/public-api-sdk-go/api" + + "github.com/segmentio/terraform-provider-segment/internal/provider/docs" + "github.com/segmentio/terraform-provider-segment/internal/provider/models" ) var ( @@ -35,6 +36,10 @@ type functionResource struct { authContext context.Context } +func hasValue(v tftypes.String) bool { + return !(v.IsNull() || v.IsUnknown()) +} + func (r *functionResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_function" } @@ -163,7 +168,7 @@ func (r *functionResource) Create(ctx context.Context, req resource.CreateReques state.Fill(function) // Destination functions append workspace name to display name causing inconsistency - if state.ResourceType.ValueString() == "DESTINATION" || state.ResourceType.ValueString() == "INSERT_DESTINATION" { + if state.ResourceType.ValueString() == "DESTINATION" || state.ResourceType.ValueString() == "INSERT_DESTINATION" || state.ResourceType.ValueString() == "INSERT_SOURCE" { state.DisplayName = plan.DisplayName } @@ -210,7 +215,7 @@ func (r *functionResource) Read(ctx context.Context, req resource.ReadRequest, r state.Fill(function) // Destination functions append workspace name to display name causing inconsistency - if state.ResourceType.ValueString() == "DESTINATION" || state.ResourceType.ValueString() == "INSERT_DESTINATION" { + if state.ResourceType.ValueString() == "DESTINATION" || state.ResourceType.ValueString() == "INSERT_DESTINATION" || state.ResourceType.ValueString() == "INSERT_SOURCE" && hasValue(previousState.DisplayName) { state.DisplayName = previousState.DisplayName } @@ -266,7 +271,7 @@ func (r *functionResource) Update(ctx context.Context, req resource.UpdateReques state.Fill(function) // Destination functions append workspace name to display name causing inconsistency - if state.ResourceType.ValueString() == "DESTINATION" || state.ResourceType.ValueString() == "INSERT_DESTINATION" { + if state.ResourceType.ValueString() == "DESTINATION" || state.ResourceType.ValueString() == "INSERT_DESTINATION" || state.ResourceType.ValueString() == "INSERT_SOURCE" { state.DisplayName = plan.DisplayName } diff --git a/internal/provider/function_resource_test.go b/internal/provider/function_resource_test.go index 0542b18..2816772 100644 --- a/internal/provider/function_resource_test.go +++ b/internal/provider/function_resource_test.go @@ -269,3 +269,219 @@ func TestAccFunctionResource(t *testing.T) { }, }) } + +func TestAccFunctionResource_InsertSource(t *testing.T) { + t.Parallel() + + // keeps track of whether we’ve already updated the Function + updated := 0 + + // ────────────────────────── Fake Segment API ────────────────────────── + fakeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("content-type", "application/json") + + switch { + // ── CREATE ───────────────────────────────────────────────────────── + case req.URL.Path == "/functions" && req.Method == http.MethodPost: + _, _ = w.Write([]byte(` + { + "data": { + "function": { + "id": "my-function-id", + "workspaceId": "my-workspace-id", + "displayName": "My test function", + "description": "My function description", + "logoUrl": "https://segment.com/cool-logo.png", + "code": "// My test code!", + "createdAt": "2023-10-11T18:52:07.087Z", + "createdBy": "my-user-id", + "previewWebhookUrl": "", + "settings": [{ + "name": "mySettingName", + "label": "My setting label", + "description": "My setting description", + "type": "STRING", + "required": false, + "sensitive": false + }], + "buildpack": "boreal", + "catalogId": "my-catalog-id", + "batchMaxCount": 0, + "resourceType": "INSERT_SOURCE" + } + } + }`)) + // ── UPDATE ───────────────────────────────────────────────────────── + case req.URL.Path == "/functions/my-function-id" && req.Method == http.MethodPatch: + updated++ + _, _ = w.Write([]byte(` + { + "data": { + "function": { + "id": "my-function-id", + "workspaceId": "my-workspace-id", + "displayName": "My new test function", + "description": "My new function description", + "logoUrl": "https://segment.com/cool-other-logo.png", + "code": "// My new test code!", + "createdAt": "2023-10-11T18:52:07.087Z", + "createdBy": "my-user-id", + "previewWebhookUrl": "", + "settings": [{ + "name": "myNewSettingName", + "label": "My new setting label", + "description": "My new setting description", + "type": "STRING", + "required": true, + "sensitive": true + }], + "buildpack": "boreal", + "catalogId": "my-catalog-id", + "batchMaxCount": 0, + "resourceType": "INSERT_SOURCE" + } + } + }`)) + + case req.URL.Path == "/functions/my-function-id" && req.Method == http.MethodGet: + if updated == 0 { + _, _ = w.Write([]byte(` + { + "data": { + "function": { + "id": "my-function-id", + "workspaceId": "my-workspace-id", + "displayName": "My test function", + "description": "My function description", + "logoUrl": "https://segment.com/cool-logo.png", + "code": "// My test code!", + "createdAt": "2023-10-11T18:52:07.087Z", + "createdBy": "my-user-id", + "previewWebhookUrl": "", + "settings": [{ + "name": "mySettingName", + "label": "My setting label", + "description": "My setting description", + "type": "STRING", + "required": false, + "sensitive": false + }], + "buildpack": "boreal", + "catalogId": "my-catalog-id", + "batchMaxCount": 0, + "resourceType": "INSERT_SOURCE" + } + } + }`)) + } else { + _, _ = w.Write([]byte(` + { + "data": { + "function": { + "id": "my-function-id", + "workspaceId": "my-workspace-id", + "displayName": "My new test function", + "description": "My new function description", + "logoUrl": "https://segment.com/cool-other-logo.png", + "code": "// My new test code!", + "createdAt": "2023-10-11T18:52:07.087Z", + "createdBy": "my-user-id", + "previewWebhookUrl": "", + "settings": [{ + "name": "myNewSettingName", + "label": "My new setting label", + "description": "My new setting description", + "type": "STRING", + "required": true, + "sensitive": true + }], + "buildpack": "boreal", + "catalogId": "my-catalog-id", + "batchMaxCount": 0, + "resourceType": "INSERT_SOURCE" + } + } + }`)) + } + } + })) + defer fakeServer.Close() + + providerConfig := ` + provider "segment" { + url = "` + fakeServer.URL + `" + token = "abc123" + } + ` + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: providerConfig + ` + resource "segment_function" "test" { + code = "// My test code!" + display_name = "My test function" + logo_url = "https://segment.com/cool-logo.png" + resource_type = "INSERT_SOURCE" + description = "My function description" + settings = [{ + name = "mySettingName" + label = "My setting label" + type = "STRING" + description = "My setting description" + required = false + sensitive = false + }] + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("segment_function.test", "id", "my-function-id"), + resource.TestCheckResourceAttr("segment_function.test", "resource_type", "INSERT_SOURCE"), + resource.TestCheckResourceAttr("segment_function.test", "display_name", "My test function"), + resource.TestCheckResourceAttr("segment_function.test", "logo_url", "https://segment.com/cool-logo.png"), + resource.TestCheckResourceAttr("segment_function.test", "description", "My function description"), + resource.TestCheckResourceAttr("segment_function.test", "settings.#", "1"), + resource.TestCheckResourceAttr("segment_function.test", "settings.0.name", "mySettingName"), + resource.TestCheckResourceAttr("segment_function.test", "settings.0.required", "false"), + resource.TestCheckResourceAttr("segment_function.test", "settings.0.sensitive", "false"), + ), + }, + + { + ResourceName: "segment_function.test", + ImportState: true, + ImportStateVerify: true, + }, + + { + Config: providerConfig + ` + resource "segment_function" "test" { + code = "// My new test code!" + display_name = "My new test function" + logo_url = "https://segment.com/cool-other-logo.png" + resource_type = "INSERT_SOURCE" + description = "My new function description" + settings = [{ + name = "myNewSettingName" + label = "My new setting label" + type = "STRING" + description = "My new setting description" + required = true + sensitive = true + }] + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("segment_function.test", "resource_type", "INSERT_SOURCE"), + resource.TestCheckResourceAttr("segment_function.test", "display_name", "My new test function"), + resource.TestCheckResourceAttr("segment_function.test", "logo_url", "https://segment.com/cool-other-logo.png"), + resource.TestCheckResourceAttr("segment_function.test", "description", "My new function description"), + resource.TestCheckResourceAttr("segment_function.test", "settings.0.name", "myNewSettingName"), + resource.TestCheckResourceAttr("segment_function.test", "settings.0.required", "true"), + resource.TestCheckResourceAttr("segment_function.test", "settings.0.sensitive", "true"), + ), + }, + }, + }) +}