@@ -65,6 +65,278 @@ func TestTest_TestStep_ExpectError_NewConfig(t *testing.T) {
6565 })
6666}
6767
68+ func Test_ExpectNonEmptyPlan_EmptyPlanError (t * testing.T ) {
69+ t .Parallel ()
70+
71+ UnitTest (t , TestCase {
72+ TerraformVersionChecks : []tfversion.TerraformVersionCheck {
73+ tfversion .SkipBelow (tfversion .Version1_4_0 ),
74+ },
75+ ExternalProviders : map [string ]ExternalProvider {
76+ "terraform" : {Source : "terraform.io/builtin/terraform" },
77+ },
78+ Steps : []TestStep {
79+ {
80+ Config : `resource "terraform_data" "test" {}` ,
81+ ExpectNonEmptyPlan : true ,
82+ ExpectError : regexp .MustCompile ("Expected a non-empty plan, but got an empty plan" ),
83+ },
84+ },
85+ })
86+ }
87+
88+ func Test_ExpectNonEmptyPlan_PreRefresh_ResourceChanges (t * testing.T ) {
89+ t .Parallel ()
90+
91+ UnitTest (t , TestCase {
92+ TerraformVersionChecks : []tfversion.TerraformVersionCheck {
93+ tfversion .SkipBelow (tfversion .Version1_4_0 ),
94+ },
95+ ExternalProviders : map [string ]ExternalProvider {
96+ "terraform" : {Source : "terraform.io/builtin/terraform" },
97+ },
98+ Steps : []TestStep {
99+ {
100+ Config : `resource "terraform_data" "test" {
101+ # Never recommended for real world configurations, but tests
102+ # the intended behavior.
103+ input = timestamp()
104+ }` ,
105+ ConfigPlanChecks : ConfigPlanChecks {
106+ // Verification of that the behavior is being caught pre
107+ // refresh. We want to ensure ExpectNonEmptyPlan allows test
108+ // to pass if pre refresh also has changes.
109+ PostApplyPreRefresh : []plancheck.PlanCheck {
110+ plancheck .ExpectResourceAction ("terraform_data.test" , plancheck .ResourceActionUpdate ),
111+ },
112+ },
113+ ExpectNonEmptyPlan : true ,
114+ },
115+ },
116+ })
117+ }
118+
119+ func Test_ExpectNonEmptyPlan_PostRefresh_OutputChanges (t * testing.T ) {
120+ t .Parallel ()
121+
122+ UnitTest (t , TestCase {
123+ TerraformVersionChecks : []tfversion.TerraformVersionCheck {
124+ tfversion .SkipAbove (tfversion .Version0_14_0 ), // outputs before 0.14 always show as created
125+ },
126+ // Avoid our own validation that requires at least one provider config.
127+ ExternalProviders : map [string ]ExternalProvider {
128+ "terraform" : {Source : "terraform.io/builtin/terraform" },
129+ },
130+ Steps : []TestStep {
131+ {
132+ Config : `output "test" { value = timestamp() }` ,
133+ ExpectNonEmptyPlan : false , // compatibility compromise for 0.12 and 0.13
134+ },
135+ },
136+ })
137+
138+ UnitTest (t , TestCase {
139+ TerraformVersionChecks : []tfversion.TerraformVersionCheck {
140+ tfversion .SkipBelow (tfversion .Version0_14_0 ), // outputs before 0.14 always show as created
141+ },
142+ // Avoid our own validation that requires at least one provider config.
143+ ExternalProviders : map [string ]ExternalProvider {
144+ "terraform" : {Source : "terraform.io/builtin/terraform" },
145+ },
146+ Steps : []TestStep {
147+ {
148+ Config : `output "test" { value = timestamp() }` ,
149+ ExpectNonEmptyPlan : true ,
150+ },
151+ },
152+ })
153+ }
154+
155+ func Test_ExpectNonEmptyPlan_PostRefresh_ResourceChanges (t * testing.T ) {
156+ t .Parallel ()
157+
158+ UnitTest (t , TestCase {
159+ TerraformVersionChecks : []tfversion.TerraformVersionCheck {
160+ tfversion .SkipBelow (tfversion .Version1_0_0 ), // ProtoV6ProviderFactories
161+ },
162+ ProtoV6ProviderFactories : map [string ]func () (tfprotov6.ProviderServer , error ){
163+ "test" : providerserver .NewProviderServer (testprovider.Provider {
164+ Resources : map [string ]testprovider.Resource {
165+ "test_resource" : {
166+ CreateResponse : & resource.CreateResponse {
167+ NewState : tftypes .NewValue (
168+ tftypes.Object {
169+ AttributeTypes : map [string ]tftypes.Type {
170+ "id" : tftypes .String ,
171+ },
172+ },
173+ map [string ]tftypes.Value {
174+ "id" : tftypes .NewValue (tftypes .String , "test" ), // intentionally same
175+ },
176+ ),
177+ },
178+ ReadResponse : & resource.ReadResponse {
179+ NewState : tftypes .NewValue (
180+ tftypes.Object {
181+ AttributeTypes : map [string ]tftypes.Type {
182+ "id" : tftypes .String ,
183+ },
184+ },
185+ map [string ]tftypes.Value {
186+ "id" : tftypes .NewValue (tftypes .String , "not-test" ), // intentionally different
187+ },
188+ ),
189+ },
190+ SchemaResponse : & resource.SchemaResponse {
191+ Schema : & tfprotov6.Schema {
192+ Block : & tfprotov6.SchemaBlock {
193+ Attributes : []* tfprotov6.SchemaAttribute {
194+ {
195+ Name : "id" ,
196+ Type : tftypes .String ,
197+ Required : true ,
198+ },
199+ },
200+ },
201+ },
202+ },
203+ },
204+ },
205+ }),
206+ },
207+ Steps : []TestStep {
208+ {
209+ Config : `resource "test_resource" "test" {
210+ # Post create refresh intentionally changes configured value
211+ # which is an errant resource implementation. Create should
212+ # account for the correct post creation state, preventing an
213+ # immediate difference next Terraform run for practitioners.
214+ # This errant resource behavior verifies the expected
215+ # behavior of ExpectNonEmptyPlan for post refresh planning.
216+ id = "test"
217+ }` ,
218+ ConfigPlanChecks : ConfigPlanChecks {
219+ // Verification of that the behavior is being caught post
220+ // refresh. We want to ensure ExpectNonEmptyPlan is being
221+ // triggered after the pre refresh plan shows no changes.
222+ PostApplyPreRefresh : []plancheck.PlanCheck {
223+ plancheck .ExpectResourceAction ("test_resource.test" , plancheck .ResourceActionNoop ),
224+ },
225+ },
226+ ExpectNonEmptyPlan : true ,
227+ },
228+ },
229+ })
230+ }
231+
232+ func Test_NonEmptyPlan_PreRefresh_Error (t * testing.T ) {
233+ t .Parallel ()
234+
235+ UnitTest (t , TestCase {
236+ TerraformVersionChecks : []tfversion.TerraformVersionCheck {
237+ tfversion .SkipBelow (tfversion .Version1_4_0 ),
238+ },
239+ ExternalProviders : map [string ]ExternalProvider {
240+ "terraform" : {Source : "terraform.io/builtin/terraform" },
241+ },
242+ Steps : []TestStep {
243+ {
244+ Config : `resource "terraform_data" "test" {
245+ # Never recommended for real world configurations, but tests
246+ # the intended behavior.
247+ input = timestamp()
248+ }` ,
249+ ConfigPlanChecks : ConfigPlanChecks {
250+ // Verification of that the behavior is being caught pre
251+ // refresh.
252+ PostApplyPreRefresh : []plancheck.PlanCheck {
253+ plancheck .ExpectResourceAction ("terraform_data.test" , plancheck .ResourceActionUpdate ),
254+ },
255+ },
256+ ExpectNonEmptyPlan : false , // intentional
257+ ExpectError : regexp .MustCompile ("After applying this test step, the plan was not empty." ),
258+ },
259+ },
260+ })
261+ }
262+
263+ func Test_NonEmptyPlan_PostRefresh_Error (t * testing.T ) {
264+ t .Parallel ()
265+
266+ UnitTest (t , TestCase {
267+ TerraformVersionChecks : []tfversion.TerraformVersionCheck {
268+ tfversion .SkipBelow (tfversion .Version1_0_0 ), // ProtoV6ProviderFactories
269+ },
270+ ProtoV6ProviderFactories : map [string ]func () (tfprotov6.ProviderServer , error ){
271+ "test" : providerserver .NewProviderServer (testprovider.Provider {
272+ Resources : map [string ]testprovider.Resource {
273+ "test_resource" : {
274+ CreateResponse : & resource.CreateResponse {
275+ NewState : tftypes .NewValue (
276+ tftypes.Object {
277+ AttributeTypes : map [string ]tftypes.Type {
278+ "id" : tftypes .String ,
279+ },
280+ },
281+ map [string ]tftypes.Value {
282+ "id" : tftypes .NewValue (tftypes .String , "test" ), // intentionally same
283+ },
284+ ),
285+ },
286+ ReadResponse : & resource.ReadResponse {
287+ NewState : tftypes .NewValue (
288+ tftypes.Object {
289+ AttributeTypes : map [string ]tftypes.Type {
290+ "id" : tftypes .String ,
291+ },
292+ },
293+ map [string ]tftypes.Value {
294+ "id" : tftypes .NewValue (tftypes .String , "not-test" ), // intentionally different
295+ },
296+ ),
297+ },
298+ SchemaResponse : & resource.SchemaResponse {
299+ Schema : & tfprotov6.Schema {
300+ Block : & tfprotov6.SchemaBlock {
301+ Attributes : []* tfprotov6.SchemaAttribute {
302+ {
303+ Name : "id" ,
304+ Type : tftypes .String ,
305+ Required : true ,
306+ },
307+ },
308+ },
309+ },
310+ },
311+ },
312+ },
313+ }),
314+ },
315+ Steps : []TestStep {
316+ {
317+ Config : `resource "test_resource" "test" {
318+ # Post create refresh intentionally changes configured value
319+ # which is an errant resource implementation. Create should
320+ # account for the correct post creation state, preventing an
321+ # immediate difference next Terraform run for practitioners.
322+ # This errant resource behavior verifies the expected
323+ # behavior of ExpectNonEmptyPlan for post refresh planning.
324+ id = "test"
325+ }` ,
326+ ConfigPlanChecks : ConfigPlanChecks {
327+ // Verification of that the behavior is being caught post
328+ // refresh.
329+ PostApplyPreRefresh : []plancheck.PlanCheck {
330+ plancheck .ExpectResourceAction ("test_resource.test" , plancheck .ResourceActionNoop ),
331+ },
332+ },
333+ ExpectNonEmptyPlan : false , // intentional
334+ ExpectError : regexp .MustCompile ("After applying this test step and performing a `terraform refresh`, the plan was not empty." ),
335+ },
336+ },
337+ })
338+ }
339+
68340func Test_ConfigPlanChecks_PreApply_Called (t * testing.T ) {
69341 t .Parallel ()
70342
0 commit comments