Skip to content

Commit df54519

Browse files
committed
extend test cases for import block tests
1 parent e3f2ff3 commit df54519

File tree

1 file changed

+355
-0
lines changed

1 file changed

+355
-0
lines changed

helper/resource/testing_new_import_block_id_test.go

Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
package resource
55

66
import (
7+
"context"
8+
"fmt"
9+
"github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/datasource"
10+
"github.com/hashicorp/terraform-plugin-testing/terraform"
711
"testing"
812

913
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
@@ -102,3 +106,354 @@ func TestTest_TestStep_ImportBlockId(t *testing.T) {
102106
},
103107
})
104108
}
109+
110+
func TestTest_TestStep_ImportBlockId_SkipDataSourceState(t *testing.T) {
111+
t.Parallel()
112+
113+
UnitTest(t, TestCase{
114+
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
115+
tfversion.SkipBelow(tfversion.Version1_5_0), // ProtoV6ProviderFactories
116+
},
117+
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
118+
"examplecloud": providerserver.NewProviderServer(testprovider.Provider{
119+
DataSources: map[string]testprovider.DataSource{
120+
"examplecloud_thing": {
121+
ReadResponse: &datasource.ReadResponse{
122+
State: tftypes.NewValue(
123+
tftypes.Object{
124+
AttributeTypes: map[string]tftypes.Type{
125+
"id": tftypes.String,
126+
},
127+
},
128+
map[string]tftypes.Value{
129+
"id": tftypes.NewValue(tftypes.String, "datasource-test"),
130+
},
131+
),
132+
},
133+
SchemaResponse: &datasource.SchemaResponse{
134+
Schema: &tfprotov6.Schema{
135+
Block: &tfprotov6.SchemaBlock{
136+
Attributes: []*tfprotov6.SchemaAttribute{
137+
{
138+
Name: "id",
139+
Type: tftypes.String,
140+
Computed: true,
141+
},
142+
},
143+
},
144+
},
145+
},
146+
},
147+
},
148+
Resources: map[string]testprovider.Resource{
149+
"examplecloud_thing": {
150+
CreateResponse: &resource.CreateResponse{
151+
NewState: tftypes.NewValue(
152+
tftypes.Object{
153+
AttributeTypes: map[string]tftypes.Type{
154+
"id": tftypes.String,
155+
},
156+
},
157+
map[string]tftypes.Value{
158+
"id": tftypes.NewValue(tftypes.String, "resource-test"),
159+
},
160+
),
161+
},
162+
ImportStateResponse: &resource.ImportStateResponse{
163+
State: tftypes.NewValue(
164+
tftypes.Object{
165+
AttributeTypes: map[string]tftypes.Type{
166+
"id": tftypes.String,
167+
},
168+
},
169+
map[string]tftypes.Value{
170+
"id": tftypes.NewValue(tftypes.String, "resource-test"),
171+
},
172+
),
173+
},
174+
SchemaResponse: &resource.SchemaResponse{
175+
Schema: &tfprotov6.Schema{
176+
Block: &tfprotov6.SchemaBlock{
177+
Attributes: []*tfprotov6.SchemaAttribute{
178+
{
179+
Name: "id",
180+
Type: tftypes.String,
181+
Computed: true,
182+
},
183+
},
184+
},
185+
},
186+
},
187+
},
188+
},
189+
}),
190+
},
191+
Steps: []TestStep{
192+
{
193+
Config: `
194+
data "examplecloud_thing" "test" {}
195+
resource "examplecloud_thing" "test" {}
196+
`,
197+
},
198+
{
199+
ResourceName: "examplecloud_thing.test",
200+
ImportState: true,
201+
ImportStateKind: ImportBlockWithId,
202+
ImportStateCheck: func(is []*terraform.InstanceState) error {
203+
if len(is) > 1 {
204+
return fmt.Errorf("expected 1 state, got: %d", len(is))
205+
}
206+
207+
return nil
208+
},
209+
},
210+
},
211+
})
212+
}
213+
214+
func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore_Real_Example(t *testing.T) {
215+
/*
216+
This test tries to imitate a real world example of behaviour we often see in the AzureRM provider which requires
217+
the use of `ImportStateVerifyIgnore` when testing the import of a resource.
218+
219+
A sensitive field e.g. a password can be supplied on create but isn't returned in the API response on a subsequent
220+
read, resulting in a different value for password in the two states.
221+
222+
In the AzureRM provider this is usually handled one of two ways, both requiring `ImportStateVerifyIgnore` to make
223+
the test pass:
224+
225+
1. Property doesn't get set in the read
226+
* in pluginSDK at create the config gets written to state because that's what we're expecting
227+
* the subsequent read updates the values to create a post-apply diff and update computed values
228+
* since we don't do anything to the property in the read the imported resource's state has the password missing
229+
compared to the created resource's state
230+
231+
2. We retrieve the value from config and set that into state
232+
* the config isn't available at import time using only the import command (I think?) so there is nothing to
233+
retrieve and set into state when importing
234+
235+
For this test to pass I needed to add a `PlanChangeFunc` to the resource to set the id to a known value in the plan - see comment in the `PlanChangeFunc`
236+
237+
I also need to omit the `password` in the import config, otherwise the value in the config is used when importing the resource and the test
238+
ends up passing regardless of whether `ImportStateVerifyIgnore` has been specified or not
239+
240+
Ultimately it looks like:
241+
* Terraform is saying there's a bug in the provider? (see comment in `PlanChangeFunc`)
242+
* The import behaviour using a block vs. the command appears to differ
243+
*/
244+
t.Parallel()
245+
246+
UnitTest(t, TestCase{
247+
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
248+
tfversion.SkipBelow(tfversion.Version1_5_0), // ProtoV6ProviderFactories
249+
},
250+
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
251+
"examplecloud": providerserver.NewProviderServer(testprovider.Provider{
252+
Resources: map[string]testprovider.Resource{
253+
"examplecloud_container": {
254+
CreateResponse: &resource.CreateResponse{
255+
NewState: tftypes.NewValue(
256+
tftypes.Object{
257+
AttributeTypes: map[string]tftypes.Type{
258+
"id": tftypes.String,
259+
"name": tftypes.String,
260+
"password": tftypes.String,
261+
},
262+
},
263+
map[string]tftypes.Value{
264+
"id": tftypes.NewValue(tftypes.String, "sometestid"),
265+
"name": tftypes.NewValue(tftypes.String, "somename"),
266+
"password": tftypes.NewValue(tftypes.String, "somevalue"),
267+
},
268+
),
269+
},
270+
ImportStateResponse: &resource.ImportStateResponse{
271+
State: tftypes.NewValue(
272+
tftypes.Object{
273+
AttributeTypes: map[string]tftypes.Type{
274+
"id": tftypes.String,
275+
"name": tftypes.String,
276+
"password": tftypes.String,
277+
},
278+
},
279+
map[string]tftypes.Value{
280+
"id": tftypes.NewValue(tftypes.String, "sometestid"),
281+
"name": tftypes.NewValue(tftypes.String, "somename"),
282+
"password": tftypes.NewValue(tftypes.String, nil), // this simulates an absent property when importing
283+
},
284+
),
285+
},
286+
PlanChangeFunc: func(ctx context.Context, request resource.PlanChangeRequest, response *resource.PlanChangeResponse) {
287+
/*
288+
Returning a nil for another attribute to simulate a situation where `ImportStateVerifyIgnore`
289+
should be used results in the error below from Terraform
290+
291+
Error: Provider returned invalid result object after apply
292+
293+
After the apply operation, the provider still indicated an unknown value for
294+
examplecloud_container.test.id. All values must be known after apply, so this
295+
is always a bug in the provider and should be reported in the provider's own
296+
repository. Terraform will still save the other known object values in the
297+
state.
298+
299+
Modifying the plan to set the id to a known value appears to be the only way to
300+
circumvent this behaviour, the cause of which I don't fully understand
301+
302+
This doesn't seem great, because this gets applied to all Plans that happen in this
303+
test - so we're modifying plans in steps that we might not want to.
304+
*/
305+
306+
objVal := map[string]tftypes.Value{}
307+
308+
if !response.PlannedState.IsNull() {
309+
_ = response.PlannedState.As(&objVal)
310+
objVal["id"] = tftypes.NewValue(tftypes.String, "sometestid")
311+
}
312+
},
313+
SchemaResponse: &resource.SchemaResponse{
314+
Schema: &tfprotov6.Schema{
315+
Block: &tfprotov6.SchemaBlock{
316+
Attributes: []*tfprotov6.SchemaAttribute{
317+
{
318+
Name: "id",
319+
Type: tftypes.String,
320+
Computed: true,
321+
},
322+
{
323+
Name: "name",
324+
Type: tftypes.String,
325+
Required: true,
326+
},
327+
{
328+
Name: "password",
329+
Type: tftypes.String,
330+
Optional: true,
331+
},
332+
},
333+
},
334+
},
335+
},
336+
},
337+
},
338+
}),
339+
},
340+
Steps: []TestStep{
341+
{
342+
Config: `
343+
resource "examplecloud_container" "test" {
344+
name = "somename"
345+
password = "somevalue"
346+
}`,
347+
},
348+
{
349+
Config: `
350+
terraform {
351+
required_providers {
352+
examplecloud = {
353+
source = "registry.terraform.io/hashicorp/examplecloud"
354+
}
355+
}
356+
}
357+
358+
resource "examplecloud_container" "test" {
359+
name = "somename"
360+
}
361+
362+
import {
363+
to = examplecloud_container.test
364+
id = "sometestid"
365+
}`,
366+
ResourceName: "examplecloud_container.test",
367+
ImportState: true,
368+
ImportStateKind: ImportBlockWithId,
369+
ImportStateVerify: true,
370+
ImportStateVerifyIgnore: []string{"password"},
371+
},
372+
},
373+
})
374+
}
375+
376+
func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore(t *testing.T) {
377+
t.Parallel()
378+
379+
UnitTest(t, TestCase{
380+
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
381+
tfversion.SkipBelow(tfversion.Version1_5_0), // ProtoV6ProviderFactories
382+
},
383+
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
384+
"examplecloud": providerserver.NewProviderServer(testprovider.Provider{
385+
Resources: map[string]testprovider.Resource{
386+
"examplecloud_container": {
387+
CreateResponse: &resource.CreateResponse{
388+
NewState: tftypes.NewValue(
389+
tftypes.Object{
390+
AttributeTypes: map[string]tftypes.Type{
391+
"id": tftypes.String,
392+
"name": tftypes.String,
393+
"password": tftypes.String,
394+
},
395+
},
396+
map[string]tftypes.Value{
397+
"id": tftypes.NewValue(tftypes.String, "sometestid"),
398+
"name": tftypes.NewValue(tftypes.String, "somename"),
399+
"password": tftypes.NewValue(tftypes.String, "somevalue"),
400+
},
401+
),
402+
},
403+
ImportStateResponse: &resource.ImportStateResponse{
404+
State: tftypes.NewValue(
405+
tftypes.Object{
406+
AttributeTypes: map[string]tftypes.Type{
407+
"id": tftypes.String,
408+
"name": tftypes.String,
409+
"password": tftypes.String,
410+
},
411+
},
412+
map[string]tftypes.Value{
413+
"id": tftypes.NewValue(tftypes.String, "sometestid"),
414+
"name": tftypes.NewValue(tftypes.String, "somename"),
415+
"password": tftypes.NewValue(tftypes.String, nil), // this simulates an absent property when importing
416+
},
417+
),
418+
},
419+
SchemaResponse: &resource.SchemaResponse{
420+
Schema: &tfprotov6.Schema{
421+
Block: &tfprotov6.SchemaBlock{
422+
Attributes: []*tfprotov6.SchemaAttribute{
423+
{
424+
Name: "id",
425+
Type: tftypes.String,
426+
Computed: true,
427+
},
428+
{
429+
Name: "name",
430+
Type: tftypes.String,
431+
Computed: true,
432+
},
433+
{
434+
Name: "password",
435+
Type: tftypes.String,
436+
Computed: true,
437+
},
438+
},
439+
},
440+
},
441+
},
442+
},
443+
},
444+
}),
445+
},
446+
Steps: []TestStep{
447+
{
448+
Config: `resource "examplecloud_container" "test" {}`,
449+
},
450+
{
451+
ResourceName: "examplecloud_container.test",
452+
ImportState: true,
453+
ImportStateKind: ImportBlockWithId,
454+
ImportStateVerify: true,
455+
ImportStateVerifyIgnore: []string{"password"},
456+
},
457+
},
458+
})
459+
}

0 commit comments

Comments
 (0)