Skip to content

Commit afb2730

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

File tree

11 files changed

+383
-0
lines changed

11 files changed

+383
-0
lines changed

internal/command/query_test.go

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
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 := queryFixtureProvider()
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+
instanceListSchema := &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+
databaseListSchema := &configschema.Block{
138+
Attributes: map[string]*configschema.Attribute{
139+
"data": {
140+
Type: cty.DynamicPseudoType,
141+
Computed: true,
142+
},
143+
},
144+
BlockTypes: map[string]*configschema.NestedBlock{
145+
"config": {
146+
Block: configschema.Block{
147+
Attributes: map[string]*configschema.Attribute{
148+
"engine": {
149+
Type: cty.String,
150+
Optional: true,
151+
},
152+
},
153+
},
154+
Nesting: configschema.NestingSingle,
155+
},
156+
},
157+
}
158+
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
159+
ResourceTypes: map[string]providers.Schema{
160+
"test_instance": {
161+
Body: &configschema.Block{
162+
Attributes: map[string]*configschema.Attribute{
163+
"id": {
164+
Type: cty.String,
165+
Computed: true,
166+
},
167+
"ami": {
168+
Type: cty.String,
169+
Optional: true,
170+
},
171+
},
172+
},
173+
},
174+
"test_database": {
175+
Body: &configschema.Block{
176+
Attributes: map[string]*configschema.Attribute{
177+
"id": {
178+
Type: cty.String,
179+
Computed: true,
180+
},
181+
"engine": {
182+
Type: cty.String,
183+
Optional: true,
184+
},
185+
},
186+
},
187+
},
188+
},
189+
ListResourceTypes: map[string]providers.Schema{
190+
"test_instance": {Body: instanceListSchema},
191+
"test_database": {Body: databaseListSchema},
192+
},
193+
}
194+
195+
// Mock the ListResources method for query operations
196+
p.ListResourceFn = func(request providers.ListResourceRequest) providers.ListResourceResponse {
197+
// Check the config to determine what kind of response to return
198+
wholeConfigMap := request.Config.AsValueMap()
199+
200+
configMap := wholeConfigMap["config"]
201+
202+
// For empty results test case //TODO: Remove?
203+
if ami, ok := wholeConfigMap["ami"]; ok && ami.AsString() == "ami-nonexistent" {
204+
return providers.ListResourceResponse{
205+
Result: cty.ObjectVal(map[string]cty.Value{
206+
"data": cty.ListVal([]cty.Value{}),
207+
"config": configMap,
208+
}),
209+
}
210+
}
211+
212+
switch request.TypeName {
213+
case "test_instance":
214+
return providers.ListResourceResponse{
215+
Result: cty.ObjectVal(map[string]cty.Value{
216+
"data": cty.ListVal([]cty.Value{
217+
cty.ObjectVal(map[string]cty.Value{
218+
"identity": cty.ObjectVal(map[string]cty.Value{
219+
"id": cty.StringVal("test-instance-1"),
220+
}),
221+
"state": cty.ObjectVal(map[string]cty.Value{
222+
"id": cty.StringVal("test-instance-1"),
223+
"ami": cty.StringVal("ami-12345"),
224+
}),
225+
"display_name": cty.StringVal("Test Instance 1"),
226+
}),
227+
cty.ObjectVal(map[string]cty.Value{
228+
"identity": cty.ObjectVal(map[string]cty.Value{
229+
"id": cty.StringVal("test-instance-2"),
230+
}),
231+
"state": cty.ObjectVal(map[string]cty.Value{
232+
"id": cty.StringVal("test-instance-2"),
233+
"ami": cty.StringVal("ami-67890"),
234+
}),
235+
"display_name": cty.StringVal("Test Instance 2"),
236+
}),
237+
}),
238+
"config": configMap,
239+
}),
240+
}
241+
case "test_database":
242+
return providers.ListResourceResponse{
243+
Result: cty.ObjectVal(map[string]cty.Value{
244+
"data": cty.ListVal([]cty.Value{
245+
cty.ObjectVal(map[string]cty.Value{
246+
"identity": cty.ObjectVal(map[string]cty.Value{
247+
"id": cty.StringVal("test-db-1"),
248+
}),
249+
"state": cty.ObjectVal(map[string]cty.Value{
250+
"id": cty.StringVal("test-db-1"),
251+
"engine": cty.StringVal("mysql"),
252+
}),
253+
"display_name": cty.StringVal("Test Database 1"),
254+
}),
255+
}),
256+
"config": configMap,
257+
}),
258+
}
259+
default:
260+
return providers.ListResourceResponse{
261+
Result: cty.ObjectVal(map[string]cty.Value{
262+
"data": cty.ListVal([]cty.Value{}),
263+
"config": configMap,
264+
}),
265+
}
266+
}
267+
}
268+
269+
return p
270+
}
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+
}
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+
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+
}

internal/command/testdata/query/no-list-block/query.tfquery.hcl

Whitespace-only changes.
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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
locals {
2+
ami = "ami-12345"
3+
}
4+
5+
list "test_instance" "example" {
6+
provider = test
7+
8+
config {
9+
ami = local.ami
10+
}
11+
}

0 commit comments

Comments
 (0)