Skip to content

Commit c4e1b0b

Browse files
committed
add tests to provider server and go package docs
1 parent 6250e01 commit c4e1b0b

File tree

3 files changed

+281
-4
lines changed

3 files changed

+281
-4
lines changed

echoprovider/doc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
// Package echoprovider contains a protocol v6 Terraform provider that can be used to transfer data from
55
// provider configuration to state via a managed resource. This is only meant for provider acceptance testing
6-
// of data that is not stored to state, such as an ephemeral resource.
6+
// of data that cannot be stored in Terraform artifacts (plan/state), such as an ephemeral resource.
77
//
88
// Example Usage:
99
//

echoprovider/server.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,48 @@ import (
1111
"github.com/hashicorp/terraform-plugin-go/tftypes"
1212
)
1313

14+
// NewProviderServer returns the "echo" provider, which is a protocol v6 Terraform provider meant only to be used for testing
15+
// data which cannot be stored in Terraform artifacts (plan/state), such as an ephemeral resource. The "echo" provider can be included in
16+
// an acceptance test with the `(resource.TestCase).ProtoV6ProviderFactories` field, for example:
17+
//
18+
// resource.UnitTest(t, resource.TestCase{
19+
// // .. other TestCase fields
20+
// ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
21+
// "echo": echoprovider.NewProviderServer(),
22+
// },
23+
//
24+
// // .. TestSteps
25+
// })
26+
//
27+
// The "echo" provider configuration accepts in a dynamic "data" attribute, which will be stored in the "echo" managed resource "data" attribute, for example:
28+
//
29+
// // Ephemeral resource that is under test
30+
// ephemeral "examplecloud_thing" "this" {
31+
// name = "thing-one"
32+
// }
33+
//
34+
// provider "echo" {
35+
// data = ephemeral.examplecloud_thing.this
36+
// }
37+
//
38+
// resource "echo" "test" {} // The `echo.test.data` attribute will contain the ephemeral data from `ephemeral.examplecloud_thing.this`
1439
func NewProviderServer() func() (tfprotov6.ProviderServer, error) {
1540
return func() (tfprotov6.ProviderServer, error) {
1641
return &echoProviderServer{}, nil
1742
}
1843
}
1944

45+
// echoProviderServer is a lightweight protocol version 6 provider server that saves data from the provider configuration (which is considered ephemeral)
46+
// and then stores that data into state during ApplyResourceChange.
47+
//
48+
// As provider configuration is ephemeral, it's possible for the data to change between plan and apply. As a result of this, during plan, this provider
49+
// will check if the provider config data is different than prior state, and if so, will set the PlannedState to contain an unknown value. If the prior state
50+
// matches the provider config data, the plan will not propose any changes.
2051
type echoProviderServer struct {
2152
// The value of the "data" attribute during provider configuration. Will be directly echoed to the echo.data attribute.
2253
providerConfigData tftypes.Value
2354
}
2455

25-
// This is a special managed resource type that is not meant to be consumed in pracitioner configurations
2656
const echoResourceType = "echo"
2757

