Skip to content

Commit 407a1aa

Browse files
Add provider configure unit test (#403)
* Add provider configure unit test Following #134 I had a bit of trouble making sure that the changes were working correctly so I decided to add a unit test for the provider configuration Changes to PR #134: - Add sub-elem string type - Remove http_headers DefaultFunc, it isn't being called - Add provider configure tests to test that configuring headers works both from env and explicitely Other changes: - Add unit test pipeline to CI * lint
1 parent 52eec2e commit 407a1aa

File tree

6 files changed

+204
-31
lines changed

6 files changed

+204
-31
lines changed

.drone/drone.jsonnet

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,19 @@ local pipeline(name, steps, services=[]) = {
7070
]
7171
),
7272

73+
pipeline(
74+
'unit tests',
75+
steps=[
76+
{
77+
name: 'tests',
78+
image: images.go,
79+
commands: [
80+
'go test ./...',
81+
],
82+
},
83+
]
84+
),
85+
7386
pipeline(
7487
'cloud tests',
7588
steps=[

.drone/drone.yml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,27 @@ workspace:
4949
path: /drone/terraform-provider-grafana
5050
---
5151
kind: pipeline
52+
name: unit tests
53+
platform:
54+
arch: amd64
55+
os: linux
56+
services: []
57+
steps:
58+
- commands:
59+
- go test ./...
60+
image: golang:1.16
61+
name: tests
62+
trigger:
63+
branch:
64+
- master
65+
event:
66+
- pull_request
67+
- push
68+
type: docker
69+
workspace:
70+
path: /drone/terraform-provider-grafana
71+
---
72+
kind: pipeline
5273
name: cloud tests
5374
platform:
5475
arch: amd64
@@ -252,6 +273,6 @@ workspace:
252273
path: /drone/terraform-provider-grafana
253274
---
254275
kind: signature
255-
hmac: 21722dfbb237f702dd2e062347c7adfeb8a15a7cf103670928142a975d743d33
276+
hmac: da6ac4bfbcd28b5ba91a57b10128a0bae335bf46b1bc0ca08ea59326eae3ed16
256277

257278
...

grafana/provider.go

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func Provider(version string) func() *schema.Provider {
6666
Type: schema.TypeMap,
6767
Optional: true,
6868
Sensitive: true,
69-
DefaultFunc: JSONEnvDefaultFunc("GRAFANA_HTTP_HEADERS", nil),
69+
Elem: &schema.Schema{Type: schema.TypeString},
7070
Description: "Optional. HTTP headers mapping keys to values used for accessing the Grafana API. May alternatively be set via the `GRAFANA_HTTP_HEADERS` environment variable in JSON format.",
7171
},
7272
"retries": {
@@ -195,33 +195,33 @@ func Provider(version string) func() *schema.Provider {
195195
}
196196

197197
type client struct {
198-
gapiURL string
199-
gapi *gapi.Client
200-
gcloudapi *gapi.Client
201-
smapi *smapi.Client
202-
mlapi *mlapi.Client
198+
gapiURL string
199+
gapi *gapi.Client
200+
gapiConfig *gapi.Config
201+
gcloudapi *gapi.Client
202+
smapi *smapi.Client
203+
mlapi *mlapi.Client
203204
}
204205

205206
func configure(version string, p *schema.Provider) func(context.Context, *schema.ResourceData) (interface{}, diag.Diagnostics) {
206207
return func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
207208
var (
208-
cfg *gapi.Config
209209
diags diag.Diagnostics
210210
err error
211211
)
212212
p.UserAgent("terraform-provider-grafana", version)
213213

214214
c := &client{}
215215

216-
c.gapiURL, cfg, c.gapi, err = createGrafanaClient(d)
216+
c.gapiURL, c.gapiConfig, c.gapi, err = createGrafanaClient(d)
217217
if err != nil {
218218
return nil, diag.FromErr(err)
219219
}
220220
c.gcloudapi, err = createCloudClient(d)
221221
if err != nil {
222222
return nil, diag.FromErr(err)
223223
}
224-
c.mlapi, err = createMLClient(c.gapiURL, cfg)
224+
c.mlapi, err = createMLClient(c.gapiURL, c.gapiConfig)
225225
if err != nil {
226226
return nil, diag.FromErr(err)
227227
}
@@ -279,17 +279,14 @@ func createGrafanaClient(d *schema.ResourceData) (string, *gapi.Config, *gapi.Cl
279279

280280
headersMap := d.Get("http_headers").(map[string]interface{})
281281
if headersMap != nil && len(headersMap) == 0 {
282-
// Workaround for a bug when DefaultFunc returns a schema.TypeMap
283-
headersMapAbs, err := JSONEnvDefaultFunc("GRAFANA_HTTP_HEADERS", nil)()
282+
// We cannot use a DefaultFunc because they do not work on maps
283+
var err error
284+
headersMap, err = getJSONMap("GRAFANA_HTTP_HEADERS")
284285
if err != nil {
285-
return "", nil, nil, err
286-
}
287-
if headersMapAbs != nil {
288-
headersMap = headersMapAbs.(map[string]interface{})
286+
return "", nil, nil, fmt.Errorf("invalid http_headers config: %w", err)
289287
}
290288
}
291-
if headersMap != nil {
292-
// Convert headers from map[string]interface{} to map[string]string
289+
if len(headersMap) > 0 {
293290
headers := make(map[string]string)
294291
for k, v := range headersMap {
295292
if v, ok := v.(string); ok {
@@ -338,19 +335,15 @@ func createSMClient(d *schema.ResourceData) *smapi.Client {
338335
return smapi.NewClient(smURL, smToken, nil)
339336
}
340337

341-
// JSONEnvDefaultFunc is a helper function that parses the given environment
342-
// variable as a JSON object, or returns the default value otherwise.
343-
func JSONEnvDefaultFunc(k string, dv interface{}) schema.SchemaDefaultFunc {
344-
return func() (interface{}, error) {
345-
if valStr := os.Getenv(k); valStr != "" {
346-
var valObj map[string]interface{}
347-
err := json.Unmarshal([]byte(valStr), &valObj)
348-
if err != nil {
349-
return nil, err
350-
}
351-
return valObj, nil
338+
// getJSONMap is a helper function that parses the given environment variable as a JSON object
339+
func getJSONMap(k string) (map[string]interface{}, error) {
340+
if valStr := os.Getenv(k); valStr != "" {
341+
var valObj map[string]interface{}
342+
err := json.Unmarshal([]byte(valStr), &valObj)
343+
if err != nil {
344+
return nil, err
352345
}
353-
354-
return dv, nil
346+
return valObj, nil
355347
}
348+
return nil, nil
356349
}

grafana/provider_test.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import (
44
"context"
55
"io/ioutil"
66
"os"
7+
"regexp"
78
"strconv"
9+
"strings"
810
"sync"
911
"testing"
1012

1113
"github.com/Masterminds/semver/v3"
14+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
1215
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1316
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
1417
)
@@ -46,11 +49,142 @@ func init() {
4649
}
4750

4851
func TestProvider(t *testing.T) {
52+
IsUnitTest(t)
53+
4954
if err := Provider("dev")().InternalValidate(); err != nil {
5055
t.Fatalf("err: %s", err)
5156
}
5257
}
5358

59+
func TestProviderConfigure(t *testing.T) {
60+
IsUnitTest(t)
61+
62+
// Helper for header tests
63+
checkHeaders := func(t *testing.T, provider *schema.Provider) {
64+
gotHeaders := provider.Meta().(*client).gapiConfig.HTTPHeaders
65+
if len(gotHeaders) != 2 {
66+
t.Errorf("expected 2 HTTP header, got %d", len(gotHeaders))
67+
}
68+
if gotHeaders["Authorization"] != "Bearer test" {
69+
t.Errorf("expected HTTP header Authorization to be \"Bearer test\", got %q", gotHeaders["Authorization"])
70+
}
71+
if gotHeaders["X-Custom-Header"] != "custom-value" {
72+
t.Errorf("expected HTTP header X-Custom-Header to be \"custom-value\", got %q", gotHeaders["X-Custom-Header"])
73+
}
74+
}
75+
76+
envBackup := os.Environ()
77+
defer func() {
78+
os.Clearenv()
79+
for _, v := range envBackup {
80+
kv := strings.SplitN(v, "=", 2)
81+
os.Setenv(kv[0], kv[1])
82+
}
83+
}()
84+
85+
cases := []struct {
86+
name string
87+
config map[string]interface{}
88+
env map[string]string
89+
expectedErr string
90+
check func(t *testing.T, provider *schema.Provider)
91+
}{
92+
{
93+
name: "no config",
94+
env: map[string]string{},
95+
expectedErr: "\"auth\": one of `auth,cloud_api_key,sm_access_token` must be specified",
96+
},
97+
{
98+
name: "grafana config from env",
99+
env: map[string]string{
100+
"GRAFANA_AUTH": "admin:admin",
101+
"GRAFANA_URL": "https://test.com",
102+
},
103+
},
104+
{
105+
name: "header config",
106+
env: map[string]string{
107+
"GRAFANA_AUTH": "admin:admin",
108+
"GRAFANA_URL": "https://test.com",
109+
},
110+
config: map[string]interface{}{
111+
"http_headers": map[string]interface{}{
112+
"Authorization": "Bearer test",
113+
"X-Custom-Header": "custom-value",
114+
},
115+
},
116+
check: checkHeaders,
117+
},
118+
{
119+
name: "header config from env",
120+
env: map[string]string{
121+
"GRAFANA_AUTH": "admin:admin",
122+
"GRAFANA_URL": "https://test.com",
123+
"GRAFANA_HTTP_HEADERS": `{"X-Custom-Header": "custom-value", "Authorization": "Bearer test"}`,
124+
},
125+
check: checkHeaders,
126+
},
127+
{
128+
name: "invalid header",
129+
env: map[string]string{
130+
"GRAFANA_AUTH": "admin:admin",
131+
"GRAFANA_URL": "https://test.com",
132+
"GRAFANA_HTTP_HEADERS": `blabla`,
133+
},
134+
expectedErr: "invalid http_headers config: invalid character 'b' looking for beginning of value",
135+
},
136+
{
137+
name: "grafana cloud config from env",
138+
env: map[string]string{
139+
"GRAFANA_CLOUD_API_KEY": "testtest",
140+
},
141+
},
142+
{
143+
name: "grafana sm config from env",
144+
env: map[string]string{
145+
"GRAFANA_SM_ACCESS_TOKEN": "testtest",
146+
},
147+
},
148+
}
149+
150+
for _, tc := range cases {
151+
t.Run(tc.name, func(t *testing.T) {
152+
os.Clearenv()
153+
for k, v := range tc.env {
154+
os.Setenv(k, v)
155+
}
156+
157+
test := resource.TestStep{
158+
// Resource is irrelevant, it's just there to test the provider being configured
159+
// Terraform will "validate" the provider, but not actually use it when planning
160+
PlanOnly: true,
161+
ExpectNonEmptyPlan: true,
162+
Config: `resource "grafana_folder" "test" {
163+
title = "test"
164+
}`,
165+
}
166+
167+
if tc.expectedErr != "" {
168+
test.ExpectError = regexp.MustCompile(tc.expectedErr)
169+
}
170+
171+
// Configure the provider and check it
172+
provider := Provider("dev")()
173+
provider.Configure(context.Background(), terraform.NewResourceConfigRaw(tc.config))
174+
if tc.check != nil {
175+
tc.check(t, provider)
176+
}
177+
// Run the plan to check for validation errors
178+
resource.UnitTest(t, resource.TestCase{
179+
Providers: map[string]*schema.Provider{
180+
"grafana": provider,
181+
},
182+
Steps: []resource.TestStep{test},
183+
})
184+
})
185+
}
186+
}
187+
54188
// testAccPreCheckEnv contains all environment variables that must be present
55189
// for acceptance tests to run. These are checked in testAccPreCheck.
56190
var testAccPreCheckEnv = []string{
@@ -114,6 +248,14 @@ func accTestsEnabled(t *testing.T, envVarName string) bool {
114248
return enabled
115249
}
116250

251+
func IsUnitTest(t *testing.T) {
252+
t.Helper()
253+
254+
if accTestsEnabled(t, "TF_ACC") {
255+
t.Skip("Skipping acceptance tests")
256+
}
257+
}
258+
117259
func CheckOSSTestsEnabled(t *testing.T) {
118260
t.Helper()
119261
if !accTestsEnabled(t, "TF_ACC_OSS") {

grafana/resource_dashboard_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ func testAccDashboardFolderCheckDestroy(dashboard *gapi.Dashboard, folder *gapi.
236236
}
237237

238238
func Test_normalizeDashboardConfigJSON(t *testing.T) {
239+
IsUnitTest(t)
240+
239241
type args struct {
240242
config interface{}
241243
}

grafana/resource_data_source_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,8 @@ func TestAccDataSource_basic(t *testing.T) {
568568
}
569569

570570
func TestDatasourceMigrationV0(t *testing.T) {
571+
IsUnitTest(t)
572+
571573
cases := []struct {
572574
name string
573575
state map[string]interface{}

0 commit comments

Comments
 (0)