Skip to content

Commit fc608d1

Browse files
committed
Test extension data source against Kernel
1 parent 0f9ca3b commit fc608d1

2 files changed

Lines changed: 291 additions & 0 deletions

File tree

.github/workflows/acceptance.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ jobs:
3333
- name: Proxy data source
3434
package: ./internal/datasources/proxy
3535
project_id_required: true
36+
- name: Extension data source
37+
package: ./internal/datasources/extension
38+
project_id_required: true
3639
# Keep the job timeout above the go test timeout so Go can report the test
3740
# timeout before the runner stops the job. Either hard timeout can bypass
3841
# t.Cleanup, so release operators still inspect for leaked test resources.
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
package extension_test
2+
3+
import (
4+
"archive/zip"
5+
"bytes"
6+
"context"
7+
"encoding/json"
8+
"errors"
9+
"fmt"
10+
"net/http"
11+
"os"
12+
"strconv"
13+
"testing"
14+
"time"
15+
16+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
17+
kernel "github.com/kernel/kernel-go-sdk"
18+
"github.com/kernel/terraform-provider-kernel/internal/acctest"
19+
"github.com/kernel/terraform-provider-kernel/internal/kernelclient"
20+
"github.com/kernel/terraform-provider-kernel/internal/projectscope"
21+
)
22+
23+
type extensionRecoveryClient interface {
24+
GetExtension(context.Context, string, string) (*kernel.ExtensionGetResponse, error)
25+
}
26+
27+
type fakeExtensionRecoveryClient struct {
28+
get func(context.Context, string, string) (*kernel.ExtensionGetResponse, error)
29+
}
30+
31+
func (f fakeExtensionRecoveryClient) GetExtension(ctx context.Context, projectID, idOrName string) (*kernel.ExtensionGetResponse, error) {
32+
return f.get(ctx, projectID, idOrName)
33+
}
34+
35+
func TestExtensionArchiveFixture(t *testing.T) {
36+
archive := testAccExtensionArchive(t)
37+
reader, err := zip.NewReader(bytes.NewReader(archive), int64(len(archive)))
38+
if err != nil {
39+
t.Fatalf("open extension ZIP: %v", err)
40+
}
41+
if len(reader.File) != 2 {
42+
t.Fatalf("extension ZIP entries = %d, want 2", len(reader.File))
43+
}
44+
if reader.File[0].Name != "manifest.json" || reader.File[1].Name != "marker.txt" {
45+
t.Fatalf("extension ZIP entries = %q, %q", reader.File[0].Name, reader.File[1].Name)
46+
}
47+
}
48+
49+
func TestRecoverExtensionFixture(t *testing.T) {
50+
tests := map[string]struct {
51+
extension *kernel.ExtensionGetResponse
52+
getErr error
53+
wantID string
54+
wantErr bool
55+
}{
56+
"found": {
57+
extension: &kernel.ExtensionGetResponse{ID: "extension_123"},
58+
wantID: "extension_123",
59+
},
60+
"not found": {
61+
getErr: testAccCodedNotFoundError(t),
62+
},
63+
"server error": {
64+
getErr: errors.New("connection reset"),
65+
wantErr: true,
66+
},
67+
"empty response": {
68+
extension: &kernel.ExtensionGetResponse{},
69+
wantErr: true,
70+
},
71+
}
72+
73+
for name, test := range tests {
74+
t.Run(name, func(t *testing.T) {
75+
extension, err := testAccRecoverExtensionFixture(fakeExtensionRecoveryClient{
76+
get: func(ctx context.Context, projectID, idOrName string) (*kernel.ExtensionGetResponse, error) {
77+
if projectID != "project_123" {
78+
t.Fatalf("project ID = %q, want project_123", projectID)
79+
}
80+
if idOrName != "fixture" {
81+
t.Fatalf("selector = %q, want fixture", idOrName)
82+
}
83+
return test.extension, test.getErr
84+
},
85+
}, "project_123", "fixture")
86+
if (err != nil) != test.wantErr {
87+
t.Fatalf("recovery error = %v, want error %t", err, test.wantErr)
88+
}
89+
if test.wantID == "" {
90+
if extension != nil {
91+
t.Fatalf("recovered extension = %#v, want nil", extension)
92+
}
93+
return
94+
}
95+
if extension == nil || extension.ID != test.wantID {
96+
t.Fatalf("recovered extension ID = %v, want %q", extension, test.wantID)
97+
}
98+
})
99+
}
100+
}
101+
102+
func testAccCodedNotFoundError(t *testing.T) error {
103+
t.Helper()
104+
105+
var apiError kernel.Error
106+
if err := json.Unmarshal([]byte(`{"error":{"code":"not_found","message":"not found"}}`), &apiError); err != nil {
107+
t.Fatalf("build coded not-found error: %v", err)
108+
}
109+
request, err := http.NewRequest(http.MethodGet, "https://api.example/extensions/fixture", nil)
110+
if err != nil {
111+
t.Fatalf("build coded not-found request: %v", err)
112+
}
113+
apiError.StatusCode = http.StatusNotFound
114+
apiError.Request = request
115+
apiError.Response = &http.Response{StatusCode: http.StatusNotFound}
116+
return &apiError
117+
}
118+
119+
func TestAccExtensionDataSourceByIDAndName(t *testing.T) {
120+
if os.Getenv("TF_ACC") != "1" {
121+
t.Skip("set TF_ACC=1 to run Terraform acceptance tests")
122+
}
123+
acctest.PreCheck(t)
124+
125+
projectID := os.Getenv(acctest.EnvProjectID)
126+
if projectID == "" {
127+
t.Fatalf("%s must be set for the extension data source acceptance test", acctest.EnvProjectID)
128+
}
129+
130+
name := acctest.UniqueName(t, "extension-data")
131+
archive := testAccExtensionArchive(t)
132+
id := testAccUploadExtensionFixture(t, projectID, name, archive)
133+
config := testAccExtensionDataSourceConfig(id, name, projectID)
134+
135+
resource.Test(t, resource.TestCase{
136+
PreCheck: func() { acctest.PreCheck(t) },
137+
ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories(),
138+
Steps: []resource.TestStep{
139+
{
140+
Config: config,
141+
Check: resource.ComposeAggregateTestCheckFunc(
142+
resource.TestCheckResourceAttr("data.kernel_extension.by_id", "id", id),
143+
resource.TestCheckResourceAttr("data.kernel_extension.by_id", "name", name),
144+
resource.TestCheckResourceAttr("data.kernel_extension.by_id", "project_id", projectID),
145+
resource.TestCheckResourceAttrSet("data.kernel_extension.by_id", "created_at"),
146+
resource.TestCheckResourceAttr("data.kernel_extension.by_id", "size_bytes", strconv.Itoa(len(archive))),
147+
resource.TestCheckNoResourceAttr("data.kernel_extension.by_id", "last_used_at"),
148+
resource.TestCheckResourceAttr("data.kernel_extension.by_name", "id", id),
149+
resource.TestCheckResourceAttr("data.kernel_extension.by_name", "name", name),
150+
resource.TestCheckNoResourceAttr("data.kernel_extension.by_name", "project_id"),
151+
resource.TestCheckResourceAttrPair("data.kernel_extension.by_name", "created_at", "data.kernel_extension.by_id", "created_at"),
152+
resource.TestCheckResourceAttrPair("data.kernel_extension.by_name", "size_bytes", "data.kernel_extension.by_id", "size_bytes"),
153+
resource.TestCheckNoResourceAttr("data.kernel_extension.by_name", "last_used_at"),
154+
),
155+
},
156+
{
157+
Config: config,
158+
PlanOnly: true,
159+
},
160+
},
161+
})
162+
}
163+
164+
func testAccUploadExtensionFixture(t *testing.T, projectID, name string, archive []byte) string {
165+
t.Helper()
166+
167+
ctx, cancel := context.WithTimeout(context.Background(), kernelclient.DefaultRequestTimeout)
168+
defer cancel()
169+
170+
extension, err := acctest.ClientFromEnv().UploadExtension(ctx, projectID, kernel.ExtensionUploadParams{
171+
File: bytes.NewReader(archive),
172+
Name: kernel.String(name),
173+
})
174+
if err != nil {
175+
recoveryErr := testAccRecoverAndTrackExtensionFixture(t, projectID, name)
176+
if recoveryErr != nil {
177+
t.Fatalf("upload Kernel extension fixture: %v; recover fixture by name: %v", err, recoveryErr)
178+
}
179+
t.Fatalf("upload Kernel extension fixture: %v", err)
180+
}
181+
if extension == nil || extension.ID == "" {
182+
if recoveryErr := testAccRecoverAndTrackExtensionFixture(t, projectID, name); recoveryErr != nil {
183+
t.Fatalf("Kernel returned an empty extension fixture response; recover fixture by name: %v", recoveryErr)
184+
}
185+
t.Fatal("Kernel returned an empty extension fixture response")
186+
}
187+
188+
testAccTrackExtensionFixture(t, projectID, extension.ID)
189+
190+
if extension.Name != name {
191+
t.Fatalf("uploaded Kernel extension name = %q, want %q", extension.Name, name)
192+
}
193+
if extension.SizeBytes != int64(len(archive)) {
194+
t.Fatalf("uploaded Kernel extension size = %d, want %d", extension.SizeBytes, len(archive))
195+
}
196+
return extension.ID
197+
}
198+
199+
func testAccRecoverAndTrackExtensionFixture(t *testing.T, projectID, name string) error {
200+
t.Helper()
201+
202+
recovered, err := testAccRecoverExtensionFixture(acctest.ClientFromEnv(), projectID, name)
203+
if recovered != nil {
204+
testAccTrackExtensionFixture(t, projectID, recovered.ID)
205+
}
206+
return err
207+
}
208+
209+
func testAccRecoverExtensionFixture(client extensionRecoveryClient, projectID, name string) (*kernel.ExtensionGetResponse, error) {
210+
ctx, cancel := context.WithTimeout(context.Background(), kernelclient.DefaultRequestTimeout)
211+
defer cancel()
212+
213+
extension, err := client.GetExtension(ctx, projectID, name)
214+
if projectscope.IsNotFound(err) {
215+
return nil, nil
216+
}
217+
if err != nil {
218+
return nil, err
219+
}
220+
if extension == nil || extension.ID == "" {
221+
return nil, fmt.Errorf("Kernel returned an empty extension response for %q", name)
222+
}
223+
return extension, nil
224+
}
225+
226+
func testAccTrackExtensionFixture(t *testing.T, projectID, id string) {
227+
t.Helper()
228+
229+
testAccRequireExtensionDeleted(t, projectID, id)
230+
acctest.CleanupExtension(t, projectID, id)
231+
}
232+
233+
func testAccRequireExtensionDeleted(t *testing.T, projectID, id string) {
234+
t.Helper()
235+
236+
t.Cleanup(func() {
237+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
238+
defer cancel()
239+
240+
_, err := acctest.ClientFromEnv().GetExtension(ctx, projectID, id)
241+
if projectscope.IsNotFound(err) {
242+
return
243+
}
244+
if err != nil {
245+
t.Errorf("read Kernel extension %s after cleanup: %v", id, err)
246+
return
247+
}
248+
t.Errorf("Kernel extension %s still exists after cleanup", id)
249+
})
250+
}
251+
252+
func testAccExtensionArchive(t *testing.T) []byte {
253+
t.Helper()
254+
255+
var archive bytes.Buffer
256+
writer := zip.NewWriter(&archive)
257+
testAccWriteExtensionFile(t, writer, "manifest.json", `{"manifest_version":3,"name":"Kernel Terraform acceptance","version":"1.0.0"}`)
258+
testAccWriteExtensionFile(t, writer, "marker.txt", "extension-data-source")
259+
if err := writer.Close(); err != nil {
260+
t.Fatalf("close extension ZIP: %v", err)
261+
}
262+
return archive.Bytes()
263+
}
264+
265+
func testAccWriteExtensionFile(t *testing.T, writer *zip.Writer, name, contents string) {
266+
t.Helper()
267+
268+
entry, err := writer.Create(name)
269+
if err != nil {
270+
t.Fatalf("create %s in extension ZIP: %v", name, err)
271+
}
272+
if _, err := entry.Write([]byte(contents)); err != nil {
273+
t.Fatalf("write %s in extension ZIP: %v", name, err)
274+
}
275+
}
276+
277+
func testAccExtensionDataSourceConfig(id, name, projectID string) string {
278+
return acctest.ProviderConfig() + fmt.Sprintf(`
279+
data "kernel_extension" "by_id" {
280+
id = %q
281+
project_id = %q
282+
}
283+
284+
data "kernel_extension" "by_name" {
285+
name = %q
286+
}
287+
`, id, projectID, name)
288+
}

0 commit comments

Comments
 (0)