2858
func (e *echoProviderServer) providerSchema() *tfprotov6.Schema {
@@ -37,7 +67,7 @@ func (e *echoProviderServer) providerSchema() *tfprotov6.Schema {
3767
Type: tftypes.DynamicPseudoType,
3868
Description: "Dynamic data to provide to the echo resource.",
3969
DescriptionKind: tfprotov6.StringKindPlain,
40-
Required: true,
70+
Optional: true,
4171
},
4272
},
4373
},
@@ -138,7 +168,7 @@ func (e *echoProviderServer) ConfigureProvider(ctx context.Context, req *tfproto
138168
if !ok {
139169
diag := &tfprotov6.Diagnostic{
140170
Severity: tfprotov6.DiagnosticSeverityError,
141-
Summary: "Config data not found",
171+
Summary: `Attribute "data" not found in config`,
142172
}
143173
resp.Diagnostics = append(resp.Diagnostics, diag)
144174
return resp, nil //nolint:nilerr // error via diagnostic, not gRPC
@@ -166,6 +196,8 @@ func (e *echoProviderServer) GetMetadata(ctx context.Context, req *tfprotov6.Get
166196
func (e *echoProviderServer) GetProviderSchema(ctx context.Context, req *tfprotov6.GetProviderSchemaRequest) (*tfprotov6.GetProviderSchemaResponse, error) {
167197
return &tfprotov6.GetProviderSchemaResponse{
168198
Provider: e.providerSchema(),
199+
// MAINTAINER NOTE: This provider is only really built to support a single special resource type ("echo"). In the future, if we want
200+
// to add more resource types to this provider, we'll likely need to refactor other RPCs in the provider server to handle that.
169201
ResourceSchemas: map[string]*tfprotov6.Schema{
170202
echoResourceType: e.testResourceSchema(),
171203
},

echoprovider/server_test.go

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
package echoprovider_test
2+
3+
import (
4+
"regexp"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
8+
"github.com/hashicorp/terraform-plugin-testing/echoprovider"
9+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
10+
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
11+
"github.com/hashicorp/terraform-plugin-testing/plancheck"
12+
"github.com/hashicorp/terraform-plugin-testing/statecheck"
13+
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
14+
)
15+
16+
func TestEchoProviderServer_primitive(t *testing.T) {
17+
t.Parallel()
18+
19+
resource.UnitTest(t, resource.TestCase{
20+
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
21+
"echo": echoprovider.NewProviderServer(),
22+
},
23+
Steps: []resource.TestStep{
24+
{
25+
Config: `
26+
provider "echo" {
27+
data = "hello world"
28+
}
29+
resource "echo" "test_one" {}
30+
`,
31+
ConfigPlanChecks: resource.ConfigPlanChecks{
32+
PreApply: []plancheck.PlanCheck{
33+
plancheck.ExpectUnknownValue("echo.test_one", tfjsonpath.New("data")),
34+
},
35+
},
36+
ConfigStateChecks: []statecheck.StateCheck{
37+
statecheck.ExpectKnownValue("echo.test_one", tfjsonpath.New("data"), knownvalue.StringExact("hello world")),
38+
},
39+
},
40+
{
41+
Config: `
42+
provider "echo" {
43+
data = 200
44+
}
45+
resource "echo" "test_one" {}
46+
`,
47+
ConfigPlanChecks: resource.ConfigPlanChecks{
48+
PreApply: []plancheck.PlanCheck{
49+
plancheck.ExpectUnknownValue("echo.test_one", tfjsonpath.New("data")),
50+
},
51+
},
52+
ConfigStateChecks: []statecheck.StateCheck{
53+
statecheck.ExpectKnownValue("echo.test_one", tfjsonpath.New("data"), knownvalue.Int64Exact(200)),
54+
},
55+
},
56+
{
57+
Config: `
58+
provider "echo" {
59+
data = true
60+
}
61+
resource "echo" "test_one" {}
62+
`,
63+
ConfigPlanChecks: resource.ConfigPlanChecks{
64+
PreApply: []plancheck.PlanCheck{
65+
plancheck.ExpectUnknownValue("echo.test_one", tfjsonpath.New("data")),
66+
},
67+
},
68+
ConfigStateChecks: []statecheck.StateCheck{
69+
statecheck.ExpectKnownValue("echo.test_one", tfjsonpath.New("data"), knownvalue.Bool(true)),
70+
},
71+
},
72+
{
73+
Config: `
74+
provider "echo" {
75+
data = true
76+
}
77+
resource "echo" "test_one" {}
78+
`,
79+
ConfigPlanChecks: resource.ConfigPlanChecks{
80+
PreApply: []plancheck.PlanCheck{
81+
plancheck.ExpectEmptyPlan(),
82+
},
83+
},
84+
},
85+
},
86+
})
87+
}
88+
89+
func TestEchoProviderServer_complex(t *testing.T) {
90+
t.Parallel()
91+
92+
resource.UnitTest(t, resource.TestCase{
93+
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
94+
"echo": echoprovider.NewProviderServer(),
95+
},
96+
Steps: []resource.TestStep{
97+
{
98+
Config: `
99+
provider "echo" {
100+
data = tolist(["hello", "world"])
101+
}
102+
resource "echo" "test_one" {}
103+
`,
104+
ConfigPlanChecks: resource.ConfigPlanChecks{
105+
PreApply: []plancheck.PlanCheck{
106+
plancheck.ExpectUnknownValue("echo.test_one", tfjsonpath.New("data")),
107+
},
108+
},
109+
ConfigStateChecks: []statecheck.StateCheck{
110+
statecheck.ExpectKnownValue("echo.test_one", tfjsonpath.New("data"),
111+
knownvalue.ListExact([]knownvalue.Check{
112+
knownvalue.StringExact("hello"),
113+
knownvalue.StringExact("world"),
114+
}),
115+
),
116+
},
117+
},
118+
{
119+
Config: `
120+
provider "echo" {
121+
data = tomap({"key1": "hello", "key2": "world"})
122+
}
123+
resource "echo" "test_one" {}
124+
`,
125+
ConfigPlanChecks: resource.ConfigPlanChecks{
126+
PreApply: []plancheck.PlanCheck{
127+
plancheck.ExpectUnknownValue("echo.test_one", tfjsonpath.New("data")),
128+
},
129+
},
130+
ConfigStateChecks: []statecheck.StateCheck{
131+
statecheck.ExpectKnownValue("echo.test_one", tfjsonpath.New("data"),
132+
knownvalue.MapExact(map[string]knownvalue.Check{
133+
"key1": knownvalue.StringExact("hello"),
134+
"key2": knownvalue.StringExact("world"),
135+
}),
136+
),
137+
},
138+
},
139+
{
140+
Config: `
141+
provider "echo" {
142+
data = tomap({"key1": "hello", "key2": "world"})
143+
}
144+
resource "echo" "test_one" {}
145+
`,
146+
ConfigPlanChecks: resource.ConfigPlanChecks{
147+
PreApply: []plancheck.PlanCheck{
148+
plancheck.ExpectEmptyPlan(),
149+
},
150+
},
151+
},
152+
},
153+
})
154+
}
155+
156+
func TestEchoProviderServer_null(t *testing.T) {
157+
t.Parallel()
158+
159+
resource.UnitTest(t, resource.TestCase{
160+
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
161+
"echo": echoprovider.NewProviderServer(),
162+
},
163+
Steps: []resource.TestStep{
164+
{
165+
Config: `
166+
provider "echo" {}
167+
resource "echo" "test_one" {}
168+
`,
169+
ConfigPlanChecks: resource.ConfigPlanChecks{
170+
PreApply: []plancheck.PlanCheck{
171+
plancheck.ExpectUnknownValue("echo.test_one", tfjsonpath.New("data")),
172+
},
173+
},
174+
ConfigStateChecks: []statecheck.StateCheck{
175+
statecheck.ExpectKnownValue("echo.test_one", tfjsonpath.New("data"), knownvalue.Null()),
176+
},
177+
},
178+
{
179+
Config: `
180+
provider "echo" {
181+
data = null
182+
}
183+
resource "echo" "test_one" {}
184+
`,
185+
ConfigPlanChecks: resource.ConfigPlanChecks{
186+
PreApply: []plancheck.PlanCheck{
187+
plancheck.ExpectEmptyPlan(),
188+
},
189+
},
190+
},
191+
},
192+
})
193+
}
194+
195+
func TestEchoProviderServer_unknown(t *testing.T) {
196+
t.Parallel()
197+
198+
resource.UnitTest(t, resource.TestCase{
199+
ExternalProviders: map[string]resource.ExternalProvider{
200+
"random": {
201+
Source: "hashicorp/random",
202+
},
203+
},
204+
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
205+
"echo": echoprovider.NewProviderServer(),
206+
},
207+
Steps: []resource.TestStep{
208+
{
209+
Config: `
210+
resource "random_string" "str" {
211+
length = 12
212+
}
213+
provider "echo" {
214+
data = random_string.str.result
215+
}
216+
resource "echo" "test_one" {}
217+
`,
218+
ConfigPlanChecks: resource.ConfigPlanChecks{
219+
PreApply: []plancheck.PlanCheck{
220+
plancheck.ExpectUnknownValue("echo.test_one", tfjsonpath.New("data")),
221+
},
222+
},
223+
ConfigStateChecks: []statecheck.StateCheck{
224+
statecheck.ExpectKnownValue("echo.test_one", tfjsonpath.New("data"), knownvalue.StringRegexp(regexp.MustCompile(`\S{12}`))),
225+
},
226+
},
227+
{
228+
Config: `
229+
resource "random_string" "str" {
230+
length = 12
231+
}
232+
provider "echo" {
233+
data = random_string.str.result
234+
}
235+
resource "echo" "test_one" {}
236+
`,
237+
ConfigPlanChecks: resource.ConfigPlanChecks{
238+
PreApply: []plancheck.PlanCheck{
239+
plancheck.ExpectEmptyPlan(),
240+
},
241+
},
242+
},
243+
},
244+
})
245+
}

0 commit comments

Comments
 (0)