Alpha (#4)
* chore: initial push to be able to work together * chore: add missing wait folder * chore: add missing folders * chore: cleanup alpha branch * feat: mssql alpha instance (#2) * fix: remove unused attribute types and functions from backup models * fix: update API client references to use sqlserverflexalpha package * fix: update package references to use sqlserverflexalpha and modify user data source model * fix: add sqlserverflexalpha user data source to provider * fix: add sqlserverflexalpha user resource and update related functionality * chore: add stackit_sqlserverflexalpha_user resource and instance_id variable * fix: refactor sqlserverflexalpha user resource and enhance schema with status and default_database --------- Co-authored-by: Andre Harms <andre.harms@stackit.cloud> Co-authored-by: Marcel S. Henselin <marcel.henselin@stackit.cloud> * feat: add sqlserver instance * chore: fixing tests * chore: update docs --------- Co-authored-by: Marcel S. Henselin <marcel.henselin@stackit.cloud> Co-authored-by: Andre Harms <andre.harms@stackit.cloud>
This commit is contained in:
parent
45073a716b
commit
2733834fc9
351 changed files with 62744 additions and 3 deletions
614
stackit/internal/utils/utils_test.go
Normal file
614
stackit/internal/utils/utils_test.go
Normal file
|
|
@ -0,0 +1,614 @@
|
|||
// Copyright (c) STACKIT
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-go/tftypes"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
)
|
||||
|
||||
func TestReconcileStrLists(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
list1 []string
|
||||
list2 []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
"empty lists",
|
||||
[]string{},
|
||||
[]string{},
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
"list1 empty",
|
||||
[]string{},
|
||||
[]string{"a", "b", "c"},
|
||||
[]string{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
"list2 empty",
|
||||
[]string{"a", "b", "c"},
|
||||
[]string{},
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
"no common elements",
|
||||
[]string{"a", "b", "c"},
|
||||
[]string{"d", "e", "f"},
|
||||
[]string{"d", "e", "f"},
|
||||
},
|
||||
{
|
||||
"common elements",
|
||||
[]string{"d", "a", "c"},
|
||||
[]string{"b", "c", "d", "e"},
|
||||
[]string{"d", "c", "b", "e"},
|
||||
},
|
||||
{
|
||||
"common elements with empty string",
|
||||
[]string{"d", "", "c"},
|
||||
[]string{"", "c", "d"},
|
||||
[]string{"d", "", "c"},
|
||||
},
|
||||
{
|
||||
"common elements with duplicates",
|
||||
[]string{"a", "b", "c", "c"},
|
||||
[]string{"b", "c", "d", "e"},
|
||||
[]string{"b", "c", "c", "d", "e"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
output := ReconcileStringSlices(tt.list1, tt.list2)
|
||||
diff := cmp.Diff(output, tt.expected)
|
||||
if diff != "" {
|
||||
t.Fatalf("Data does not match: %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListValuetoStrSlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input basetypes.ListValue
|
||||
expected []string
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
description: "empty list",
|
||||
input: types.ListValueMust(types.StringType, []attr.Value{}),
|
||||
expected: []string{},
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
description: "values ok",
|
||||
input: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("a"),
|
||||
types.StringValue("b"),
|
||||
types.StringValue("c"),
|
||||
}),
|
||||
expected: []string{"a", "b", "c"},
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
description: "different type",
|
||||
input: types.ListValueMust(types.Int64Type, []attr.Value{
|
||||
types.Int64Value(12),
|
||||
}),
|
||||
isValid: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
output, err := ListValuetoStringSlice(tt.input)
|
||||
if err != nil {
|
||||
if !tt.isValid {
|
||||
return
|
||||
}
|
||||
t.Fatalf("Should not have failed: %v", err)
|
||||
}
|
||||
if !tt.isValid {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
diff := cmp.Diff(output, tt.expected)
|
||||
if diff != "" {
|
||||
t.Fatalf("Data does not match: %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertPointerSliceToStringSlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input []*string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
description: "nil slice",
|
||||
input: nil,
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
description: "empty slice",
|
||||
input: []*string{},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
description: "slice with valid pointers",
|
||||
input: []*string{utils.Ptr("apple"), utils.Ptr("banana"), utils.Ptr("cherry")},
|
||||
expected: []string{"apple", "banana", "cherry"},
|
||||
},
|
||||
{
|
||||
description: "slice with some nil pointers",
|
||||
input: []*string{utils.Ptr("apple"), nil, utils.Ptr("cherry"), nil},
|
||||
expected: []string{"apple", "cherry"},
|
||||
},
|
||||
{
|
||||
description: "slice with all nil pointers",
|
||||
input: []*string{nil, nil, nil},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
description: "slice with a pointer to an empty string",
|
||||
input: []*string{utils.Ptr("apple"), utils.Ptr(""), utils.Ptr("cherry")},
|
||||
expected: []string{"apple", "", "cherry"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
output := ConvertPointerSliceToStringSlice(tt.input)
|
||||
diff := cmp.Diff(output, tt.expected)
|
||||
if diff != "" {
|
||||
t.Fatalf("Data does not match: %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimplifyBackupSchedule(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"simple schedule",
|
||||
"0 0 * * *",
|
||||
"0 0 * * *",
|
||||
},
|
||||
{
|
||||
"schedule with leading zeros",
|
||||
"00 00 * * *",
|
||||
"0 0 * * *",
|
||||
},
|
||||
{
|
||||
"schedule with leading zeros 2",
|
||||
"00 001 * * *",
|
||||
"0 1 * * *",
|
||||
},
|
||||
{
|
||||
"schedule with leading zeros 3",
|
||||
"00 0010 * * *",
|
||||
"0 10 * * *",
|
||||
},
|
||||
{
|
||||
"simple schedule with slash",
|
||||
"0 0/6 * * *",
|
||||
"0 0/6 * * *",
|
||||
},
|
||||
{
|
||||
"schedule with leading zeros and slash",
|
||||
"00 00/6 * * *",
|
||||
"0 0/6 * * *",
|
||||
},
|
||||
{
|
||||
"schedule with leading zeros and slash 2",
|
||||
"00 001/06 * * *",
|
||||
"0 1/6 * * *",
|
||||
},
|
||||
{
|
||||
"simple schedule with comma",
|
||||
"0 10,15 * * *",
|
||||
"0 10,15 * * *",
|
||||
},
|
||||
{
|
||||
"schedule with leading zeros and comma",
|
||||
"0 010,0015 * * *",
|
||||
"0 10,15 * * *",
|
||||
},
|
||||
{
|
||||
"simple schedule with comma and slash",
|
||||
"0 0-11/10 * * *",
|
||||
"0 0-11/10 * * *",
|
||||
},
|
||||
{
|
||||
"schedule with leading zeros, comma, and slash",
|
||||
"00 000-011/010 * * *",
|
||||
"0 0-11/10 * * *",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
output := SimplifyBackupSchedule(tt.input)
|
||||
if output != tt.expected {
|
||||
t.Fatalf("Data does not match: %s", output)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsLegacyProjectRole(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
role string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
"non legacy role",
|
||||
"owner",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"leagcy role",
|
||||
"project.owner",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"leagcy role 2",
|
||||
"project.admin",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"leagcy role 3",
|
||||
"project.member",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"leagcy role 4",
|
||||
"project.auditor",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
output := IsLegacyProjectRole(tt.role)
|
||||
if output != tt.expected {
|
||||
t.Fatalf("Data does not match: %v", output)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatPossibleValues(t *testing.T) {
|
||||
gotPrefix := "Possible values are:"
|
||||
|
||||
type args struct {
|
||||
values []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "single string value",
|
||||
args: args{
|
||||
values: []string{"foo"},
|
||||
},
|
||||
want: fmt.Sprintf("%s `foo`.", gotPrefix),
|
||||
},
|
||||
{
|
||||
name: "multiple string value",
|
||||
args: args{
|
||||
values: []string{"foo", "bar", "trololol"},
|
||||
},
|
||||
want: fmt.Sprintf("%s `foo`, `bar`, `trololol`.", gotPrefix),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := FormatPossibleValues(tt.args.values...); got != tt.want {
|
||||
t.Errorf("FormatPossibleValues() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUndefined(t *testing.T) {
|
||||
type args struct {
|
||||
val value
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "undefined value",
|
||||
args: args{
|
||||
val: types.StringNull(),
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "unknown value",
|
||||
args: args{
|
||||
val: types.StringUnknown(),
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "string value",
|
||||
args: args{
|
||||
val: types.StringValue(""),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := IsUndefined(tt.args.val); got != tt.want {
|
||||
t.Errorf("IsUndefined() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildInternalTerraformId(t *testing.T) {
|
||||
type args struct {
|
||||
idParts []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want types.String
|
||||
}{
|
||||
{
|
||||
name: "no id parts",
|
||||
args: args{
|
||||
idParts: []string{},
|
||||
},
|
||||
want: types.StringValue(""),
|
||||
},
|
||||
{
|
||||
name: "multiple id parts",
|
||||
args: args{
|
||||
idParts: []string{"abc", "foo", "bar", "xyz"},
|
||||
},
|
||||
want: types.StringValue("abc,foo,bar,xyz"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := BuildInternalTerraformId(tt.args.idParts...); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("BuildInternalTerraformId() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckListRemoval(t *testing.T) {
|
||||
type model struct {
|
||||
AllowedAddresses types.List `tfsdk:"allowed_addresses"`
|
||||
}
|
||||
tests := []struct {
|
||||
description string
|
||||
configModelList types.List
|
||||
planModelList types.List
|
||||
path path.Path
|
||||
listType attr.Type
|
||||
createEmptyList bool
|
||||
expectedAdjustedResp bool
|
||||
}{
|
||||
{
|
||||
"config and plan are the same - no change",
|
||||
types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("value1"),
|
||||
}),
|
||||
types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("value1"),
|
||||
}),
|
||||
path.Root("allowed_addresses"),
|
||||
types.StringType,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"list was removed from config",
|
||||
types.ListNull(types.StringType),
|
||||
types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("value1"),
|
||||
}),
|
||||
path.Root("allowed_addresses"),
|
||||
types.StringType,
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"list was added to config",
|
||||
types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("value1"),
|
||||
}),
|
||||
types.ListNull(types.StringType),
|
||||
path.Root("allowed_addresses"),
|
||||
types.StringType,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"no list provided at all",
|
||||
types.ListNull(types.StringType),
|
||||
types.ListNull(types.StringType),
|
||||
path.Root("allowed_addresses"),
|
||||
types.StringType,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"create empty list test - list was removed from config",
|
||||
types.ListNull(types.StringType),
|
||||
types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("value1"),
|
||||
}),
|
||||
path.Root("allowed_addresses"),
|
||||
types.StringType,
|
||||
true,
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
// create resp
|
||||
plan := tfsdk.Plan{
|
||||
Schema: schema.Schema{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"allowed_addresses": schema.ListAttribute{
|
||||
ElementType: basetypes.StringType{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// set input planModelList to plan
|
||||
if diags := plan.Set(context.Background(), model{tt.planModelList}); diags.HasError() {
|
||||
t.Fatalf("cannot create test model: %v", diags)
|
||||
}
|
||||
resp := resource.ModifyPlanResponse{
|
||||
Plan: plan,
|
||||
}
|
||||
|
||||
CheckListRemoval(context.Background(), tt.configModelList, tt.planModelList, tt.path, tt.listType, tt.createEmptyList, &resp)
|
||||
// check targetList
|
||||
var respList types.List
|
||||
resp.Plan.GetAttribute(context.Background(), tt.path, &respList)
|
||||
|
||||
if tt.createEmptyList {
|
||||
emptyList, _ := types.ListValueFrom(context.Background(), tt.listType, []string{})
|
||||
diffEmptyList := cmp.Diff(emptyList, respList)
|
||||
if diffEmptyList != "" {
|
||||
t.Fatalf("an empty list should have been created but was not: %s", diffEmptyList)
|
||||
}
|
||||
}
|
||||
|
||||
// compare planModelList and resp list
|
||||
diff := cmp.Diff(tt.planModelList, respList)
|
||||
if tt.expectedAdjustedResp {
|
||||
if diff == "" {
|
||||
t.Fatalf("plan should be adjusted but was not")
|
||||
}
|
||||
} else {
|
||||
if diff != "" {
|
||||
t.Fatalf("plan should not be adjusted but diff is: %s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetAndLogStateFields(t *testing.T) {
|
||||
testSchema := schema.Schema{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"project_id": schema.StringAttribute{},
|
||||
"instance_id": schema.StringAttribute{},
|
||||
},
|
||||
}
|
||||
|
||||
type args struct {
|
||||
diags *diag.Diagnostics
|
||||
state *tfsdk.State
|
||||
values map[string]interface{}
|
||||
}
|
||||
type want struct {
|
||||
hasError bool
|
||||
state *tfsdk.State
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "empty map",
|
||||
args: args{
|
||||
diags: &diag.Diagnostics{},
|
||||
state: &tfsdk.State{},
|
||||
values: map[string]interface{}{},
|
||||
},
|
||||
want: want{
|
||||
hasError: false,
|
||||
state: &tfsdk.State{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "base",
|
||||
args: args{
|
||||
diags: &diag.Diagnostics{},
|
||||
state: func() *tfsdk.State {
|
||||
ctx := context.Background()
|
||||
state := tfsdk.State{
|
||||
Raw: tftypes.NewValue(testSchema.Type().TerraformType(ctx), map[string]tftypes.Value{
|
||||
"project_id": tftypes.NewValue(tftypes.String, "9b15d120-86f8-45f5-81d8-a554f09c7582"),
|
||||
"instance_id": tftypes.NewValue(tftypes.String, nil),
|
||||
}),
|
||||
Schema: testSchema,
|
||||
}
|
||||
return &state
|
||||
}(),
|
||||
values: map[string]interface{}{
|
||||
"project_id": "a414f971-3f7a-4e9a-8671-51a8acb7bcc8",
|
||||
"instance_id": "97073250-8cad-46c3-8424-6258ac0b3731",
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
hasError: false,
|
||||
state: func() *tfsdk.State {
|
||||
ctx := context.Background()
|
||||
state := tfsdk.State{
|
||||
Raw: tftypes.NewValue(testSchema.Type().TerraformType(ctx), map[string]tftypes.Value{
|
||||
"project_id": tftypes.NewValue(tftypes.String, nil),
|
||||
"instance_id": tftypes.NewValue(tftypes.String, nil),
|
||||
}),
|
||||
Schema: testSchema,
|
||||
}
|
||||
state.SetAttribute(ctx, path.Root("project_id"), "a414f971-3f7a-4e9a-8671-51a8acb7bcc8")
|
||||
state.SetAttribute(ctx, path.Root("instance_id"), "97073250-8cad-46c3-8424-6258ac0b3731")
|
||||
return &state
|
||||
}(),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
SetAndLogStateFields(ctx, tt.args.diags, tt.args.state, tt.args.values)
|
||||
|
||||
if tt.args.diags.HasError() != tt.want.hasError {
|
||||
t.Errorf("TestSetAndLogStateFields() error count = %v, hasErr %v", tt.args.diags.ErrorsCount(), tt.want.hasError)
|
||||
}
|
||||
|
||||
diff := cmp.Diff(tt.args.state, tt.want.state)
|
||||
if diff != "" {
|
||||
t.Fatalf("Data does not match: %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue