From 38b369b12e2a7856075fff49e537a6d310145116 Mon Sep 17 00:00:00 2001 From: Or Bauberg Date: Sun, 23 Feb 2025 16:42:00 +0000 Subject: [PATCH 1/3] initial commit --- internal/provider/provider.go | 2 + internal/services/mirroreddatabase/base.go | 45 +++ .../services/mirroreddatabase/base_test.go | 14 + .../data_mirrored_database.go | 80 +++++ .../data_mirrored_database_test.go | 279 ++++++++++++++++++ internal/services/mirroreddatabase/models.go | 53 ++++ ...chema_data_mirrored_database_definition.go | 39 +++ .../testhelp/fakes/fabric_mirroreddatabase.go | 225 ++++++++++++++ internal/testhelp/fakes/fake_handlers.go | 34 ++- internal/testhelp/fakes/fake_server.go | 1 + internal/testhelp/fakes/fake_typedhandler.go | 34 ++- 11 files changed, 803 insertions(+), 3 deletions(-) create mode 100644 internal/services/mirroreddatabase/base.go create mode 100644 internal/services/mirroreddatabase/base_test.go create mode 100644 internal/services/mirroreddatabase/data_mirrored_database.go create mode 100644 internal/services/mirroreddatabase/data_mirrored_database_test.go create mode 100644 internal/services/mirroreddatabase/models.go create mode 100644 internal/services/mirroreddatabase/schema_data_mirrored_database_definition.go create mode 100644 internal/testhelp/fakes/fabric_mirroreddatabase.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 01b5f144..e6cad12a 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -50,6 +50,7 @@ import ( "github.com/microsoft/terraform-provider-fabric/internal/services/kqldatabase" "github.com/microsoft/terraform-provider-fabric/internal/services/kqlqueryset" "github.com/microsoft/terraform-provider-fabric/internal/services/lakehouse" + "github.com/microsoft/terraform-provider-fabric/internal/services/mirroreddatabase" "github.com/microsoft/terraform-provider-fabric/internal/services/mirroredwarehouse" "github.com/microsoft/terraform-provider-fabric/internal/services/mlexperiment" "github.com/microsoft/terraform-provider-fabric/internal/services/mlmodel" @@ -442,6 +443,7 @@ func (p *FabricProvider) DataSources(ctx context.Context) []func() datasource.Da func() datasource.DataSource { return lakehouse.NewDataSourceLakehouses(ctx) }, lakehouse.NewDataSourceLakehouseTable, lakehouse.NewDataSourceLakehouseTables, + mirroreddatabase.NewDataSourceMirroredDatabase, mirroredwarehouse.NewDataSourceMirroredWarehouses, mlexperiment.NewDataSourceMLExperiment, mlexperiment.NewDataSourceMLExperiments, diff --git a/internal/services/mirroreddatabase/base.go b/internal/services/mirroreddatabase/base.go new file mode 100644 index 00000000..fdebc90f --- /dev/null +++ b/internal/services/mirroreddatabase/base.go @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package mirroreddatabase + +import ( + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + + "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/fabricitem" +) + +const ( + ItemName = "Mirrored Database" + ItemTFName = "mirrored_database" + ItemsName = "Mirrored Databases" + ItemsTFName = "mirrored_databases" + ItemType = fabcore.ItemTypeMirroredDatabase + ItemDocsSPNSupport = common.DocsSPNSupported + ItemDocsURL = "https://learn.microsoft.com/en-us/fabric/database/mirrored-database/overview" + ItemDefinitionEmpty = `{ + "properties": { + "source": { + "type": "", + "typeProperties": null + }, + "target": { + "type": "", + "typeProperties": { + "format": "", + "defaultSchema": "" + } + } + } +}` + ItemDefinitionPathDocsURL = "https://learn.microsoft.com/en-us/rest/api/fabric/articles/item-management/definitions/mirrored-database-definition" +) + +var itemDefinitionFormats = []fabricitem.DefinitionFormat{ //nolint:gochecknoglobals + { + Type: fabricitem.DefinitionFormatDefault, + API: "", + Paths: []string{"mirroring.json"}, + }, +} diff --git a/internal/services/mirroreddatabase/base_test.go b/internal/services/mirroreddatabase/base_test.go new file mode 100644 index 00000000..f407ae41 --- /dev/null +++ b/internal/services/mirroreddatabase/base_test.go @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package mirroreddatabase_test + +import ( + "github.com/microsoft/terraform-provider-fabric/internal/services/mirroreddatabase" +) + +const ( + itemTFName = mirroreddatabase.ItemTFName + itemsTFName = mirroreddatabase.ItemsTFName + itemType = mirroreddatabase.ItemType +) diff --git a/internal/services/mirroreddatabase/data_mirrored_database.go b/internal/services/mirroreddatabase/data_mirrored_database.go new file mode 100644 index 00000000..051ed271 --- /dev/null +++ b/internal/services/mirroreddatabase/data_mirrored_database.go @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package mirroreddatabase + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/microsoft/fabric-sdk-go/fabric" + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + "github.com/microsoft/fabric-sdk-go/fabric/mirroreddatabase" + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/microsoft/terraform-provider-fabric/internal/pkg/fabricitem" +) + +func NewDataSourceMirroredDatabase() datasource.DataSource { + propertiesSetter := func(ctx context.Context, from *mirroreddatabase.Properties, to *fabricitem.DataSourceFabricItemDefinitionPropertiesModel[mirroredDatabasePropertiesModel, mirroreddatabase.Properties]) diag.Diagnostics { + properties := supertypes.NewSingleNestedObjectValueOfNull[mirroredDatabasePropertiesModel](ctx) + if from != nil { + propertiesModel := &mirroredDatabasePropertiesModel{} + propertiesModel.set(ctx, *from) + if diags := properties.Set(ctx, propertiesModel); diags.HasError() { + return diags + } + } + to.Properties = properties + return nil + } + + // itemGetter retrieves a single mirrored database item. + itemGetter := func(ctx context.Context, fabricClient fabric.Client, model fabricitem.DataSourceFabricItemDefinitionPropertiesModel[mirroredDatabasePropertiesModel, mirroreddatabase.Properties], fabricItem *fabricitem.FabricItemProperties[mirroreddatabase.Properties]) error { + client := mirroreddatabase.NewClientFactoryWithClient(fabricClient).NewItemsClient() + respGet, err := client.GetMirroredDatabase(ctx, model.WorkspaceID.ValueString(), model.ID.ValueString(), nil) + if err != nil { + return err + } + fabricItem.Set(respGet.MirroredDatabase) + return nil + } + + // itemListGetter searches for a mirrored database by its display name. + itemListGetter := func(ctx context.Context, fabricClient fabric.Client, model fabricitem.DataSourceFabricItemDefinitionPropertiesModel[mirroredDatabasePropertiesModel, mirroreddatabase.Properties], errNotFound fabcore.ResponseError, fabricItem *fabricitem.FabricItemProperties[mirroreddatabase.Properties]) error { + client := mirroreddatabase.NewClientFactoryWithClient(fabricClient).NewItemsClient() + pager := client.NewListMirroredDatabasesPager(model.WorkspaceID.ValueString(), nil) + for pager.More() { + page, err := pager.NextPage(ctx) + if err != nil { + return err + } + for _, entity := range page.Value { + if *entity.DisplayName == model.DisplayName.ValueString() { + fabricItem.Set(entity) + return nil + } + } + } + return &errNotFound + } + + config := fabricitem.DataSourceFabricItemDefinitionProperties[mirroredDatabasePropertiesModel, mirroreddatabase.Properties]{ + DataSourceFabricItemDefinition: fabricitem.DataSourceFabricItemDefinition{ + Type: "MirroredDatabase", + Name: "Mirrored Database", + TFName: "mirrored_database", + MarkdownDescription: "Get a Fabric Mirrored Database.\n\n" + + "Use this data source to fetch a Mirrored Database.\n\n", + IsDisplayNameUnique: true, + DefinitionFormats: itemDefinitionFormats, + }, + PropertiesAttributes: getDataSourceMirroredDatabasePropertiesAttributes(), // define this function to return schema attributes + PropertiesSetter: propertiesSetter, + ItemGetter: itemGetter, + ItemListGetter: itemListGetter, + } + + return fabricitem.NewDataSourceFabricItemDefinitionProperties(config) +} diff --git a/internal/services/mirroreddatabase/data_mirrored_database_test.go b/internal/services/mirroreddatabase/data_mirrored_database_test.go new file mode 100644 index 00000000..61f5b050 --- /dev/null +++ b/internal/services/mirroreddatabase/data_mirrored_database_test.go @@ -0,0 +1,279 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package mirroreddatabase_test + +import ( + "regexp" + "testing" + + at "github.com/dcarbone/terraform-plugin-framework-utils/v3/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp/fakes" +) + +var ( + testDataSourceMirroredDatabaseFQN = testhelp.DataSourceFQN("fabric", itemTFName, "test") + testDataSourceMirroredDatabaseHeader = at.DataSourceHeader(testhelp.TypeName("fabric", itemTFName), "test") +) + +func TestUnit_MirroredDatabaseDataSource(t *testing.T) { + workspaceID := testhelp.RandomUUID() + entity := fakes.NewRandomItemWithWorkspace(itemTFName, workspaceID) + + fakes.FakeServer.Upsert(fakes.NewRandomItemWithWorkspace(itemTFName, workspaceID)) + fakes.FakeServer.Upsert(entity) + fakes.FakeServer.Upsert(fakes.NewRandomItemWithWorkspace(itemTFName, workspaceID)) + + resource.ParallelTest(t, testhelp.NewTestUnitCase(t, nil, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ + // error - no attributes provided + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{}, + ), + ExpectError: regexp.MustCompile(`The argument "workspace_id" is required, but no definition was found`), + }, + // error - invalid workspace_id UUID + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "workspace_id": "invalid uuid", + }, + ), + ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + }, + // error - unexpected attribute + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "workspace_id": workspaceID, + "unexpected_attr": "test", + }, + ), + ExpectError: regexp.MustCompile(`An argument named "unexpected_attr" is not expected here`), + }, + // error - conflicting attributes provided together + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "workspace_id": workspaceID, + "id": *entity.ID, + "display_name": *entity.DisplayName, + }, + ), + ExpectError: regexp.MustCompile(`These attributes cannot be configured together: \[id,display_name\]`), + }, + // error - missing one of required attributes (neither id nor display_name provided) + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "workspace_id": workspaceID, + }, + ), + ExpectError: regexp.MustCompile(`Exactly one of these attributes must be configured: \[id,display_name\]`), + }, + // error - id provided without workspace_id + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "id": *entity.ID, + }, + ), + ExpectError: regexp.MustCompile(`The argument "workspace_id" is required, but no definition was found`), + }, + // read by id - success + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "workspace_id": workspaceID, + "id": *entity.ID, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "workspace_id", entity.WorkspaceID), + resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "id", entity.ID), + resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "display_name", entity.DisplayName), + resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "description", entity.Description), + ), + }, + // read by id - not found + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "workspace_id": workspaceID, + "id": testhelp.RandomUUID(), + }, + ), + ExpectError: regexp.MustCompile(common.ErrorReadHeader), + }, + // read by display name - success + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "workspace_id": workspaceID, + "display_name": *entity.DisplayName, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "workspace_id", entity.WorkspaceID), + resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "id", entity.ID), + resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "display_name", entity.DisplayName), + resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "description", entity.Description), + ), + }, + // read by display name - not found + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "workspace_id": workspaceID, + "display_name": testhelp.RandomName(), + }, + ), + ExpectError: regexp.MustCompile(common.ErrorReadHeader), + }, + // read by id with definition - missing required format configuration + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "workspace_id": workspaceID, + "id": *entity.ID, + "output_definition": true, + }, + ), + ExpectError: regexp.MustCompile("Invalid configuration for attribute format"), + }, + // read by id with definition - success + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "workspace_id": workspaceID, + "id": *entity.ID, + "output_definition": true, + "format": "Default", + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "workspace_id", entity.WorkspaceID), + resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "id", entity.ID), + resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "display_name", entity.DisplayName), + resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "description", entity.Description), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "definition.RealTimeDefinition.json.content"), + ), + }, + })) +} + +func TestAcc_MirroredDatabaseDataSource(t *testing.T) { + workspace := testhelp.WellKnown()["WorkspaceDS"].(map[string]any) + workspaceID := workspace["id"].(string) + + entity := testhelp.WellKnown()["MirroredDatabase"].(map[string]any) + entityID := entity["id"].(string) + entityDisplayName := entity["displayName"].(string) + entityDescription := entity["description"].(string) + + resource.ParallelTest(t, testhelp.NewTestAccCase(t, nil, nil, []resource.TestStep{ + // read by id - success + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "workspace_id": workspaceID, + "id": entityID, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "workspace_id", workspaceID), + resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "id", entityID), + resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "display_name", entityDisplayName), + resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "description", entityDescription), + ), + }, + // read by id - not found + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "workspace_id": workspaceID, + "id": testhelp.RandomUUID(), + }, + ), + ExpectError: regexp.MustCompile(common.ErrorReadHeader), + }, + // read by display name - success + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "workspace_id": workspaceID, + "display_name": entityDisplayName, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "workspace_id", workspaceID), + resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "id", entityID), + resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "display_name", entityDisplayName), + resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "description", entityDescription), + resource.TestCheckNoResourceAttr(testDataSourceMirroredDatabaseFQN, "definition"), + ), + }, + // read by display name - not found + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "workspace_id": workspaceID, + "display_name": testhelp.RandomName(), + }, + ), + ExpectError: regexp.MustCompile(common.ErrorReadHeader), + }, + // read by id with definition - missing format error + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "workspace_id": workspaceID, + "id": entityID, + "output_definition": true, + }, + ), + ExpectError: regexp.MustCompile("Invalid configuration for attribute format"), + }, + // read by id with definition - success + { + Config: at.CompileConfig( + testDataSourceMirroredDatabaseHeader, + map[string]any{ + "workspace_id": workspaceID, + "id": entityID, + "output_definition": true, + "format": "Default", + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "workspace_id", workspaceID), + resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "id", entityID), + resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "display_name", entityDisplayName), + resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "description", entityDescription), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "definition.RealTimeDefinition.json.content"), + ), + }, + })) +} diff --git a/internal/services/mirroreddatabase/models.go b/internal/services/mirroreddatabase/models.go new file mode 100644 index 00000000..3183e00c --- /dev/null +++ b/internal/services/mirroreddatabase/models.go @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package mirroreddatabase + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + mirroreddatabase "github.com/microsoft/fabric-sdk-go/fabric/mirroreddatabase" + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" +) + +type mirroredDatabasePropertiesModel struct { + DefaultSchema types.String `tfsdk:"default_schema"` + OneLakeTablesPath types.String `tfsdk:"onelake_tables_path"` + SQLEndpointProperties supertypes.SingleNestedObjectValueOf[SQLEndpointPropertiesModel] `tfsdk:"sql_endpoint_properties"` +} + +type SQLEndpointPropertiesModel struct { + ProvisioningStatus types.String `tfsdk:"provisioning_status"` // PossibleSQLEndpointProvisioningStatusValues + ConnectionString types.String `tfsdk:"connection_string"` + ID customtypes.UUID `tfsdk:"id"` +} + +func (to *mirroredDatabasePropertiesModel) set(ctx context.Context, from mirroreddatabase.Properties) diag.Diagnostics { + to.DefaultSchema = types.StringPointerValue(from.DefaultSchema) + to.OneLakeTablesPath = types.StringPointerValue(from.OneLakeTablesPath) + + sqlEndpointProperties := supertypes.NewSingleNestedObjectValueOfNull[SQLEndpointPropertiesModel](ctx) + + if from.SQLEndpointProperties != nil { + sqlEndpointPropertiesModel := &SQLEndpointPropertiesModel{} + sqlEndpointPropertiesModel.set(from.SQLEndpointProperties) + + if diags := sqlEndpointProperties.Set(ctx, sqlEndpointPropertiesModel); diags.HasError() { + return diags + } + } + + to.SQLEndpointProperties = sqlEndpointProperties + + return nil +} + +func (to *SQLEndpointPropertiesModel) set(from *mirroreddatabase.SQLEndpointProperties) { + to.ProvisioningStatus = types.StringPointerValue((*string)(from.ProvisioningStatus)) + to.ConnectionString = types.StringPointerValue(from.ConnectionString) + to.ID = customtypes.NewUUIDPointerValue(from.ID) +} diff --git a/internal/services/mirroreddatabase/schema_data_mirrored_database_definition.go b/internal/services/mirroreddatabase/schema_data_mirrored_database_definition.go new file mode 100644 index 00000000..35d7b90b --- /dev/null +++ b/internal/services/mirroreddatabase/schema_data_mirrored_database_definition.go @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package mirroreddatabase + +import ( + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" +) + +func getDataSourceMirroredDatabasePropertiesAttributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "default_schema": schema.StringAttribute{ + MarkdownDescription: "Default schema of the mirrored database, returned only for mirrored databases that enable default schema in definition.", + Computed: true, + }, + "onelake_tables_path": schema.StringAttribute{ + MarkdownDescription: "OneLake path to the mirrored database tables directory.", + Computed: true, + }, + "sql_endpoint_properties": schema.SingleNestedAttribute{ + MarkdownDescription: "An object containing the properties of the SQL endpoint.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "provisioning_status": schema.StringAttribute{ + MarkdownDescription: "The SQL endpoint provisioning status.", + Computed: true, + }, + "connection_string": schema.StringAttribute{ + MarkdownDescription: "The SQL endpoint connection string.", + Computed: true, + }, + "id": schema.StringAttribute{ + MarkdownDescription: "The SQL endpoint ID.", + Computed: true, + }, + }, + }, + } +} diff --git a/internal/testhelp/fakes/fabric_mirroreddatabase.go b/internal/testhelp/fakes/fabric_mirroreddatabase.go new file mode 100644 index 00000000..48d7f9c3 --- /dev/null +++ b/internal/testhelp/fakes/fabric_mirroreddatabase.go @@ -0,0 +1,225 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package fakes + +import ( + "net/http" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + fabfake "github.com/microsoft/fabric-sdk-go/fabric/fake" + "github.com/microsoft/fabric-sdk-go/fabric/mirroreddatabase" + + "github.com/microsoft/terraform-provider-fabric/internal/testhelp" +) + +// operationsMirroredDatabase implements the operations for MirroredDatabase +// and satisfies the concreteEntityOperations and concreteDefinitionOperations interfaces. +type operationsMirroredDatabase struct{} + +// ConvertItemToEntity implements itemConverter. It converts a generic fabcore.Item into a +// mirroreddatabase.MirroredDatabase. +func (o *operationsMirroredDatabase) ConvertItemToEntity(item fabcore.Item) mirroreddatabase.MirroredDatabase { + return mirroreddatabase.MirroredDatabase{ + ID: item.ID, + DisplayName: item.DisplayName, + Description: item.Description, + WorkspaceID: item.WorkspaceID, + // Set the type to mirrored database. + Type: to.Ptr(mirroreddatabase.ItemTypeMirroredDatabase), + Properties: NewRandomMirroredDatabase().Properties, + } +} + +// CreateDefinition implements concreteDefinitionOperations. +// It simply returns the Definition provided in the Create request. +func (o *operationsMirroredDatabase) CreateDefinition(data mirroreddatabase.CreateMirroredDatabaseRequest) *mirroreddatabase.Definition { + return data.Definition +} + +// TransformDefinition implements concreteDefinitionOperations. +// It wraps the public definition into the GetDefinition response. +func (o *operationsMirroredDatabase) TransformDefinition(entity *mirroreddatabase.Definition) mirroreddatabase.ItemsClientGetMirroredDatabaseDefinitionResponse { + return mirroreddatabase.ItemsClientGetMirroredDatabaseDefinitionResponse{ + DefinitionResponse: mirroreddatabase.DefinitionResponse{ + Definition: entity, + }, + } +} + +// UpdateDefinition implements concreteDefinitionOperations. +// It returns the updated definition as provided in the Update request. +func (o *operationsMirroredDatabase) UpdateDefinition(_ *mirroreddatabase.Definition, data mirroreddatabase.UpdateMirroredDatabaseDefinitionRequest) *mirroreddatabase.Definition { + return data.Definition +} + +// CreateWithParentID implements concreteOperations. +// It creates a mirrored database entity with the given parent workspace ID. +func (o *operationsMirroredDatabase) CreateWithParentID(parentID string, data mirroreddatabase.CreateMirroredDatabaseRequest) mirroreddatabase.MirroredDatabase { + entity := NewRandomMirroredDatabaseWithWorkspace(parentID) + entity.DisplayName = data.DisplayName + entity.Description = data.Description + return entity +} + +// Filter implements concreteOperations, returning only entities whose WorkspaceID matches parentID. +func (o *operationsMirroredDatabase) Filter(entities []mirroreddatabase.MirroredDatabase, parentID string) []mirroreddatabase.MirroredDatabase { + var ret []mirroreddatabase.MirroredDatabase + for _, entity := range entities { + if *entity.WorkspaceID == parentID { + ret = append(ret, entity) + } + } + return ret +} + +// GetID implements concreteOperations. +// It returns a generated unique ID composed of the workspace ID and the entity ID. +func (o *operationsMirroredDatabase) GetID(entity mirroreddatabase.MirroredDatabase) string { + return generateID(*entity.WorkspaceID, *entity.ID) +} + +// TransformCreate implements concreteOperations. It wraps the created entity in the Create response. +func (o *operationsMirroredDatabase) TransformCreate(entity mirroreddatabase.MirroredDatabase) mirroreddatabase.ItemsClientCreateMirroredDatabaseResponse { + return mirroreddatabase.ItemsClientCreateMirroredDatabaseResponse{ + MirroredDatabase: entity, + } +} + +// TransformGet implements concreteOperations. +// It returns the get response for the given mirrored database entity. +func (o *operationsMirroredDatabase) TransformGet(entity mirroreddatabase.MirroredDatabase) mirroreddatabase.ItemsClientGetMirroredDatabaseResponse { + return mirroreddatabase.ItemsClientGetMirroredDatabaseResponse{ + MirroredDatabase: entity, + } +} + +// TransformList implements concreteOperations. +// It wraps a list of entities into the List response. +func (o *operationsMirroredDatabase) TransformList(entities []mirroreddatabase.MirroredDatabase) mirroreddatabase.ItemsClientListMirroredDatabasesResponse { + return mirroreddatabase.ItemsClientListMirroredDatabasesResponse{ + MirroredDatabases: mirroreddatabase.MirroredDatabases{ + Value: entities, + }, + } +} + +// TransformUpdate implements concreteOperations. +// It wraps the updated entity into the Update response. +func (o *operationsMirroredDatabase) TransformUpdate(entity mirroreddatabase.MirroredDatabase) mirroreddatabase.ItemsClientUpdateMirroredDatabaseResponse { + return mirroreddatabase.ItemsClientUpdateMirroredDatabaseResponse{ + MirroredDatabase: entity, + } +} + +// Update implements concreteOperations. +// It updates the base entity with the new data. +func (o *operationsMirroredDatabase) Update(base mirroreddatabase.MirroredDatabase, data mirroreddatabase.UpdateMirroredDatabaseRequest) mirroreddatabase.MirroredDatabase { + base.DisplayName = data.DisplayName + base.Description = data.Description + return base +} + +// Validate implements concreteOperations. +// It checks for duplicate DisplayName among existing entities. +func (o *operationsMirroredDatabase) Validate(newEntity mirroreddatabase.MirroredDatabase, existing []mirroreddatabase.MirroredDatabase) (int, error) { + for _, entity := range existing { + if *entity.DisplayName == *newEntity.DisplayName { + return http.StatusConflict, fabfake.SetResponseError( + http.StatusConflict, + fabcore.ErrItem.ItemDisplayNameAlreadyInUse.Error(), + fabcore.ErrItem.ItemDisplayNameAlreadyInUse.Error(), + ) + } + } + return http.StatusCreated, nil +} + +// Add this function below your operationsMirroredDatabase type and its methods. + +func configureMirroredDatabase(server *fakeServer) mirroreddatabase.MirroredDatabase { + // Define the entity operations interface for MirroredDatabase. + type concreteEntityOperations interface { + parentIDOperations[ + mirroreddatabase.MirroredDatabase, + mirroreddatabase.ItemsClientGetMirroredDatabaseResponse, + mirroreddatabase.ItemsClientUpdateMirroredDatabaseResponse, + mirroreddatabase.ItemsClientCreateMirroredDatabaseResponse, + mirroreddatabase.ItemsClientListMirroredDatabasesResponse, + mirroreddatabase.CreateMirroredDatabaseRequest, + mirroreddatabase.UpdateMirroredDatabaseRequest, + ] + } + + // Define the definition operations interface for MirroredDatabase. + type concreteDefinitionOperations interface { + definitionOperations[ + mirroreddatabase.Definition, + mirroreddatabase.CreateMirroredDatabaseRequest, + mirroreddatabase.UpdateMirroredDatabaseDefinitionRequest, + mirroreddatabase.ItemsClientGetMirroredDatabaseDefinitionResponse, + mirroreddatabase.ItemsClientUpdateMirroredDatabaseDefinitionResponse, + ] + } + + var entityOperations concreteEntityOperations = &operationsMirroredDatabase{} + var definitionOperations concreteDefinitionOperations = &operationsMirroredDatabase{} + var converter itemConverter[mirroreddatabase.MirroredDatabase] = &operationsMirroredDatabase{} + + handler := newTypedHandlerWithConverter(server, entityOperations, converter) + + // handleGetWithParentID(handler, entityOperations, &handler.ServerFactory.MirroredDatabase.ItemsServer.GetMirroredDatabase) + // handleUpdateWithParentID(handler, entityOperations, entityOperations, &handler.ServerFactory.MirroredDatabase.ItemsServer.UpdateMirroredDatabase) + // handleNonLROCreate(handler, entityOperations, entityOperations, entityOperations, &handler.ServerFactory.MirroredDatabase.ItemsServer.CreateMirroredDatabase) + // handleListPagerWithParentID(handler, entityOperations, entityOperations, &handler.ServerFactory.MirroredDatabase.ItemsServer.NewListMirroredDatabasesPager) + // handleDeleteWithParentID(handler, &handler.ServerFactory.MirroredDatabase.ItemsServer.DeleteMirroredDatabase) + + configureEntityWithParentID( + handler, + entityOperations, + &server.ServerFactory.MirroredDatabase.ItemsServer.GetMirroredDatabase, + &server.ServerFactory.MirroredDatabase.ItemsServer.UpdateMirroredDatabase, + &server.ServerFactory.MirroredDatabase.ItemsServer.CreateMirroredDatabase, + &server.ServerFactory.MirroredDatabase.ItemsServer.NewListMirroredDatabasesPager, + &server.ServerFactory.MirroredDatabase.ItemsServer.DeleteMirroredDatabase, + ) + + configureDefinitions( + handler, + entityOperations, + definitionOperations, + &server.ServerFactory.MirroredDatabase.ItemsServer.CreateMirroredDatabase, + &server.ServerFactory.MirroredDatabase.ItemsServer.GetMirroredDatabaseDefinition, + &server.ServerFactory.MirroredDatabase.ItemsServer.UpdateMirroredDatabaseDefinition, + ) + + return mirroreddatabase.MirroredDatabase{} +} + +// NewRandomMirroredDatabase returns a random Mirrored Database entity. +func NewRandomMirroredDatabase() mirroreddatabase.MirroredDatabase { + return mirroreddatabase.MirroredDatabase{ + ID: to.Ptr(testhelp.RandomUUID()), + DisplayName: to.Ptr(testhelp.RandomName()), + Description: to.Ptr(testhelp.RandomName()), + WorkspaceID: to.Ptr(testhelp.RandomUUID()), + Type: to.Ptr(mirroreddatabase.ItemTypeMirroredDatabase), + Properties: &mirroreddatabase.Properties{ + DefaultSchema: to.Ptr(testhelp.RandomName()), + OneLakeTablesPath: to.Ptr(testhelp.RandomURI()), + SQLEndpointProperties: &mirroreddatabase.SQLEndpointProperties{ + ProvisioningStatus: to.Ptr(mirroreddatabase.SQLEndpointProvisioningStatusSuccess), + ConnectionString: to.Ptr("Server=test;Database=mydb;User Id=test;Password=secret;"), + ID: to.Ptr(testhelp.RandomUUID()), + }, + }, + } +} + +// NewRandomMirroredDatabaseWithWorkspace returns a random Mirrored Database entity with a given workspaceID. +func NewRandomMirroredDatabaseWithWorkspace(workspaceID string) mirroreddatabase.MirroredDatabase { + db := NewRandomMirroredDatabase() + db.WorkspaceID = to.Ptr(workspaceID) + return db +} diff --git a/internal/testhelp/fakes/fake_handlers.go b/internal/testhelp/fakes/fake_handlers.go index 0b7a590d..fae153ea 100644 --- a/internal/testhelp/fakes/fake_handlers.go +++ b/internal/testhelp/fakes/fake_handlers.go @@ -154,7 +154,7 @@ func handleUpdateDefinition[TEntity, TDefinition, TRequest, TOptions, TResponse } } -func handleCreate[TEntity, TOptions, TCreateRequest, TResponse any](handler *typedHandler[TEntity], creator creator[TCreateRequest, TEntity], validator validator[TEntity], createTransformer createTransformer[TEntity, TResponse], f *func(ctx context.Context, createRequest TCreateRequest, options *TOptions) (resp azfake.Responder[TResponse], errResp azfake.ErrorResponder)) { +func handleCreateWithoutWorkspace[TEntity, TOptions, TCreateRequest, TResponse any](handler *typedHandler[TEntity], creator creator[TCreateRequest, TEntity], validator validator[TEntity], createTransformer createTransformer[TEntity, TResponse], f *func(ctx context.Context, createRequest TCreateRequest, options *TOptions) (resp azfake.Responder[TResponse], errResp azfake.ErrorResponder)) { if f == nil { return } @@ -184,6 +184,38 @@ func handleCreate[TEntity, TOptions, TCreateRequest, TResponse any](handler *typ } } +func handleNonLROCreate[TEntity, TOptions, TCreateRequest, TResponse any]( + handler *typedHandler[TEntity], + creator creatorWithParentID[TCreateRequest, TEntity], + validator validator[TEntity], + createTransformer createTransformer[TEntity, TResponse], + f *func(ctx context.Context, parentID string, createRequest TCreateRequest, options *TOptions) (resp azfake.Responder[TResponse], errResp azfake.ErrorResponder), +) { + if f == nil { + return + } + + *f = func(_ context.Context, parentID string, createRequest TCreateRequest, _ *TOptions) (azfake.Responder[TResponse], azfake.ErrorResponder) { + var resp azfake.Responder[TResponse] + var errResp azfake.ErrorResponder + + newEntity := creator.CreateWithParentID(parentID, createRequest) + + if statusCode, err := validator.Validate(newEntity, handler.Elements()); err != nil { + var empty TEntity + respValue := createTransformer.TransformCreate(empty) + resp.SetResponse(statusCode, respValue, nil) + errResp.SetError(err) + } else { + handler.Upsert(newEntity) + respValue := createTransformer.TransformCreate(newEntity) + resp.SetResponse(statusCode, respValue, nil) + } + + return resp, errResp + } +} + func handleCreateLRO[TEntity, TOptions, TCreateRequest, TResponse any](h *typedHandler[TEntity], creator creatorWithParentID[TCreateRequest, TEntity], validator validator[TEntity], createTransformer createTransformer[TEntity, TResponse], f *func(ctx context.Context, parentID string, createRequest TCreateRequest, options *TOptions) (resp azfake.PollerResponder[TResponse], errResp azfake.ErrorResponder)) { if f == nil { return diff --git a/internal/testhelp/fakes/fake_server.go b/internal/testhelp/fakes/fake_server.go index b6f671e9..9ddc2935 100644 --- a/internal/testhelp/fakes/fake_server.go +++ b/internal/testhelp/fakes/fake_server.go @@ -37,6 +37,7 @@ func newFakeServer() *fakeServer { handleEntity(server, configureEnvironment) handleEntity(server, configureKQLDatabase) handleEntity(server, configureLakehouse) + handleEntity(server, configureMirroredDatabase) handleEntity(server, configureNotebook) handleEntity(server, configureReport) handleEntity(server, configureSemanticModel) diff --git a/internal/testhelp/fakes/fake_typedhandler.go b/internal/testhelp/fakes/fake_typedhandler.go index 02edc146..a31f13bb 100644 --- a/internal/testhelp/fakes/fake_typedhandler.go +++ b/internal/testhelp/fakes/fake_typedhandler.go @@ -62,7 +62,7 @@ func configureEntityPagerWithSimpleID[TEntity, TGetOutput, TUpdateOutput, TCreat ) { handleGetWithSimpleID(handler, operations, getFunction) handleUpdateWithSimpleID(handler, operations, operations, updateFunction) - handleCreate(handler, operations, operations, operations, createFunction) + handleCreateWithoutWorkspace(handler, operations, operations, operations, createFunction) handleListPager(handler, operations, listFunction) handleDeleteWithSimpleID(handler, deleteFunction) } @@ -78,7 +78,7 @@ func configureEntityWithSimpleID[TEntity, TGetOutput, TUpdateOutput, TCreateOutp ) { handleGetWithSimpleID(handler, operations, getFunction) handleUpdateWithSimpleID(handler, operations, operations, updateFunction) - handleCreate(handler, operations, operations, operations, createFunction) + handleCreateWithoutWorkspace(handler, operations, operations, operations, createFunction) handleList(handler, operations, listFunction) handleDeleteWithSimpleID(handler, deleteFunction) } @@ -100,6 +100,23 @@ func configureEntityWithParentID[TEntity, TGetOutput, TUpdateOutput, TCreateOutp handleDeleteWithParentID(handler, deleteFunction) } +// ConfigureEntityWithParentID configures an entity with a parent ID. +func configureEntityWithParentIDNonLROCreation[TEntity, TGetOutput, TUpdateOutput, TCreateOutput, TListOutput, TCreationData, TUpdateData, TGetOptions, TUpdateOptions, TCreateOptions, TListOptions, TDeleteOptions, TDeleteResponse any]( + handler *typedHandler[TEntity], + operations parentIDOperations[TEntity, TGetOutput, TUpdateOutput, TCreateOutput, TListOutput, TCreationData, TUpdateData], + getFunction *func(ctx context.Context, parentID, childID string, options *TGetOptions) (resp azfake.Responder[TGetOutput], errResp azfake.ErrorResponder), + updateFunction *func(ctx context.Context, parentID, childID string, updateRequest TUpdateData, options *TUpdateOptions) (resp azfake.Responder[TUpdateOutput], errResp azfake.ErrorResponder), + createFunction *func(ctx context.Context, parentID string, createRequest TCreationData, options *TCreateOptions) (resp azfake.Responder[TCreateOutput], errResp azfake.ErrorResponder), + listFunction *func(parentID string, options *TListOptions) (resp azfake.PagerResponder[TListOutput]), + deleteFunction *func(ctx context.Context, parentID, childID string, options *TDeleteOptions) (resp azfake.Responder[TDeleteResponse], errResp azfake.ErrorResponder), +) { + handleGetWithParentID(handler, operations, getFunction) + handleUpdateWithParentID(handler, operations, operations, updateFunction) + handleNonLROCreate(handler, operations, operations, operations, createFunction) + handleListPagerWithParentID(handler, operations, operations, listFunction) + handleDeleteWithParentID(handler, deleteFunction) +} + // ConfigureDefinitions configures the definitions for an entity. func configureDefinitions[TEntity, TGetOutput, TUpdateOutput, TCreateOutput, TListOutput, TCreationData, TUpdateData, TCreateOptions, TDefinition, TUpdateDefinitionOptions, TDefinitionUpdateData, TDefinitionTransformerOutput, TUpdateDefinitionTransformerOutput, TGetDefinitionsOptions any]( handler *typedHandler[TEntity], @@ -114,6 +131,19 @@ func configureDefinitions[TEntity, TGetOutput, TUpdateOutput, TCreateOutput, TLi handleUpdateDefinition(handler, definitionOperations, updateDefinitionsFunction) } +func configureDefinitionsNonLROCreation[TEntity, TGetOutput, TUpdateOutput, TCreateOutput, TListOutput, TCreationData, TUpdateData, TCreateOptions, TDefinition, TUpdateDefinitionOptions, TDefinitionUpdateData, TDefinitionTransformerOutput, TUpdateDefinitionTransformerOutput, TGetDefinitionsOptions any]( + handler *typedHandler[TEntity], + entityOperations parentIDOperations[TEntity, TGetOutput, TUpdateOutput, TCreateOutput, TListOutput, TCreationData, TUpdateData], + definitionOperations definitionOperations[TDefinition, TCreationData, TDefinitionUpdateData, TDefinitionTransformerOutput, TUpdateDefinitionTransformerOutput], + createFunction *func(ctx context.Context, parentID string, createRequest TCreationData, options *TCreateOptions) (resp azfake.Responder[TCreateOutput], errResp azfake.ErrorResponder), + getDefinitionsFunction *func(ctx context.Context, parentID, childID string, options *TGetDefinitionsOptions) (resp azfake.Responder[TDefinitionTransformerOutput], errResp azfake.ErrorResponder), + updateDefinitionsFunction *func(ctx context.Context, parentID, childID string, updateRequest TDefinitionUpdateData, options *TUpdateDefinitionOptions) (resp azfake.Responder[TUpdateDefinitionTransformerOutput], errResp azfake.ErrorResponder), +) { + handleNonLROCreate(handler, entityOperations, entityOperations, entityOperations, createFunction) + handleGetDefinition(handler, definitionOperations, getDefinitionsFunction) + handleUpdateDefinition(handler, definitionOperations, updateDefinitionsFunction) +} + // GenerateID generates an ID from a parent and child ID. func generateID(parentID, childID string) string { return parentID + "/" + childID From 2653ae22f4d553e52d285a47d6749b23c30d10ef Mon Sep 17 00:00:00 2001 From: Or Bauberg Date: Thu, 27 Feb 2025 14:29:49 +0000 Subject: [PATCH 2/3] implementation without working resource E2E --- internal/provider/provider.go | 2 + .../data_mirrored_database.go | 13 +- .../data_mirrored_database_test.go | 70 ++-- .../data_mirrored_databases.go | 73 ++++ .../data_mirrored_databases_test.go | 101 ++++++ .../resource_mirrored_database.go | 80 ++++ .../resource_mirrored_database_test.go | 343 ++++++++++++++++++ ...on.go => schema_data_mirrored_database.go} | 0 .../schema_resource_mirrored_database.go | 39 ++ .../testhelp/fakes/fabric_mirroreddatabase.go | 41 +-- internal/testhelp/fakes/fake_handlers.go | 55 +++ internal/testhelp/fakes/fake_operations.go | 10 + internal/testhelp/fakes/fake_typedhandler.go | 17 +- 13 files changed, 766 insertions(+), 78 deletions(-) create mode 100644 internal/services/mirroreddatabase/data_mirrored_databases.go create mode 100644 internal/services/mirroreddatabase/data_mirrored_databases_test.go create mode 100644 internal/services/mirroreddatabase/resource_mirrored_database.go create mode 100644 internal/services/mirroreddatabase/resource_mirrored_database_test.go rename internal/services/mirroreddatabase/{schema_data_mirrored_database_definition.go => schema_data_mirrored_database.go} (100%) create mode 100644 internal/services/mirroreddatabase/schema_resource_mirrored_database.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index e6cad12a..b186d0e4 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -396,6 +396,7 @@ func (p *FabricProvider) Resources(ctx context.Context) []func() resource.Resour kqldatabase.NewResourceKQLDatabase, kqlqueryset.NewResourceKQLQueryset, func() resource.Resource { return lakehouse.NewResourceLakehouse(ctx) }, + mirroreddatabase.NewResourceMirroredDatabase, mlexperiment.NewResourceMLExperiment, mlmodel.NewResourceMLModel, notebook.NewResourceNotebook, @@ -444,6 +445,7 @@ func (p *FabricProvider) DataSources(ctx context.Context) []func() datasource.Da lakehouse.NewDataSourceLakehouseTable, lakehouse.NewDataSourceLakehouseTables, mirroreddatabase.NewDataSourceMirroredDatabase, + mirroreddatabase.NewDataSourceMirroredDatabases, mirroredwarehouse.NewDataSourceMirroredWarehouses, mlexperiment.NewDataSourceMLExperiment, mlexperiment.NewDataSourceMLExperiments, diff --git a/internal/services/mirroreddatabase/data_mirrored_database.go b/internal/services/mirroreddatabase/data_mirrored_database.go index 051ed271..8f3aeae3 100644 --- a/internal/services/mirroreddatabase/data_mirrored_database.go +++ b/internal/services/mirroreddatabase/data_mirrored_database.go @@ -30,7 +30,6 @@ func NewDataSourceMirroredDatabase() datasource.DataSource { return nil } - // itemGetter retrieves a single mirrored database item. itemGetter := func(ctx context.Context, fabricClient fabric.Client, model fabricitem.DataSourceFabricItemDefinitionPropertiesModel[mirroredDatabasePropertiesModel, mirroreddatabase.Properties], fabricItem *fabricitem.FabricItemProperties[mirroreddatabase.Properties]) error { client := mirroreddatabase.NewClientFactoryWithClient(fabricClient).NewItemsClient() respGet, err := client.GetMirroredDatabase(ctx, model.WorkspaceID.ValueString(), model.ID.ValueString(), nil) @@ -41,7 +40,6 @@ func NewDataSourceMirroredDatabase() datasource.DataSource { return nil } - // itemListGetter searches for a mirrored database by its display name. itemListGetter := func(ctx context.Context, fabricClient fabric.Client, model fabricitem.DataSourceFabricItemDefinitionPropertiesModel[mirroredDatabasePropertiesModel, mirroreddatabase.Properties], errNotFound fabcore.ResponseError, fabricItem *fabricitem.FabricItemProperties[mirroreddatabase.Properties]) error { client := mirroreddatabase.NewClientFactoryWithClient(fabricClient).NewItemsClient() pager := client.NewListMirroredDatabasesPager(model.WorkspaceID.ValueString(), nil) @@ -62,11 +60,12 @@ func NewDataSourceMirroredDatabase() datasource.DataSource { config := fabricitem.DataSourceFabricItemDefinitionProperties[mirroredDatabasePropertiesModel, mirroreddatabase.Properties]{ DataSourceFabricItemDefinition: fabricitem.DataSourceFabricItemDefinition{ - Type: "MirroredDatabase", - Name: "Mirrored Database", - TFName: "mirrored_database", - MarkdownDescription: "Get a Fabric Mirrored Database.\n\n" + - "Use this data source to fetch a Mirrored Database.\n\n", + Type: ItemType, + Name: ItemName, + TFName: ItemTFName, + MarkdownDescription: "Get a Fabric " + ItemName + ".\n\n" + + "Use this data source to fetch a [" + ItemName + "](" + ItemDocsURL + ").\n\n" + + ItemDocsSPNSupport, IsDisplayNameUnique: true, DefinitionFormats: itemDefinitionFormats, }, diff --git a/internal/services/mirroreddatabase/data_mirrored_database_test.go b/internal/services/mirroreddatabase/data_mirrored_database_test.go index 61f5b050..3992adc6 100644 --- a/internal/services/mirroreddatabase/data_mirrored_database_test.go +++ b/internal/services/mirroreddatabase/data_mirrored_database_test.go @@ -23,11 +23,11 @@ var ( func TestUnit_MirroredDatabaseDataSource(t *testing.T) { workspaceID := testhelp.RandomUUID() - entity := fakes.NewRandomItemWithWorkspace(itemTFName, workspaceID) + entity := fakes.NewRandomMirroredDatabaseWithWorkspace(workspaceID) - fakes.FakeServer.Upsert(fakes.NewRandomItemWithWorkspace(itemTFName, workspaceID)) + fakes.FakeServer.Upsert(fakes.NewRandomMirroredDatabaseWithWorkspace(workspaceID)) fakes.FakeServer.Upsert(entity) - fakes.FakeServer.Upsert(fakes.NewRandomItemWithWorkspace(itemTFName, workspaceID)) + fakes.FakeServer.Upsert(fakes.NewRandomMirroredDatabaseWithWorkspace(workspaceID)) resource.ParallelTest(t, testhelp.NewTestUnitCase(t, nil, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ // error - no attributes provided @@ -145,37 +145,37 @@ func TestUnit_MirroredDatabaseDataSource(t *testing.T) { ), ExpectError: regexp.MustCompile(common.ErrorReadHeader), }, - // read by id with definition - missing required format configuration - { - Config: at.CompileConfig( - testDataSourceMirroredDatabaseHeader, - map[string]any{ - "workspace_id": workspaceID, - "id": *entity.ID, - "output_definition": true, - }, - ), - ExpectError: regexp.MustCompile("Invalid configuration for attribute format"), - }, - // read by id with definition - success - { - Config: at.CompileConfig( - testDataSourceMirroredDatabaseHeader, - map[string]any{ - "workspace_id": workspaceID, - "id": *entity.ID, - "output_definition": true, - "format": "Default", - }, - ), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "workspace_id", entity.WorkspaceID), - resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "id", entity.ID), - resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "display_name", entity.DisplayName), - resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "description", entity.Description), - resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "definition.RealTimeDefinition.json.content"), - ), - }, + // // read by id with definition - missing required format configuration + // { + // Config: at.CompileConfig( + // testDataSourceMirroredDatabaseHeader, + // map[string]any{ + // "workspace_id": workspaceID, + // "id": *entity.ID, + // "output_definition": true, + // }, + // ), + // ExpectError: regexp.MustCompile("Invalid configuration for attribute format"), + // }, + // // read by id with definition - success + // { + // Config: at.CompileConfig( + // testDataSourceMirroredDatabaseHeader, + // map[string]any{ + // "workspace_id": workspaceID, + // "id": *entity.ID, + // "output_definition": true, + // "format": "Default", + // }, + // ), + // Check: resource.ComposeAggregateTestCheckFunc( + // resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "workspace_id", entity.WorkspaceID), + // resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "id", entity.ID), + // resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "display_name", entity.DisplayName), + // resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "description", entity.Description), + // resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "definition.mirroring.json.content"), + // ), + // }, })) } @@ -272,7 +272,7 @@ func TestAcc_MirroredDatabaseDataSource(t *testing.T) { resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "id", entityID), resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "display_name", entityDisplayName), resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "description", entityDescription), - resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "definition.RealTimeDefinition.json.content"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "definition.mirroring.json.content"), ), }, })) diff --git a/internal/services/mirroreddatabase/data_mirrored_databases.go b/internal/services/mirroreddatabase/data_mirrored_databases.go new file mode 100644 index 00000000..47b6b305 --- /dev/null +++ b/internal/services/mirroreddatabase/data_mirrored_databases.go @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package mirroreddatabase + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/microsoft/fabric-sdk-go/fabric" + "github.com/microsoft/fabric-sdk-go/fabric/mirroreddatabase" + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/microsoft/terraform-provider-fabric/internal/pkg/fabricitem" +) + +func NewDataSourceMirroredDatabases() datasource.DataSource { + propertiesSetter := func(ctx context.Context, from *mirroreddatabase.Properties, to *fabricitem.FabricItemPropertiesModel[mirroredDatabasePropertiesModel, mirroreddatabase.Properties]) diag.Diagnostics { + properties := supertypes.NewSingleNestedObjectValueOfNull[mirroredDatabasePropertiesModel](ctx) + + if from != nil { + propertiesModel := &mirroredDatabasePropertiesModel{} + propertiesModel.set(ctx, *from) + + if diags := properties.Set(ctx, propertiesModel); diags.HasError() { + return diags + } + } + + to.Properties = properties + + return nil + } + + itemListGetter := func(ctx context.Context, fabricClient fabric.Client, model fabricitem.DataSourceFabricItemsPropertiesModel[mirroredDatabasePropertiesModel, mirroreddatabase.Properties], fabricItems *[]fabricitem.FabricItemProperties[mirroreddatabase.Properties]) error { + client := mirroreddatabase.NewClientFactoryWithClient(fabricClient).NewItemsClient() + + fabItems := make([]fabricitem.FabricItemProperties[mirroreddatabase.Properties], 0) + + respList, err := client.ListMirroredDatabases(ctx, model.WorkspaceID.ValueString(), nil) + if err != nil { + return err + } + + for _, entity := range respList { + var fabricItem fabricitem.FabricItemProperties[mirroreddatabase.Properties] + fabricItem.Set(entity) + fabItems = append(fabItems, fabricItem) + } + + *fabricItems = fabItems + + return nil + } + + config := fabricitem.DataSourceFabricItemsProperties[mirroredDatabasePropertiesModel, mirroreddatabase.Properties]{ + DataSourceFabricItems: fabricitem.DataSourceFabricItems{ + Type: ItemType, + Name: ItemName, + Names: ItemsName, + TFName: ItemsTFName, + MarkdownDescription: "List Fabric " + ItemsName + ".\n\n" + + "Use this data source to list [" + ItemsName + "](" + ItemDocsURL + ").\n\n" + + ItemDocsSPNSupport, + }, + PropertiesAttributes: getDataSourceMirroredDatabasePropertiesAttributes(), + PropertiesSetter: propertiesSetter, + ItemListGetter: itemListGetter, + } + + return fabricitem.NewDataSourceFabricItemsProperties(config) +} diff --git a/internal/services/mirroreddatabase/data_mirrored_databases_test.go b/internal/services/mirroreddatabase/data_mirrored_databases_test.go new file mode 100644 index 00000000..7168a8a4 --- /dev/null +++ b/internal/services/mirroreddatabase/data_mirrored_databases_test.go @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package mirroreddatabase_test + +import ( + "regexp" + "testing" + + at "github.com/dcarbone/terraform-plugin-framework-utils/v3/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp/fakes" +) + +var ( + testDataSourceMirroredDatabasesFQN = testhelp.DataSourceFQN("fabric", "mirrored_databases", "test") + testDataSourceMirroredDatabasesHeader = at.DataSourceHeader(testhelp.TypeName("fabric", "mirrored_databases"), "test") +) + +func TestUnit_MirroredDatabasesDataSource(t *testing.T) { + workspaceID := testhelp.RandomUUID() + entity := fakes.NewRandomMirroredDatabaseWithWorkspace(workspaceID) + + fakes.FakeServer.Upsert(fakes.NewRandomMirroredDatabaseWithWorkspace(workspaceID)) + fakes.FakeServer.Upsert(entity) + fakes.FakeServer.Upsert(fakes.NewRandomMirroredDatabaseWithWorkspace(workspaceID)) + + resource.ParallelTest(t, testhelp.NewTestUnitCase(t, nil, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ + // error - no attributes + { + Config: at.CompileConfig( + testDataSourceMirroredDatabasesHeader, + map[string]any{}, + ), + ExpectError: regexp.MustCompile(`The argument "workspace_id" is required, but no definition was found`), + }, + // error - workspace_id - invalid UUID + { + Config: at.CompileConfig( + testDataSourceMirroredDatabasesHeader, + map[string]any{ + "workspace_id": "invalid uuid", + }, + ), + ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + }, + // error - unexpected_attr + { + Config: at.CompileConfig( + testDataSourceMirroredDatabasesHeader, + map[string]any{ + "workspace_id": workspaceID, + "unexpected_attr": "test", + }, + ), + ExpectError: regexp.MustCompile(`An argument named "unexpected_attr" is not expected here`), + }, + // read + { + Config: at.CompileConfig( + testDataSourceMirroredDatabasesHeader, + map[string]any{ + "workspace_id": workspaceID, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabasesFQN, "workspace_id", entity.WorkspaceID), + resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabasesFQN, "values.1.id", entity.ID), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabasesFQN, "values.1.properties.default_schema"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabasesFQN, "values.1.properties.onelake_tables_path"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabasesFQN, "values.1.properties.sql_endpoint_properties.connection_string"), + ), + }, + })) +} + +func TestAcc_MirroredDatabasesDataSource(t *testing.T) { + workspace := testhelp.WellKnown()["WorkspaceDS"].(map[string]any) + workspaceID := workspace["id"].(string) + + resource.ParallelTest(t, testhelp.NewTestAccCase(t, nil, nil, []resource.TestStep{ + // read + { + Config: at.CompileConfig( + testDataSourceMirroredDatabasesHeader, + map[string]any{ + "workspace_id": workspaceID, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceMirroredDatabasesFQN, "workspace_id", workspaceID), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabasesFQN, "values.0.id"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabasesFQN, "values.0.properties.default_schema"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabasesFQN, "values.0.properties.onelake_tables_path"), + ), + }, + })) +} diff --git a/internal/services/mirroreddatabase/resource_mirrored_database.go b/internal/services/mirroreddatabase/resource_mirrored_database.go new file mode 100644 index 00000000..b4fe2c8c --- /dev/null +++ b/internal/services/mirroreddatabase/resource_mirrored_database.go @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package mirroreddatabase + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/microsoft/fabric-sdk-go/fabric" + "github.com/microsoft/fabric-sdk-go/fabric/mirroreddatabase" + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/microsoft/terraform-provider-fabric/internal/pkg/fabricitem" +) + +func NewResourceMirroredDatabase() resource.Resource { + propertiesSetter := func(ctx context.Context, from *mirroreddatabase.Properties, to *fabricitem.ResourceFabricItemDefinitionPropertiesModel[mirroredDatabasePropertiesModel, mirroreddatabase.Properties]) diag.Diagnostics { + properties := supertypes.NewSingleNestedObjectValueOfNull[mirroredDatabasePropertiesModel](ctx) + + if from == nil { + return nil + } + + propertiesModel := &mirroredDatabasePropertiesModel{} + if diags := propertiesModel.set(ctx, *from); diags.HasError() { + return diags + } + + diags := properties.Set(ctx, propertiesModel) + if diags.HasError() { + return diags + } + + to.Properties = properties + return nil + } + + itemGetter := func(ctx context.Context, client fabric.Client, model fabricitem.ResourceFabricItemDefinitionPropertiesModel[mirroredDatabasePropertiesModel, mirroreddatabase.Properties], fabricItem *fabricitem.FabricItemProperties[mirroreddatabase.Properties]) error { + mirroredDbClient := mirroreddatabase.NewClientFactoryWithClient(client).NewItemsClient() + + resp, err := mirroredDbClient.GetMirroredDatabase(ctx, model.WorkspaceID.ValueString(), model.ID.ValueString(), nil) + if err != nil { + return err + } + + fabricItem.Set(resp.MirroredDatabase) + return nil + } + + config := fabricitem.ResourceFabricItemDefinitionProperties[mirroredDatabasePropertiesModel, mirroreddatabase.Properties]{ + ResourceFabricItemDefinition: fabricitem.ResourceFabricItemDefinition{ + Type: ItemType, + Name: ItemName, + NameRenameAllowed: true, + TFName: ItemTFName, + MarkdownDescription: "Manages a Fabric " + ItemName + ".\n\n" + + "Use this resource to create and manage a [" + ItemName + "](" + ItemDocsURL + ").\n\n" + + ItemDocsSPNSupport, + DisplayNameMaxLength: 123, + DescriptionMaxLength: 256, + DefinitionPathDocsURL: ItemDefinitionPathDocsURL, + DefinitionPathKeysValidator: []validator.Map{ + mapvalidator.SizeAtMost(1), + mapvalidator.KeysAre(fabricitem.DefinitionPathKeysValidator(itemDefinitionFormats)...), + }, + DefinitionRequired: false, + DefinitionEmpty: ItemDefinitionEmpty, + DefinitionFormats: itemDefinitionFormats, + }, + PropertiesAttributes: getResourceMirroredDatabasePropertiesAttributes(), + PropertiesSetter: propertiesSetter, + ItemGetter: itemGetter, + } + + return fabricitem.NewResourceFabricItemDefinitionProperties(config) +} diff --git a/internal/services/mirroreddatabase/resource_mirrored_database_test.go b/internal/services/mirroreddatabase/resource_mirrored_database_test.go new file mode 100644 index 00000000..7501bf21 --- /dev/null +++ b/internal/services/mirroreddatabase/resource_mirrored_database_test.go @@ -0,0 +1,343 @@ +package mirroreddatabase_test + +import ( + "errors" + "fmt" + "regexp" + "testing" + + at "github.com/dcarbone/terraform-plugin-framework-utils/v3/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp/fakes" +) + +var ( + testResourceItemFQN = testhelp.ResourceFQN("fabric", itemTFName, "test") + testResourceItemHeader = at.ResourceHeader(testhelp.TypeName("fabric", itemTFName), "test") +) + +var testHelperLocals = at.CompileLocalsConfig(map[string]any{ + "path": testhelp.GetFixturesDirPath("mirrored_database"), +}) + +var testHelperDefinition = map[string]any{ + `"mirroring.json"`: map[string]any{ + "source": "${local.path}/mirroring.json", + }, +} + +func TestUnit_MirroredDatabaseResource_Attributes(t *testing.T) { + resource.ParallelTest(t, testhelp.NewTestUnitCase(t, &testResourceItemFQN, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ + // error - no attributes + { + ResourceName: testResourceItemFQN, + Config: at.JoinConfigs( + testHelperLocals, + at.CompileConfig( + testResourceItemHeader, + map[string]any{}, + ), + ), + ExpectError: regexp.MustCompile(`Missing required argument`), + }, + // error - workspace_id - invalid UUID + { + ResourceName: testResourceItemFQN, + Config: at.JoinConfigs( + testHelperLocals, + at.CompileConfig( + testResourceItemHeader, + map[string]any{ + "workspace_id": "invalid uuid", + "display_name": "test", + "format": "Default", + "definition": testHelperDefinition, + }, + ), + ), + ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + }, + // error - unexpected_attr + { + ResourceName: testResourceItemFQN, + Config: at.JoinConfigs( + testHelperLocals, + at.CompileConfig( + testResourceItemHeader, + map[string]any{ + "workspace_id": "00000000-0000-0000-0000-000000000000", + "display_name": "test", + "unexpected_attr": "test", + "format": "Default", + "definition": testHelperDefinition, + }, + ), + ), + ExpectError: regexp.MustCompile(`An argument named "unexpected_attr" is not expected here.`), + }, + // error - no required attributes + { + ResourceName: testResourceItemFQN, + Config: at.JoinConfigs( + testHelperLocals, + at.CompileConfig( + testResourceItemHeader, + map[string]any{ + "display_name": "test", + "format": "Default", + "definition": testHelperDefinition, + }, + )), + ExpectError: regexp.MustCompile(`The argument "workspace_id" is required, but no definition was found.`), + }, + // error - no required attributes + { + ResourceName: testResourceItemFQN, + Config: at.JoinConfigs( + testHelperLocals, + at.CompileConfig( + testResourceItemHeader, + map[string]any{ + "workspace_id": "00000000-0000-0000-0000-000000000000", + "format": "Default", + "definition": testHelperDefinition, + }, + )), + ExpectError: regexp.MustCompile(`The argument "display_name" is required, but no definition was found.`), + }, + })) +} + +func TestUnit_MirroredDatabaseResource_ImportState(t *testing.T) { + workspaceID := testhelp.RandomUUID() + entity := fakes.NewRandomMirroredDatabaseWithWorkspace(workspaceID) + + fakes.FakeServer.Upsert(fakes.NewRandomMirroredDatabaseWithWorkspace(workspaceID)) + fakes.FakeServer.Upsert(entity) + fakes.FakeServer.Upsert(fakes.NewRandomMirroredDatabaseWithWorkspace(workspaceID)) + + testCase := at.JoinConfigs( + testHelperLocals, + at.CompileConfig( + testResourceItemHeader, + map[string]any{ + "workspace_id": *entity.WorkspaceID, + "display_name": *entity.DisplayName, + "format": "Default", + "definition": testHelperDefinition, + }, + )) + + resource.Test(t, testhelp.NewTestUnitCase(t, &testResourceItemFQN, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ + { + ResourceName: testResourceItemFQN, + Config: testCase, + ImportStateId: "not-valid", + ImportState: true, + ExpectError: regexp.MustCompile(fmt.Sprintf(common.ErrorImportIdentifierDetails, fmt.Sprintf("WorkspaceID/%sID", string(itemType)))), + }, + { + ResourceName: testResourceItemFQN, + Config: testCase, + ImportStateId: "test/id", + ImportState: true, + ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + }, + { + ResourceName: testResourceItemFQN, + Config: testCase, + ImportStateId: fmt.Sprintf("%s/%s", "test", *entity.ID), + ImportState: true, + ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + }, + { + ResourceName: testResourceItemFQN, + Config: testCase, + ImportStateId: fmt.Sprintf("%s/%s", *entity.WorkspaceID, "test"), + ImportState: true, + ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + }, + // Import state testing + { + ResourceName: testResourceItemFQN, + Config: testCase, + ImportStateId: fmt.Sprintf("%s/%s", *entity.WorkspaceID, *entity.ID), + ImportState: true, + ImportStatePersist: true, + ImportStateCheck: func(is []*terraform.InstanceState) error { + if len(is) != 1 { + return errors.New("expected one instance state") + } + + if is[0].ID != *entity.ID { + return errors.New(testResourceItemFQN + ": unexpected ID") + } + + return nil + }, + }, + })) +} + +func TestUnit_MirroredDatabaseResource_CRUD(t *testing.T) { + workspaceID := testhelp.RandomUUID() + entityExist := fakes.NewRandomMirroredDatabaseWithWorkspace(workspaceID) + entityBefore := fakes.NewRandomMirroredDatabaseWithWorkspace(workspaceID) + entityAfter := fakes.NewRandomMirroredDatabaseWithWorkspace(workspaceID) + entityNoDefinition := fakes.NewRandomMirroredDatabaseWithWorkspace(workspaceID) + + fakes.FakeServer.Upsert(fakes.NewRandomMirroredDatabaseWithWorkspace(workspaceID)) + fakes.FakeServer.Upsert(entityExist) + fakes.FakeServer.Upsert(entityAfter) + fakes.FakeServer.Upsert(entityNoDefinition) + fakes.FakeServer.Upsert(fakes.NewRandomMirroredDatabaseWithWorkspace(workspaceID)) + + resource.Test(t, testhelp.NewTestUnitCase(t, &testResourceItemFQN, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ + // error - create - existing entity + { + ResourceName: testResourceItemFQN, + Config: at.JoinConfigs( + testHelperLocals, + at.CompileConfig( + testResourceItemHeader, + map[string]any{ + "workspace_id": *entityExist.WorkspaceID, + "display_name": *entityExist.DisplayName, + "format": "Default", + "definition": testHelperDefinition, + }, + )), + ExpectError: regexp.MustCompile(common.ErrorCreateHeader), + }, + // Create and Read with definition + { + ResourceName: testResourceItemFQN, + Config: at.JoinConfigs( + testHelperLocals, + at.CompileConfig( + testResourceItemHeader, + map[string]any{ + "workspace_id": *entityBefore.WorkspaceID, + "display_name": *entityBefore.DisplayName, + "description": *entityBefore.Description, + "format": "Default", + "definition": testHelperDefinition, + }, + )), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPtr(testResourceItemFQN, "display_name", entityBefore.DisplayName), + resource.TestCheckResourceAttrPtr(testResourceItemFQN, "description", entityBefore.Description), + resource.TestCheckResourceAttr(testResourceItemFQN, "definition_update_enabled", "true"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.default_schema"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.onelake_tables_path"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.provisioning_status"), + ), + }, + // Create and Read without definition + { + ResourceName: testResourceItemFQN, + Config: at.CompileConfig( + testResourceItemHeader, + map[string]any{ + "workspace_id": *entityNoDefinition.WorkspaceID, + "display_name": *entityNoDefinition.DisplayName, + "description": *entityNoDefinition.Description, + }), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPtr(testResourceItemFQN, "display_name", entityNoDefinition.DisplayName), + resource.TestCheckResourceAttrPtr(testResourceItemFQN, "description", entityNoDefinition.Description), + resource.TestCheckResourceAttr(testResourceItemFQN, "definition_update_enabled", "true"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.default_schema"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.onelake_tables_path"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.provisioning_status"), + resource.TestCheckNoResourceAttr(testResourceItemFQN, "definition"), + resource.TestCheckNoResourceAttr(testResourceItemFQN, "format"), + ), + }, + // Update and Read + { + ResourceName: testResourceItemFQN, + Config: at.JoinConfigs( + testHelperLocals, + at.CompileConfig( + testResourceItemHeader, + map[string]any{ + "workspace_id": *entityBefore.WorkspaceID, + "display_name": *entityAfter.DisplayName, + "description": *entityAfter.Description, + "format": "Default", + "definition": testHelperDefinition, + }, + )), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPtr(testResourceItemFQN, "display_name", entityAfter.DisplayName), + resource.TestCheckResourceAttrPtr(testResourceItemFQN, "description", entityAfter.Description), + resource.TestCheckResourceAttr(testResourceItemFQN, "definition_update_enabled", "true"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.default_schema"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.onelake_tables_path"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.provisioning_status"), + ), + }, + // Delete testing automatically occurs in TestCase + })) +} + +func TestAcc_MirroredDatabaseResource_CRUD(t *testing.T) { + workspace := testhelp.WellKnown()["WorkspaceDS"].(map[string]any) + workspaceID := workspace["id"].(string) + + entityCreateDisplayName := testhelp.RandomName() + entityCreateDescription := testhelp.RandomName() + entityUpdateDisplayName := testhelp.RandomName() + entityUpdateDescription := testhelp.RandomName() + + resource.Test(t, testhelp.NewTestAccCase(t, &testResourceItemFQN, nil, []resource.TestStep{ + // Create with definition and Read + { + ResourceName: testResourceItemFQN, + Config: at.JoinConfigs( + testHelperLocals, + at.CompileConfig( + testResourceItemHeader, + map[string]any{ + "workspace_id": workspaceID, + "display_name": entityCreateDisplayName, + "description": entityCreateDescription, + "format": "Default", + "definition": testHelperDefinition, + }, + )), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testResourceItemFQN, "display_name", entityCreateDisplayName), + resource.TestCheckResourceAttr(testResourceItemFQN, "description", entityCreateDescription), + resource.TestCheckResourceAttr(testResourceItemFQN, "definition_update_enabled", "true"), + ), + }, + // Update definition and Read + { + ResourceName: testResourceItemFQN, + Config: at.JoinConfigs( + testHelperLocals, + at.CompileConfig( + testResourceItemHeader, + map[string]any{ + "workspace_id": workspaceID, + "display_name": entityUpdateDisplayName, + "description": entityUpdateDescription, + "format": "Default", + "definition": testHelperDefinition, + }, + )), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testResourceItemFQN, "display_name", entityUpdateDisplayName), + resource.TestCheckResourceAttr(testResourceItemFQN, "description", entityUpdateDescription), + resource.TestCheckResourceAttr(testResourceItemFQN, "definition_update_enabled", "true")), + }, + }, + )) +} diff --git a/internal/services/mirroreddatabase/schema_data_mirrored_database_definition.go b/internal/services/mirroreddatabase/schema_data_mirrored_database.go similarity index 100% rename from internal/services/mirroreddatabase/schema_data_mirrored_database_definition.go rename to internal/services/mirroreddatabase/schema_data_mirrored_database.go diff --git a/internal/services/mirroreddatabase/schema_resource_mirrored_database.go b/internal/services/mirroreddatabase/schema_resource_mirrored_database.go new file mode 100644 index 00000000..ea0ec03e --- /dev/null +++ b/internal/services/mirroreddatabase/schema_resource_mirrored_database.go @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package mirroreddatabase + +import ( + "github.com/hashicorp/terraform-plugin-framework/resource/schema" +) + +func getResourceMirroredDatabasePropertiesAttributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "default_schema": schema.StringAttribute{ + MarkdownDescription: "Default schema of the mirrored database, returned only for mirrored databases that enable default schema in definition.", + Computed: true, + }, + "onelake_tables_path": schema.StringAttribute{ + MarkdownDescription: "OneLake path to the mirrored database tables directory.", + Computed: true, + }, + "sql_endpoint_properties": schema.SingleNestedAttribute{ + MarkdownDescription: "An object containing the properties of the SQL endpoint.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "provisioning_status": schema.StringAttribute{ + MarkdownDescription: "The SQL endpoint provisioning status.", + Computed: true, + }, + "connection_string": schema.StringAttribute{ + MarkdownDescription: "The SQL endpoint connection string.", + Computed: true, + }, + "id": schema.StringAttribute{ + MarkdownDescription: "The SQL endpoint ID.", + Computed: true, + }, + }, + }, + } +} diff --git a/internal/testhelp/fakes/fabric_mirroreddatabase.go b/internal/testhelp/fakes/fabric_mirroreddatabase.go index 48d7f9c3..949bcee4 100644 --- a/internal/testhelp/fakes/fabric_mirroreddatabase.go +++ b/internal/testhelp/fakes/fabric_mirroreddatabase.go @@ -33,24 +33,21 @@ func (o *operationsMirroredDatabase) ConvertItemToEntity(item fabcore.Item) mirr } // CreateDefinition implements concreteDefinitionOperations. -// It simply returns the Definition provided in the Create request. -func (o *operationsMirroredDatabase) CreateDefinition(data mirroreddatabase.CreateMirroredDatabaseRequest) *mirroreddatabase.Definition { +func (o *operationsMirroredDatabase) CreateDefinition(data fabcore.CreateItemRequest) *fabcore.ItemDefinition { return data.Definition } // TransformDefinition implements concreteDefinitionOperations. -// It wraps the public definition into the GetDefinition response. -func (o *operationsMirroredDatabase) TransformDefinition(entity *mirroreddatabase.Definition) mirroreddatabase.ItemsClientGetMirroredDatabaseDefinitionResponse { - return mirroreddatabase.ItemsClientGetMirroredDatabaseDefinitionResponse{ - DefinitionResponse: mirroreddatabase.DefinitionResponse{ +func (o *operationsMirroredDatabase) TransformDefinition(entity *fabcore.ItemDefinition) fabcore.ItemsClientGetItemDefinitionResponse { + return fabcore.ItemsClientGetItemDefinitionResponse{ + ItemDefinitionResponse: fabcore.ItemDefinitionResponse{ Definition: entity, }, } } // UpdateDefinition implements concreteDefinitionOperations. -// It returns the updated definition as provided in the Update request. -func (o *operationsMirroredDatabase) UpdateDefinition(_ *mirroreddatabase.Definition, data mirroreddatabase.UpdateMirroredDatabaseDefinitionRequest) *mirroreddatabase.Definition { +func (o *operationsMirroredDatabase) UpdateDefinition(_ *fabcore.ItemDefinition, data fabcore.UpdateItemDefinitionRequest) *fabcore.ItemDefinition { return data.Definition } @@ -154,13 +151,11 @@ func configureMirroredDatabase(server *fakeServer) mirroreddatabase.MirroredData // Define the definition operations interface for MirroredDatabase. type concreteDefinitionOperations interface { - definitionOperations[ - mirroreddatabase.Definition, - mirroreddatabase.CreateMirroredDatabaseRequest, - mirroreddatabase.UpdateMirroredDatabaseDefinitionRequest, - mirroreddatabase.ItemsClientGetMirroredDatabaseDefinitionResponse, - mirroreddatabase.ItemsClientUpdateMirroredDatabaseDefinitionResponse, - ] + definitionOperationsNonLROCreation[ + fabcore.ItemDefinition, + fabcore.UpdateItemDefinitionRequest, + fabcore.ItemsClientGetItemDefinitionResponse, + fabcore.ItemsClientUpdateItemDefinitionResponse] } var entityOperations concreteEntityOperations = &operationsMirroredDatabase{} @@ -169,13 +164,7 @@ func configureMirroredDatabase(server *fakeServer) mirroreddatabase.MirroredData handler := newTypedHandlerWithConverter(server, entityOperations, converter) - // handleGetWithParentID(handler, entityOperations, &handler.ServerFactory.MirroredDatabase.ItemsServer.GetMirroredDatabase) - // handleUpdateWithParentID(handler, entityOperations, entityOperations, &handler.ServerFactory.MirroredDatabase.ItemsServer.UpdateMirroredDatabase) - // handleNonLROCreate(handler, entityOperations, entityOperations, entityOperations, &handler.ServerFactory.MirroredDatabase.ItemsServer.CreateMirroredDatabase) - // handleListPagerWithParentID(handler, entityOperations, entityOperations, &handler.ServerFactory.MirroredDatabase.ItemsServer.NewListMirroredDatabasesPager) - // handleDeleteWithParentID(handler, &handler.ServerFactory.MirroredDatabase.ItemsServer.DeleteMirroredDatabase) - - configureEntityWithParentID( + configureNonLROEntityWithParentID( handler, entityOperations, &server.ServerFactory.MirroredDatabase.ItemsServer.GetMirroredDatabase, @@ -185,13 +174,11 @@ func configureMirroredDatabase(server *fakeServer) mirroreddatabase.MirroredData &server.ServerFactory.MirroredDatabase.ItemsServer.DeleteMirroredDatabase, ) - configureDefinitions( + ConfigureDefinitionsNonLROCreation( handler, - entityOperations, definitionOperations, - &server.ServerFactory.MirroredDatabase.ItemsServer.CreateMirroredDatabase, - &server.ServerFactory.MirroredDatabase.ItemsServer.GetMirroredDatabaseDefinition, - &server.ServerFactory.MirroredDatabase.ItemsServer.UpdateMirroredDatabaseDefinition, + &server.ServerFactory.Core.ItemsServer.BeginGetItemDefinition, + &server.ServerFactory.Core.ItemsServer.BeginUpdateItemDefinition, ) return mirroreddatabase.MirroredDatabase{} diff --git a/internal/testhelp/fakes/fake_handlers.go b/internal/testhelp/fakes/fake_handlers.go index fae153ea..bc3536da 100644 --- a/internal/testhelp/fakes/fake_handlers.go +++ b/internal/testhelp/fakes/fake_handlers.go @@ -216,6 +216,61 @@ func handleNonLROCreate[TEntity, TOptions, TCreateRequest, TResponse any]( } } +func handleNonLROUpdateDefinition[TEntity, TDefinition, TRequest, TOptions, TResponse any](handler *typedHandler[TEntity], definitionUpdater definitionUpdater[TRequest, TDefinition], function *func(ctx context.Context, parentID, childID string, request TRequest, options *TOptions) (azfake.Responder[TResponse], azfake.ErrorResponder)) { + if function == nil { + return + } + + *function = func(_ context.Context, parentID, childID string, request TRequest, _ *TOptions) (azfake.Responder[TResponse], azfake.ErrorResponder) { //nolint:unparam + var resp azfake.Responder[TResponse] + + var errResp azfake.ErrorResponder + + id := generateID(parentID, childID) + + typedDefinition := getDefinition[TDefinition](handler, id) + + updatedDefinition := definitionUpdater.UpdateDefinition(typedDefinition, request) + upsertDefinition(handler, id, updatedDefinition) + + var respValue TResponse + + resp.SetResponse(http.StatusOK, respValue, nil) + + return resp, errResp + } +} + +func handleNonLROGetDefinition[TEntity, TDefinition, TOptions, TResponse any](handler *typedHandler[TEntity], definitionTransformer definitionTransformer[TDefinition, TResponse], function *func(ctx context.Context, parentID, childID string, options *TOptions) (azfake.Responder[TResponse], azfake.ErrorResponder)) { + if function == nil { + return + } + + *function = func(_ context.Context, parentID, childID string, _ *TOptions) (azfake.Responder[TResponse], azfake.ErrorResponder) { //nolint:unparam + var resp azfake.Responder[TResponse] + + var errResp azfake.ErrorResponder + + id := generateID(parentID, childID) + + if definition, ok := handler.definitions[id]; ok { + typedDefinition, ok := definition.(TDefinition) + + if !ok { + panic("Definition not of the expected type") // lintignore:R009 + } + + respValue := definitionTransformer.TransformDefinition(&typedDefinition) + resp.SetResponse(http.StatusOK, respValue, nil) + } else { + respValue := definitionTransformer.TransformDefinition(nil) + resp.SetResponse(http.StatusOK, respValue, nil) + } + + return resp, errResp + } +} + func handleCreateLRO[TEntity, TOptions, TCreateRequest, TResponse any](h *typedHandler[TEntity], creator creatorWithParentID[TCreateRequest, TEntity], validator validator[TEntity], createTransformer createTransformer[TEntity, TResponse], f *func(ctx context.Context, parentID string, createRequest TCreateRequest, options *TOptions) (resp azfake.PollerResponder[TResponse], errResp azfake.ErrorResponder)) { if f == nil { return diff --git a/internal/testhelp/fakes/fake_operations.go b/internal/testhelp/fakes/fake_operations.go index 5412eee5..cf9571aa 100644 --- a/internal/testhelp/fakes/fake_operations.go +++ b/internal/testhelp/fakes/fake_operations.go @@ -59,3 +59,13 @@ type definitionOperations[ definitionUpdater[TDefinitionUpdateRequest, TDefinition] definitionTransformer[TDefinition, TGetDefinitionResponse] } + +// Operations that apply to entities with a definition non LRO creation. +type definitionOperationsNonLROCreation[ + TDefinition, + TDefinitionUpdateRequest, + TGetDefinitionResponse, + TUpdateDefinitionResponse any] interface { + definitionUpdater[TDefinitionUpdateRequest, TDefinition] + definitionTransformer[TDefinition, TGetDefinitionResponse] +} diff --git a/internal/testhelp/fakes/fake_typedhandler.go b/internal/testhelp/fakes/fake_typedhandler.go index a31f13bb..22f32457 100644 --- a/internal/testhelp/fakes/fake_typedhandler.go +++ b/internal/testhelp/fakes/fake_typedhandler.go @@ -100,8 +100,8 @@ func configureEntityWithParentID[TEntity, TGetOutput, TUpdateOutput, TCreateOutp handleDeleteWithParentID(handler, deleteFunction) } -// ConfigureEntityWithParentID configures an entity with a parent ID. -func configureEntityWithParentIDNonLROCreation[TEntity, TGetOutput, TUpdateOutput, TCreateOutput, TListOutput, TCreationData, TUpdateData, TGetOptions, TUpdateOptions, TCreateOptions, TListOptions, TDeleteOptions, TDeleteResponse any]( +// ConfigureEntityWithParentID configures an entity with a parent ID with sync creation +func configureNonLROEntityWithParentID[TEntity, TGetOutput, TUpdateOutput, TCreateOutput, TListOutput, TCreationData, TUpdateData, TGetOptions, TUpdateOptions, TCreateOptions, TListOptions, TDeleteOptions, TDeleteResponse any]( handler *typedHandler[TEntity], operations parentIDOperations[TEntity, TGetOutput, TUpdateOutput, TCreateOutput, TListOutput, TCreationData, TUpdateData], getFunction *func(ctx context.Context, parentID, childID string, options *TGetOptions) (resp azfake.Responder[TGetOutput], errResp azfake.ErrorResponder), @@ -131,15 +131,14 @@ func configureDefinitions[TEntity, TGetOutput, TUpdateOutput, TCreateOutput, TLi handleUpdateDefinition(handler, definitionOperations, updateDefinitionsFunction) } -func configureDefinitionsNonLROCreation[TEntity, TGetOutput, TUpdateOutput, TCreateOutput, TListOutput, TCreationData, TUpdateData, TCreateOptions, TDefinition, TUpdateDefinitionOptions, TDefinitionUpdateData, TDefinitionTransformerOutput, TUpdateDefinitionTransformerOutput, TGetDefinitionsOptions any]( +// ConfigureDefinitionsNonLROCreation configures the definitions for an entity with non-LRO creation +// This handles the case where entity creation doesn't involve long-running operations +func ConfigureDefinitionsNonLROCreation[TEntity, TDefinition, TUpdateDefinitionOptions, TDefinitionUpdateData, TDefinitionTransformerOutput, TUpdateDefinitionTransformerOutput any, TGetDefinitionsOptions any]( handler *typedHandler[TEntity], - entityOperations parentIDOperations[TEntity, TGetOutput, TUpdateOutput, TCreateOutput, TListOutput, TCreationData, TUpdateData], - definitionOperations definitionOperations[TDefinition, TCreationData, TDefinitionUpdateData, TDefinitionTransformerOutput, TUpdateDefinitionTransformerOutput], - createFunction *func(ctx context.Context, parentID string, createRequest TCreationData, options *TCreateOptions) (resp azfake.Responder[TCreateOutput], errResp azfake.ErrorResponder), - getDefinitionsFunction *func(ctx context.Context, parentID, childID string, options *TGetDefinitionsOptions) (resp azfake.Responder[TDefinitionTransformerOutput], errResp azfake.ErrorResponder), - updateDefinitionsFunction *func(ctx context.Context, parentID, childID string, updateRequest TDefinitionUpdateData, options *TUpdateDefinitionOptions) (resp azfake.Responder[TUpdateDefinitionTransformerOutput], errResp azfake.ErrorResponder), + definitionOperations definitionOperationsNonLROCreation[TDefinition, TDefinitionUpdateData, TDefinitionTransformerOutput, TUpdateDefinitionTransformerOutput], + getDefinitionsFunction *func(ctx context.Context, parentID, childID string, options *TGetDefinitionsOptions) (resp azfake.PollerResponder[TDefinitionTransformerOutput], errResp azfake.ErrorResponder), + updateDefinitionsFunction *func(ctx context.Context, parentID, childID string, updateRequest TDefinitionUpdateData, options *TUpdateDefinitionOptions) (resp azfake.PollerResponder[TUpdateDefinitionTransformerOutput], errResp azfake.ErrorResponder), ) { - handleNonLROCreate(handler, entityOperations, entityOperations, entityOperations, createFunction) handleGetDefinition(handler, definitionOperations, getDefinitionsFunction) handleUpdateDefinition(handler, definitionOperations, updateDefinitionsFunction) } From 6c9ba2dacd1d08437cffc20b2b1469d638ed5e75 Mon Sep 17 00:00:00 2001 From: Or Bauberg Date: Wed, 5 Mar 2025 12:54:14 +0000 Subject: [PATCH 3/3] final commit without update defintion E2E --- internal/services/mirroreddatabase/base.go | 16 +-- .../data_mirrored_database_test.go | 46 ++---- .../data_mirrored_databases_test.go | 2 + .../resource_mirrored_database.go | 2 +- .../resource_mirrored_database_test.go | 134 ++++++++++++++---- .../mirrored_database/mirroring_update.json | 14 ++ 6 files changed, 150 insertions(+), 64 deletions(-) create mode 100644 internal/testhelp/fixtures/mirrored_database/mirroring_update.json diff --git a/internal/services/mirroreddatabase/base.go b/internal/services/mirroreddatabase/base.go index fdebc90f..73a611e4 100644 --- a/internal/services/mirroreddatabase/base.go +++ b/internal/services/mirroreddatabase/base.go @@ -18,20 +18,20 @@ const ( ItemType = fabcore.ItemTypeMirroredDatabase ItemDocsSPNSupport = common.DocsSPNSupported ItemDocsURL = "https://learn.microsoft.com/en-us/fabric/database/mirrored-database/overview" - ItemDefinitionEmpty = `{ - "properties": { + ItemDefinitionEmpty = ` +{ + "properties": { "source": { - "type": "", - "typeProperties": null + "type": "GenericMirror" }, "target": { - "type": "", + "type": "MountedRelationalDatabase", "typeProperties": { - "format": "", - "defaultSchema": "" + "format": "Delta", + "defaultSchema": "dbo" } } - } + } }` ItemDefinitionPathDocsURL = "https://learn.microsoft.com/en-us/rest/api/fabric/articles/item-management/definitions/mirrored-database-definition" ) diff --git a/internal/services/mirroreddatabase/data_mirrored_database_test.go b/internal/services/mirroreddatabase/data_mirrored_database_test.go index 3992adc6..ff000fed 100644 --- a/internal/services/mirroreddatabase/data_mirrored_database_test.go +++ b/internal/services/mirroreddatabase/data_mirrored_database_test.go @@ -105,6 +105,11 @@ func TestUnit_MirroredDatabaseDataSource(t *testing.T) { resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "id", entity.ID), resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "display_name", entity.DisplayName), resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "description", entity.Description), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "properties.default_schema"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "properties.onelake_tables_path"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "properties.sql_endpoint_properties.provisioning_status"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "properties.sql_endpoint_properties.connection_string"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "properties.sql_endpoint_properties.id"), ), }, // read by id - not found @@ -132,6 +137,11 @@ func TestUnit_MirroredDatabaseDataSource(t *testing.T) { resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "id", entity.ID), resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "display_name", entity.DisplayName), resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "description", entity.Description), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "properties.default_schema"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "properties.onelake_tables_path"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "properties.sql_endpoint_properties.provisioning_status"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "properties.sql_endpoint_properties.connection_string"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "properties.sql_endpoint_properties.id"), ), }, // read by display name - not found @@ -145,37 +155,6 @@ func TestUnit_MirroredDatabaseDataSource(t *testing.T) { ), ExpectError: regexp.MustCompile(common.ErrorReadHeader), }, - // // read by id with definition - missing required format configuration - // { - // Config: at.CompileConfig( - // testDataSourceMirroredDatabaseHeader, - // map[string]any{ - // "workspace_id": workspaceID, - // "id": *entity.ID, - // "output_definition": true, - // }, - // ), - // ExpectError: regexp.MustCompile("Invalid configuration for attribute format"), - // }, - // // read by id with definition - success - // { - // Config: at.CompileConfig( - // testDataSourceMirroredDatabaseHeader, - // map[string]any{ - // "workspace_id": workspaceID, - // "id": *entity.ID, - // "output_definition": true, - // "format": "Default", - // }, - // ), - // Check: resource.ComposeAggregateTestCheckFunc( - // resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "workspace_id", entity.WorkspaceID), - // resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "id", entity.ID), - // resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "display_name", entity.DisplayName), - // resource.TestCheckResourceAttrPtr(testDataSourceMirroredDatabaseFQN, "description", entity.Description), - // resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "definition.mirroring.json.content"), - // ), - // }, })) } @@ -203,6 +182,11 @@ func TestAcc_MirroredDatabaseDataSource(t *testing.T) { resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "id", entityID), resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "display_name", entityDisplayName), resource.TestCheckResourceAttr(testDataSourceMirroredDatabaseFQN, "description", entityDescription), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "properties.default_schema"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "properties.onelake_tables_path"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "properties.sql_endpoint_properties.provisioning_status"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "properties.sql_endpoint_properties.connection_string"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabaseFQN, "properties.sql_endpoint_properties.id"), ), }, // read by id - not found diff --git a/internal/services/mirroreddatabase/data_mirrored_databases_test.go b/internal/services/mirroreddatabase/data_mirrored_databases_test.go index 7168a8a4..1f9012fd 100644 --- a/internal/services/mirroreddatabase/data_mirrored_databases_test.go +++ b/internal/services/mirroreddatabase/data_mirrored_databases_test.go @@ -72,6 +72,8 @@ func TestUnit_MirroredDatabasesDataSource(t *testing.T) { resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabasesFQN, "values.1.properties.default_schema"), resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabasesFQN, "values.1.properties.onelake_tables_path"), resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabasesFQN, "values.1.properties.sql_endpoint_properties.connection_string"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabasesFQN, "values.1.properties.sql_endpoint_properties.id"), + resource.TestCheckResourceAttrSet(testDataSourceMirroredDatabasesFQN, "values.1.properties.sql_endpoint_properties.provisioning_status"), ), }, })) diff --git a/internal/services/mirroreddatabase/resource_mirrored_database.go b/internal/services/mirroreddatabase/resource_mirrored_database.go index b4fe2c8c..bb35f107 100644 --- a/internal/services/mirroreddatabase/resource_mirrored_database.go +++ b/internal/services/mirroreddatabase/resource_mirrored_database.go @@ -67,7 +67,7 @@ func NewResourceMirroredDatabase() resource.Resource { mapvalidator.SizeAtMost(1), mapvalidator.KeysAre(fabricitem.DefinitionPathKeysValidator(itemDefinitionFormats)...), }, - DefinitionRequired: false, + DefinitionRequired: true, DefinitionEmpty: ItemDefinitionEmpty, DefinitionFormats: itemDefinitionFormats, }, diff --git a/internal/services/mirroreddatabase/resource_mirrored_database_test.go b/internal/services/mirroreddatabase/resource_mirrored_database_test.go index 7501bf21..c80f8bbd 100644 --- a/internal/services/mirroreddatabase/resource_mirrored_database_test.go +++ b/internal/services/mirroreddatabase/resource_mirrored_database_test.go @@ -5,6 +5,7 @@ import ( "fmt" "regexp" "testing" + "time" at "github.com/dcarbone/terraform-plugin-framework-utils/v3/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -31,6 +32,12 @@ var testHelperDefinition = map[string]any{ }, } +// var testHelperDefinitionUpdate = map[string]any{ +// `"mirroring.json"`: map[string]any{ +// "source": "${local.path}/mirroring_update.json", +// }, +// } + func TestUnit_MirroredDatabaseResource_Attributes(t *testing.T) { resource.ParallelTest(t, testhelp.NewTestUnitCase(t, &testResourceItemFQN, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ // error - no attributes @@ -214,6 +221,21 @@ func TestUnit_MirroredDatabaseResource_CRUD(t *testing.T) { )), ExpectError: regexp.MustCompile(common.ErrorCreateHeader), }, + // error - no required attributes + { + ResourceName: testResourceItemFQN, + Config: at.JoinConfigs( + testHelperLocals, + at.CompileConfig( + testResourceItemHeader, + map[string]any{ + "workspace_id": "00000000-0000-0000-0000-000000000000", + "display_name": "test", + "format": "Default", + }, + )), + ExpectError: regexp.MustCompile(`The argument "definition" is required, but no definition was found.`), + }, // Create and Read with definition { ResourceName: testResourceItemFQN, @@ -236,27 +258,8 @@ func TestUnit_MirroredDatabaseResource_CRUD(t *testing.T) { resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.default_schema"), resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.onelake_tables_path"), resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.provisioning_status"), - ), - }, - // Create and Read without definition - { - ResourceName: testResourceItemFQN, - Config: at.CompileConfig( - testResourceItemHeader, - map[string]any{ - "workspace_id": *entityNoDefinition.WorkspaceID, - "display_name": *entityNoDefinition.DisplayName, - "description": *entityNoDefinition.Description, - }), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrPtr(testResourceItemFQN, "display_name", entityNoDefinition.DisplayName), - resource.TestCheckResourceAttrPtr(testResourceItemFQN, "description", entityNoDefinition.Description), - resource.TestCheckResourceAttr(testResourceItemFQN, "definition_update_enabled", "true"), - resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.default_schema"), - resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.onelake_tables_path"), - resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.provisioning_status"), - resource.TestCheckNoResourceAttr(testResourceItemFQN, "definition"), - resource.TestCheckNoResourceAttr(testResourceItemFQN, "format"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.connection_string"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.id"), ), }, // Update and Read @@ -281,9 +284,10 @@ func TestUnit_MirroredDatabaseResource_CRUD(t *testing.T) { resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.default_schema"), resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.onelake_tables_path"), resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.provisioning_status"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.connection_string"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.id"), ), }, - // Delete testing automatically occurs in TestCase })) } @@ -316,9 +320,17 @@ func TestAcc_MirroredDatabaseResource_CRUD(t *testing.T) { resource.TestCheckResourceAttr(testResourceItemFQN, "display_name", entityCreateDisplayName), resource.TestCheckResourceAttr(testResourceItemFQN, "description", entityCreateDescription), resource.TestCheckResourceAttr(testResourceItemFQN, "definition_update_enabled", "true"), + + // wait before update definition + func(s *terraform.State) error { + // Add a 30-second sleep between test steps in order to allow the Fabric service to complete the provisioning + t.Log("Sleeping for 30 seconds before the next step...") + time.Sleep(30 * time.Second) + return nil + }, ), }, - // Update definition and Read + // Update and Read { ResourceName: testResourceItemFQN, Config: at.JoinConfigs( @@ -336,8 +348,82 @@ func TestAcc_MirroredDatabaseResource_CRUD(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(testResourceItemFQN, "display_name", entityUpdateDisplayName), resource.TestCheckResourceAttr(testResourceItemFQN, "description", entityUpdateDescription), - resource.TestCheckResourceAttr(testResourceItemFQN, "definition_update_enabled", "true")), + resource.TestCheckResourceAttr(testResourceItemFQN, "definition_update_enabled", "true"), + resource.TestCheckResourceAttr(testResourceItemFQN, "properties.default_schema", "dbo"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.onelake_tables_path"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.provisioning_status"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.connection_string"), + resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.id"), + ), }, }, )) } + +// func TestAcc_MirroredDatabaseResource_UpdateDefinition(t *testing.T) { +// workspace := testhelp.WellKnown()["WorkspaceDS"].(map[string]any) +// workspaceID := workspace["id"].(string) + +// entityDisplayName := testhelp.RandomName() +// entityDescription := testhelp.RandomName() + +// resource.Test(t, testhelp.NewTestAccCase(t, &testResourceItemFQN, nil, []resource.TestStep{ +// // Create with initial definition and Read +// { +// ResourceName: testResourceItemFQN, +// Config: at.JoinConfigs( +// testHelperLocals, +// at.CompileConfig( +// testResourceItemHeader, +// map[string]any{ +// "workspace_id": workspaceID, +// "display_name": entityDisplayName, +// "description": entityDescription, +// "format": "Default", +// "definition": testHelperDefinition, +// }, +// )), +// Check: resource.ComposeAggregateTestCheckFunc( +// resource.TestCheckResourceAttr(testResourceItemFQN, "display_name", entityDisplayName), +// resource.TestCheckResourceAttr(testResourceItemFQN, "description", entityDescription), +// resource.TestCheckResourceAttr(testResourceItemFQN, "definition_update_enabled", "true"), +// resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.provisioning_status"), + +// // wait before updating definition +// func(s *terraform.State) error { +// // Add a 30-second sleep between test steps in order to allow the Fabric service to complete the provisioning +// t.Log("Sleeping for 30 seconds before updating the definition...") +// time.Sleep(30 * time.Second) +// return nil +// }, +// ), +// }, +// // Update definition and Read +// { +// ResourceName: testResourceItemFQN, +// Config: at.JoinConfigs( +// testHelperLocals, +// at.CompileConfig( +// testResourceItemHeader, +// map[string]any{ +// "workspace_id": workspaceID, +// "display_name": entityDisplayName, +// "description": entityDescription, +// "format": "Default", +// "definition": testHelperDefinitionUpdate, // updated definition +// "definition_update_enabled": true, +// }, +// )), +// Check: resource.ComposeAggregateTestCheckFunc( +// resource.TestCheckResourceAttr(testResourceItemFQN, "display_name", entityDisplayName), +// resource.TestCheckResourceAttr(testResourceItemFQN, "description", entityDescription), +// resource.TestCheckResourceAttr(testResourceItemFQN, "definition_update_enabled", "true"), +// resource.TestCheckResourceAttr(testResourceItemFQN, "properties.default_schema", "dbo"), +// resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.onelake_tables_path"), +// resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.provisioning_status"), +// resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.connection_string"), +// resource.TestCheckResourceAttrSet(testResourceItemFQN, "properties.sql_endpoint_properties.id"), +// ), +// }, +// })) +// } diff --git a/internal/testhelp/fixtures/mirrored_database/mirroring_update.json b/internal/testhelp/fixtures/mirrored_database/mirroring_update.json new file mode 100644 index 00000000..e5fbf6b5 --- /dev/null +++ b/internal/testhelp/fixtures/mirrored_database/mirroring_update.json @@ -0,0 +1,14 @@ +{ + "properties": { + "source": { + "type": "GenericMirror" + }, + "target": { + "type": "MountedRelationalDatabase", + "typeProperties": { + "format": "Delta", + "defaultSchema": "updated_schema" + } + } + } +}