Skip to content

Commit e7fd3fb

Browse files
committed
Tests for query command outputs Return warning to the CLI if the config
has no query files.
1 parent 9256074 commit e7fd3fb

File tree

11 files changed

+462
-0
lines changed

11 files changed

+462
-0
lines changed

internal/command/query_test.go

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package command
5+
6+
import (
7+
"path"
8+
"strings"
9+
"testing"
10+
11+
"github.com/hashicorp/terraform/internal/configs/configschema"
12+
"github.com/hashicorp/terraform/internal/providers"
13+
testing_provider "github.com/hashicorp/terraform/internal/providers/testing"
14+
"github.com/zclconf/go-cty/cty"
15+
)
16+
17+
func TestQuery(t *testing.T) {
18+
tests := []struct {
19+
name string
20+
directory string
21+
expectedOut string
22+
expectedErr []string
23+
initCode int
24+
}{
25+
{
26+
name: "basic query",
27+
directory: "basic",
28+
expectedOut: `list.test_instance.example id=test-instance-1 Test Instance 1
29+
list.test_instance.example id=test-instance-2 Test Instance 2`,
30+
},
31+
{
32+
name: "query referencing local variable",
33+
directory: "with-locals",
34+
expectedOut: `list.test_instance.example id=test-instance-1 Test Instance 1
35+
list.test_instance.example id=test-instance-2 Test Instance 2`,
36+
},
37+
{
38+
name: "config with no query block",
39+
directory: "no-list-block",
40+
expectedOut: "",
41+
expectedErr: []string{`Warning: No resources to query
42+
43+
The configuration does not contain any resources that can be queried.`},
44+
},
45+
{
46+
name: "missing query file",
47+
directory: "missing-query-file",
48+
expectedOut: "",
49+
expectedErr: []string{"No resources to query"},
50+
},
51+
{
52+
name: "invalid query syntax",
53+
directory: "invalid-syntax",
54+
expectedOut: "",
55+
initCode: 1,
56+
expectedErr: []string{"Unsupported block type", "on query.tfquery.hcl line 11", `resource "test_instance" "example"`},
57+
},
58+
}
59+
60+
for _, ts := range tests {
61+
t.Run(ts.name, func(t *testing.T) {
62+
td := t.TempDir()
63+
testCopyDir(t, testFixturePath(path.Join("query", ts.directory)), td)
64+
t.Chdir(td)
65+
providerSource, close := newMockProviderSource(t, map[string][]string{
66+
"hashicorp/test": {"1.0.0"},
67+
})
68+
defer close()
69+
70+
p := queryFixtureProvider2()
71+
view, done := testView(t)
72+
meta := Meta{
73+
testingOverrides: metaOverridesForProvider(p),
74+
View: view,
75+
AllowExperimentalFeatures: true,
76+
ProviderSource: providerSource,
77+
}
78+
79+
init := &InitCommand{
80+
Meta: meta,
81+
}
82+
if code := init.Run(nil); code != ts.initCode {
83+
output := done(t)
84+
t.Fatalf("expected status code %d but got %d: %s", ts.initCode, code, output.All())
85+
}
86+
87+
c := &QueryCommand{
88+
Meta: meta,
89+
}
90+
args := []string{"-no-color"}
91+
code := c.Run(args)
92+
output := done(t)
93+
actual := output.All()
94+
if len(ts.expectedErr) == 0 {
95+
if code != 0 && len(ts.expectedErr) == 0 {
96+
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
97+
98+
// Check that we have query output
99+
if !strings.Contains(actual, ts.expectedOut) {
100+
t.Errorf("expected query output to contain '%s', got: %s", ts.expectedOut, actual)
101+
}
102+
}
103+
} else {
104+
for _, expected := range ts.expectedErr {
105+
if !strings.Contains(actual, expected) {
106+
t.Errorf("expected error message to contain '%s', got: %s", expected, actual)
107+
}
108+
}
109+
}
110+
})
111+
}
112+
}
113+
114+
func queryFixtureProvider() *testing_provider.MockProvider {
115+
p := testProvider()
116+
listSchema := &configschema.Block{
117+
Attributes: map[string]*configschema.Attribute{
118+
"data": {
119+
Type: cty.DynamicPseudoType,
120+
Computed: true,
121+
},
122+
},
123+
BlockTypes: map[string]*configschema.NestedBlock{
124+
"config": {
125+
Block: configschema.Block{
126+
Attributes: map[string]*configschema.Attribute{
127+
"ami": {
128+
Type: cty.String,
129+
Required: true,
130+
},
131+
},
132+
},
133+
Nesting: configschema.NestingSingle,
134+
},
135+
},
136+
}
137+
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
138+
ResourceTypes: map[string]providers.Schema{
139+
"test_instance": {
140+
Body: &configschema.Block{
141+
Attributes: map[string]*configschema.Attribute{
142+
"id": {
143+
Type: cty.String,
144+
Computed: true,
145+
},
146+
"ami": {
147+
Type: cty.String,
148+
Optional: true,
149+
},
150+
},
151+
},
152+
},
153+
},
154+
ListResourceTypes: map[string]providers.Schema{
155+
"test_instance": {Body: listSchema},
156+
},
157+
}
158+
159+
// Mock the ListResources method for query operations
160+
p.ListResourceResponse = providers.ListResourceResponse{
161+
Result: cty.ObjectVal(map[string]cty.Value{
162+
"data": cty.ListVal([]cty.Value{
163+
cty.ObjectVal(map[string]cty.Value{
164+
"identity": cty.ObjectVal(map[string]cty.Value{
165+
"id": cty.StringVal("test-instance-1"),
166+
}),
167+
"state": cty.ObjectVal(map[string]cty.Value{
168+
"id": cty.StringVal("test-instance-1"),
169+
"ami": cty.StringVal("ami-12345"),
170+
}),
171+
"display_name": cty.StringVal("Test Instance 1"),
172+
}),
173+
cty.ObjectVal(map[string]cty.Value{
174+
"identity": cty.ObjectVal(map[string]cty.Value{
175+
"id": cty.StringVal("test-instance-2"),
176+
}),
177+
"state": cty.ObjectVal(map[string]cty.Value{
178+
"id": cty.StringVal("test-instance-2"),
179+
"ami": cty.StringVal("ami-67890"),
180+
}),
181+
"display_name": cty.StringVal("Test Instance 2"),
182+
}),
183+
}),
184+
"config": cty.ObjectVal(map[string]cty.Value{
185+
"ami": cty.StringVal("ami-12345"),
186+
}),
187+
}),
188+
}
189+
190+
return p
191+
}
192+
193+
func queryFixtureProvider2() *testing_provider.MockProvider {
194+
p := testProvider()
195+
instanceListSchema := &configschema.Block{
196+
Attributes: map[string]*configschema.Attribute{
197+
"data": {
198+
Type: cty.DynamicPseudoType,
199+
Computed: true,
200+
},
201+
},
202+
BlockTypes: map[string]*configschema.NestedBlock{
203+
"config": {
204+
Block: configschema.Block{
205+
Attributes: map[string]*configschema.Attribute{
206+
"ami": {
207+
Type: cty.String,
208+
Required: true,
209+
},
210+
},
211+
},
212+
Nesting: configschema.NestingSingle,
213+
},
214+
},
215+
}
216+
databaseListSchema := &configschema.Block{
217+
Attributes: map[string]*configschema.Attribute{
218+
"data": {
219+
Type: cty.DynamicPseudoType,
220+
Computed: true,
221+
},
222+
},
223+
BlockTypes: map[string]*configschema.NestedBlock{
224+
"config": {
225+
Block: configschema.Block{
226+
Attributes: map[string]*configschema.Attribute{
227+
"engine": {
228+
Type: cty.String,
229+
Optional: true,
230+
},
231+
},
232+
},
233+
Nesting: configschema.NestingSingle,
234+
},
235+
},
236+
}
237+
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
238+
ResourceTypes: map[string]providers.Schema{
239+
"test_instance": {
240+
Body: &configschema.Block{
241+
Attributes: map[string]*configschema.Attribute{
242+
"id": {
243+
Type: cty.String,
244+
Computed: true,
245+
},
246+
"ami": {
247+
Type: cty.String,
248+
Optional: true,
249+
},
250+
},
251+
},
252+
},
253+
"test_database": {
254+
Body: &configschema.Block{
255+
Attributes: map[string]*configschema.Attribute{
256+
"id": {
257+
Type: cty.String,
258+
Computed: true,
259+
},
260+
"engine": {
261+
Type: cty.String,
262+
Optional: true,
263+
},
264+
},
265+
},
266+
},
267+
},
268+
ListResourceTypes: map[string]providers.Schema{
269+
"test_instance": {Body: instanceListSchema},
270+
"test_database": {Body: databaseListSchema},
271+
},
272+
}
273+
274+
// Mock the ListResources method for query operations
275+
p.ListResourceFn = func(request providers.ListResourceRequest) providers.ListResourceResponse {
276+
// Check the config to determine what kind of response to return
277+
wholeConfigMap := request.Config.AsValueMap()
278+
279+
configMap := wholeConfigMap["config"]
280+
281+
// For empty results test case //TODO: Remove?
282+
if ami, ok := wholeConfigMap["ami"]; ok && ami.AsString() == "ami-nonexistent" {
283+
return providers.ListResourceResponse{
284+
Result: cty.ObjectVal(map[string]cty.Value{
285+
"data": cty.ListVal([]cty.Value{}),
286+
"config": configMap,
287+
}),
288+
}
289+
}
290+
291+
switch request.TypeName {
292+
case "test_instance":
293+
return providers.ListResourceResponse{
294+
Result: cty.ObjectVal(map[string]cty.Value{
295+
"data": cty.ListVal([]cty.Value{
296+
cty.ObjectVal(map[string]cty.Value{
297+
"identity": cty.ObjectVal(map[string]cty.Value{
298+
"id": cty.StringVal("test-instance-1"),
299+
}),
300+
"state": cty.ObjectVal(map[string]cty.Value{
301+
"id": cty.StringVal("test-instance-1"),
302+
"ami": cty.StringVal("ami-12345"),
303+
}),
304+
"display_name": cty.StringVal("Test Instance 1"),
305+
}),
306+
cty.ObjectVal(map[string]cty.Value{
307+
"identity": cty.ObjectVal(map[string]cty.Value{
308+
"id": cty.StringVal("test-instance-2"),
309+
}),
310+
"state": cty.ObjectVal(map[string]cty.Value{
311+
"id": cty.StringVal("test-instance-2"),
312+
"ami": cty.StringVal("ami-67890"),
313+
}),
314+
"display_name": cty.StringVal("Test Instance 2"),
315+
}),
316+
}),
317+
"config": configMap,
318+
}),
319+
}
320+
case "test_database":
321+
return providers.ListResourceResponse{
322+
Result: cty.ObjectVal(map[string]cty.Value{
323+
"data": cty.ListVal([]cty.Value{
324+
cty.ObjectVal(map[string]cty.Value{
325+
"identity": cty.ObjectVal(map[string]cty.Value{
326+
"id": cty.StringVal("test-db-1"),
327+
}),
328+
"state": cty.ObjectVal(map[string]cty.Value{
329+
"id": cty.StringVal("test-db-1"),
330+
"engine": cty.StringVal("mysql"),
331+
}),
332+
"display_name": cty.StringVal("Test Database 1"),
333+
}),
334+
}),
335+
"config": configMap,
336+
}),
337+
}
338+
default:
339+
return providers.ListResourceResponse{
340+
Result: cty.ObjectVal(map[string]cty.Value{
341+
"data": cty.ListVal([]cty.Value{}),
342+
"config": configMap,
343+
}),
344+
}
345+
}
346+
}
347+
348+
return p
349+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
terraform {
2+
required_providers {
3+
test = {
4+
source = "hashicorp/test"
5+
}
6+
}
7+
}
8+
9+
provider "test" {}
10+
11+
resource "test_instance" "example" {
12+
ami = "ami-12345"
13+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
list "test_instance" "example" {
2+
provider = test
3+
4+
config {
5+
ami = "ami-12345"
6+
}
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
terraform {
2+
required_providers {
3+
test = {
4+
source = "hashicorp/test"
5+
}
6+
}
7+
}
8+
9+
provider "test" {}
10+
11+
resource "test_instance" "example" {
12+
ami = "ami-12345"
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
list "test_instance" "example" {
2+
provider = test
3+
4+
config {
5+
ami = "ami-12345"
6+
}
7+
}
8+
9+
10+
// resource type not supported in query files
11+
resource "test_instance" "example" {
12+
provider = test
13+
}

0 commit comments

Comments
 (0)