Skip to content

Commit a9e119d

Browse files
authored
test: Supports testing PlanModifier with unit.MockPlanChecksAndRun aka MipT - Mocked import+plan Tests (#3190)
* test: Support testing PlanModifier * fix broken unit test * Address PR comments * docs: Update testing best practices to include MipT for testing PlanModifier logic * update docs based on suggestions * pr suggestions * test: Remove test files after usage * refactor: Use unit.PlanCheckTest instead of map[string]*args for test cases and rename SideEffect to RunBeforeEach
1 parent 93e76c4 commit a9e119d

26 files changed

+1479
-66
lines changed

.github/workflows/code-health.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ jobs:
3535
go-version-file: 'go.mod'
3636
- name: Unit Test
3737
run: make test
38+
env:
39+
MONGODB_ATLAS_PREVIEW_PROVIDER_V2_ADVANCED_CLUSTER: "true"
3840
lint:
3941
runs-on: ubuntu-latest
4042
permissions: {}

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ clean-atlas-org: ## Run a test to clean all projects and pending resources in an
4040

4141
.PHONY: test
4242
test: fmtcheck ## Run unit tests
43-
go test ./... -timeout=30s -parallel=4 -race
43+
go test ./... -timeout=60s -parallel=$(PARALLEL_GO_TEST) -race
4444

4545
.PHONY: testmact
4646
testmact: ## Run MacT tests (mocked acc tests)
@@ -118,8 +118,8 @@ docs: ## Give URL to test Terraform documentation
118118
@echo "Use this site to preview markdown rendering: https://registry.terraform.io/tools/doc-preview"
119119

120120
.PHONY: tflint
121-
tflint: fmtcheck ## Linter for Terraform files
122-
tflint -f compact --recursive --minimum-failure-severity=warning
121+
tflint: fmtcheck ## Linter for Terraform files in examples/ dir (avoid `internal/**/testdata/main*.tf`)
122+
tflint --chdir=examples/ -f compact --recursive --minimum-failure-severity=warning
123123

124124
.PHONY: tf-validate
125125
tf-validate: fmtcheck ## Validate Terraform files

contributing/testing-best-practices.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
- All resource folders must have a `main_test.go` file to handle resource reuse lifecycle, e.g. [here](https://github.com/mongodb/terraform-provider-mongodbatlas/blob/f3ff5bb678c1b07c16cc467471f483e483565427/internal/service/advancedcluster/main_test.go).
1717
- `internal/testutil/acc` contains helper test functions for Acceptance tests.
1818
- `internal/testutil/mig` contains helper test functions specifically for Migration tests.
19-
- `internal/testutil/unit` contains helper test functions for [MacT (Mocked Acceptance Tests)](#mact---mocked-acceptance-tests). MacT is used to capture and replay HTTP traffic with MongoDB Atlas and allow diff assertions on requests.
19+
- `internal/testutil/unit` contains helper test functions for [MacT (Mocked Acceptance Tests)](#mact---mocked-acceptance-tests) and [MipT- (Mocked Import Plan Tests)](#mipt---mocked-importplan-tests). MacT is used to capture and replay HTTP traffic with MongoDB Atlas and allow diff assertions on requests. MipT is used to test PlanModifier logic.
2020

2121
## Unit tests
2222

@@ -131,3 +131,25 @@ It is advised to only run a **single** test at a time when a plural data source
131131
- What is `duplicate_responses`?
132132
- A counter increasd for every response that is the same as the previous response.
133133
- Not used during replay.
134+
135+
## MipT - Mocked Import+Plan Tests
136+
**Experimental** framework for testing PlanModifier logic. It creates a test case with two steps and mocks the HTTP request/responses:
137+
1. Import state with a fixed `.tf` file
138+
2. Run terraform plan with an updated `*.tf` file and perform plan checks, for example check for known/unknown values in the plan. The actual update is not performed. See [Hashicorp docs](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing@v1.12.0/plancheck#PlanCheck) for plan check options.
139+
140+
For a full example see [plan_modifier_test.go](../internal/service/advancedclustertpf/plan_modifier_test.go).
141+
142+
### File generation
143+
For a full example of generation see [`http_mocker_plan_checks_test.go`](../internal/testutil/unit/http_mocker_plan_checks_test.go)
144+
145+
1. Stores the last `GET` response from an existing [MacT](#mact---mocked-acceptance-tests) test case step. For example the last GET of `/api/atlas/v2/groups/{groupId}/clusters/{clusterName}`
146+
1. ImportName: `ClusterTwoRepSpecsWithAutoScalingAndSpecs`
147+
2. GET responses are stored in `testdata/{ImportName}/import_*.json`
148+
2. The Terraform configuration is:
149+
1. Import step is always the same to ensure the config matches the response from (1). Stored in `testdata/{ImportName}/main.tf`
150+
2. Plan config is different per test. During planning all `GET` responses are as before (1) since API shouldn't have any changes. Stored in `testdata/{ImportName}/main_{plan_step_name}.tf`
151+
### Maintenance and tips
152+
- `plan_step_name` is meant to be created manually (usually by copy-pasting `main.tf` and making changes)
153+
- Use `testCases := map[string][]plancheck.PlanCheck{}` to test many different plan configs for the same import
154+
155+

internal/service/advancedcluster/resource_advanced_cluster_test.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,9 @@ const (
6161
var (
6262
configServerManagementModeFixedToDedicated = "FIXED_TO_DEDICATED"
6363
configServerManagementModeAtlasManaged = "ATLAS_MANAGED"
64-
mockConfig = unit.MockHTTPDataConfig{AllowMissingRequests: true, SideEffect: shortenRetries, IsDiffMustSubstrings: []string{"/clusters"}, QueryVars: []string{"providerName"}}
64+
mockConfig = unit.MockConfigAdvancedClusterTPF
6565
)
6666

67-
func shortenRetries() error {
68-
advancedclustertpf.RetryMinTimeout = 100 * time.Millisecond
69-
advancedclustertpf.RetryDelay = 100 * time.Millisecond
70-
advancedclustertpf.RetryPollInterval = 100 * time.Millisecond
71-
return nil
72-
}
73-
7467
func TestGetReplicationSpecAttributesFromOldAPI(t *testing.T) {
7568
var (
7669
projectID = "11111"

internal/service/advancedcluster/testdata/TestAccMockableAdvancedCluster_removeBlocksFromConfig.yaml

Lines changed: 312 additions & 0 deletions
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"labels": [],
3+
"tags": []
4+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package advancedclustertpf_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
7+
"github.com/hashicorp/terraform-plugin-testing/plancheck"
8+
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
9+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/unit"
10+
)
11+
12+
var (
13+
repSpec0 = tfjsonpath.New("replication_specs").AtSliceIndex(0)
14+
repSpec1 = tfjsonpath.New("replication_specs").AtSliceIndex(1)
15+
regionConfig0 = repSpec0.AtMapKey("region_configs").AtSliceIndex(0)
16+
regionConfig1 = repSpec1.AtMapKey("region_configs").AtSliceIndex(0)
17+
mockConfig = unit.MockConfigAdvancedClusterTPF
18+
)
19+
20+
func autoScalingKnownValue(computeEnabled, diskEnabled, scaleDown bool, minInstanceSize, maxInstanceSize string) knownvalue.Check {
21+
return knownvalue.ObjectExact(map[string]knownvalue.Check{
22+
"compute_enabled": knownvalue.Bool(computeEnabled),
23+
"disk_gb_enabled": knownvalue.Bool(diskEnabled),
24+
"compute_scale_down_enabled": knownvalue.Bool(scaleDown),
25+
"compute_min_instance_size": knownvalue.StringExact(minInstanceSize),
26+
"compute_max_instance_size": knownvalue.StringExact(maxInstanceSize),
27+
})
28+
}
29+
30+
func TestPlanChecksClusterTwoRepSpecsWithAutoScalingAndSpecs(t *testing.T) {
31+
var (
32+
baseConfig = unit.NewMockPlanChecksConfig(t, &mockConfig, unit.ImportNameClusterTwoRepSpecsWithAutoScalingAndSpecs)
33+
resourceName = baseConfig.ResourceName
34+
autoScalingEnabled = autoScalingKnownValue(true, true, true, "M10", "M30")
35+
testCases = []unit.PlanCheckTest{
36+
{
37+
ConfigFilename: "main_removed_blocks_from_config_no_plan_changes.tf",
38+
Checks: []plancheck.PlanCheck{
39+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop),
40+
},
41+
},
42+
{
43+
ConfigFilename: "main_removed_blocks_from_config_and_instance_change.tf",
44+
Checks: []plancheck.PlanCheck{
45+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate),
46+
plancheck.ExpectKnownValue(resourceName, regionConfig0.AtMapKey("read_only_specs").AtMapKey("instance_size"), knownvalue.StringExact("M10")),
47+
plancheck.ExpectKnownValue(resourceName, regionConfig1.AtMapKey("read_only_specs").AtMapKey("instance_size"), knownvalue.StringExact("M20")),
48+
plancheck.ExpectKnownValue(resourceName, regionConfig0.AtMapKey("auto_scaling"), autoScalingEnabled),
49+
plancheck.ExpectKnownValue(resourceName, regionConfig0.AtMapKey("analytics_auto_scaling"), autoScalingEnabled),
50+
plancheck.ExpectKnownValue(resourceName, regionConfig1.AtMapKey("auto_scaling"), autoScalingEnabled),
51+
plancheck.ExpectKnownValue(resourceName, regionConfig1.AtMapKey("analytics_auto_scaling"), autoScalingEnabled),
52+
plancheck.ExpectUnknownValue(resourceName, regionConfig0.AtMapKey("analytics_specs")), // analytics specs was defined in region_configs.0 but not in region_configs.1
53+
plancheck.ExpectKnownValue(resourceName, regionConfig1.AtMapKey("analytics_specs"), knownvalue.NotNull()),
54+
plancheck.ExpectUnknownValue(resourceName, repSpec0.AtMapKey("id")),
55+
plancheck.ExpectUnknownValue(resourceName, repSpec1.AtMapKey("id")),
56+
},
57+
},
58+
}
59+
)
60+
for _, testCase := range testCases {
61+
t.Run(testCase.ConfigFilename, func(t *testing.T) {
62+
unit.MockPlanChecksAndRun(t, baseConfig.WithPlanCheckTest(testCase))
63+
})
64+
}
65+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
variables:
2+
clusterName: mocked-cluster
3+
groupId: "111111111111111111111111"
4+
steps:
5+
- config: |-
6+
resource "mongodbatlas_advanced_cluster" "test" {
7+
project_id = "111111111111111111111111"
8+
name = "mocked-cluster"
9+
cluster_type = "GEOSHARDED"
10+
11+
12+
replication_specs = [{
13+
region_configs = [{
14+
analytics_auto_scaling = {
15+
compute_enabled = true
16+
compute_max_instance_size = "M30"
17+
compute_min_instance_size = "M10"
18+
compute_scale_down_enabled = true
19+
disk_gb_enabled = true
20+
}
21+
auto_scaling = {
22+
compute_enabled = true
23+
compute_max_instance_size = "M30"
24+
compute_min_instance_size = "M10"
25+
compute_scale_down_enabled = true
26+
disk_gb_enabled = true
27+
}
28+
electable_specs = {
29+
instance_size = "M10"
30+
node_count = 5
31+
}
32+
priority = 7
33+
provider_name = "AWS"
34+
read_only_specs = {
35+
instance_size = "M10"
36+
node_count = 2
37+
}
38+
region_name = "US_EAST_1"
39+
}]
40+
zone_name = "Zone 1"
41+
}, {
42+
region_configs = [{
43+
analytics_auto_scaling = {
44+
compute_enabled = true
45+
compute_max_instance_size = "M30"
46+
compute_min_instance_size = "M10"
47+
compute_scale_down_enabled = true
48+
disk_gb_enabled = true
49+
}
50+
analytics_specs = {
51+
instance_size = "M10"
52+
node_count = 4
53+
}
54+
auto_scaling = {
55+
compute_enabled = true
56+
compute_max_instance_size = "M30"
57+
compute_min_instance_size = "M10"
58+
compute_scale_down_enabled = true
59+
disk_gb_enabled = true
60+
}
61+
electable_specs = {
62+
instance_size = "M10"
63+
node_count = 3
64+
}
65+
priority = 7
66+
provider_name = "AWS"
67+
read_only_specs = {
68+
instance_size = "M10"
69+
node_count = 1
70+
}
71+
region_name = "US_WEST_2"
72+
}]
73+
zone_name = "Zone 2"
74+
}]
75+
}
76+
diff_requests: []
77+
request_responses:
78+
- path: /api/atlas/v2/groups/{groupId}/clusters/{clusterName}
79+
method: GET
80+
version: '2024-08-05'
81+
text: ""
82+
responses:
83+
- response_index: 0
84+
status: 200
85+
text: "import_GET__api_atlas_v2_groups_{groupId}_clusters_{clusterName}_2024-08-05.json"
86+
- path: /api/atlas/v2/groups/{groupId}/clusters/{clusterName}
87+
method: GET
88+
version: '2023-02-01'
89+
text: ""
90+
responses:
91+
- response_index: 0
92+
status: 200
93+
text: "import_GET__api_atlas_v2_groups_{groupId}_clusters_{clusterName}_2023-02-01.json"
94+
- path: /api/atlas/v2/groups/{groupId}/containers?providerName=AWS
95+
method: GET
96+
version: '2023-01-01'
97+
text: ""
98+
responses:
99+
- response_index: 0
100+
status: 200
101+
text: "import_GET__api_atlas_v2_groups_{groupId}_containers?providerName=AWS_2023-01-01.json"
102+
- path: /api/atlas/v2/groups/{groupId}/clusters/{clusterName}/processArgs
103+
method: GET
104+
version: '2023-01-01'
105+
text: ""
106+
responses:
107+
- response_index: 0
108+
status: 200
109+
text: "import_GET__api_atlas_v2_groups_{groupId}_clusters_{clusterName}_processArgs_2023-01-01.json"
110+
- path: /api/atlas/v2/groups/{groupId}/clusters/{clusterName}/processArgs
111+
method: GET
112+
version: '2024-08-05'
113+
text: ""
114+
responses:
115+
- response_index: 0
116+
status: 200
117+
text: "import_GET__api_atlas_v2_groups_{groupId}_clusters_{clusterName}_processArgs_2024-08-05.json"

0 commit comments

Comments
 (0)