Skip to content

Commit 60c955b

Browse files
committed
added unit test
1 parent 49f5828 commit 60c955b

2 files changed

Lines changed: 135 additions & 24 deletions

File tree

stackit/internal/services/iaas/image/resource.go

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ func (r *imageResource) Create(ctx context.Context, req resource.CreateRequest,
519519
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating image", "Error in config")
520520
return
521521
}
522-
file, err := downloadImage(ctx, &resp.Diagnostics, downloadModel.URL.ValueString())
522+
file, err := downloadImage(ctx, downloadModel.URL.ValueString())
523523
if err != nil {
524524
core.LogAndAddError(ctx, &resp.Diagnostics, "Error downloading image", fmt.Sprintf("Downloading Image: %v", err))
525525
return
@@ -1019,62 +1019,73 @@ func uploadImage(ctx context.Context, diags *diag.Diagnostics, filePath, uploadU
10191019
return nil
10201020
}
10211021

1022-
// file zurückgeben - unit test mock server (dummy file), file pointer checken | diags raus
1023-
func downloadImage(ctx context.Context, diags *diag.Diagnostics, downloadURL string) (*os.File, error) {
1022+
func downloadImage(ctx context.Context, downloadURL string) (*os.File, error) {
10241023
if downloadURL == "" {
1025-
return nil, fmt.Errorf("upload URL is empty")
1024+
return nil, fmt.Errorf("download URL is empty")
10261025
}
1026+
10271027
md5sum := fmt.Sprintf("%x", md5.Sum([]byte(downloadURL)))
1028-
// uuid?
1029-
// go tmp verzeichnis pro ressource -> kein konflikt
1030-
tmpDir, err := os.MkdirTemp("", "tf-prodiver-download-*")
1028+
1029+
tmpDir, err := os.MkdirTemp("", "tf-provider-download-*")
10311030
if err != nil {
10321031
return nil, fmt.Errorf("failed to create temp dir: %w", err)
10331032
}
1033+
10341034
filename := filepath.Join(tmpDir, md5sum+".img")
1035-
delFile := func() {
1036-
if err := os.Remove(filename); err != nil {
1037-
tflog.Debug(ctx, "failed to cleanup file")
1035+
1036+
cleanupOnErr := func() {
1037+
if err := os.RemoveAll(tmpDir); err != nil {
1038+
tflog.Warn(ctx, "failed to cleanup temp directory", map[string]interface{}{
1039+
"dir": tmpDir,
1040+
"error": err.Error(),
1041+
})
10381042
}
10391043
}
1040-
// TODO: retry
1044+
10411045
req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, nil)
10421046
if err != nil {
1047+
cleanupOnErr()
10431048
return nil, fmt.Errorf("create download request: %w", err)
10441049
}
10451050

10461051
client := &http.Client{}
10471052
resp, err := client.Do(req)
10481053
if err != nil {
1054+
cleanupOnErr()
10491055
return nil, fmt.Errorf("download image: %w", err)
10501056
}
10511057

10521058
defer func() {
1053-
err = resp.Body.Close()
1054-
if err != nil {
1055-
// can test if handled, return caller should care
1056-
core.LogAndAddError(ctx, diags, "Error downloading image", fmt.Sprintf("Closing response body: %v", err))
1059+
if err := resp.Body.Close(); err != nil {
1060+
tflog.Debug(ctx, "failed to close HTTP response body", map[string]interface{}{
1061+
"error": err.Error(),
1062+
})
10571063
}
10581064
}()
10591065

10601066
if resp.StatusCode != http.StatusOK {
1061-
return nil, fmt.Errorf("upload image: %s", resp.Status)
1067+
cleanupOnErr()
1068+
return nil, fmt.Errorf("download image unexpected status: %s", resp.Status)
10621069
}
10631070

1064-
file, err := os.CreateTemp("", filename)
1071+
file, err := os.Create(filename)
10651072
if err != nil {
1066-
delFile()
1073+
cleanupOnErr()
10671074
return nil, fmt.Errorf("creating file: %w", err)
10681075
}
1069-
defer func() {
1070-
err = resp.Body.Close()
1071-
if err != nil {
1072-
core.LogAndAddError(ctx, diags, "Error uploading image", fmt.Sprintf("Closing response body: %v", err))
1073-
}
1074-
}()
1076+
10751077
_, err = io.Copy(file, resp.Body)
10761078
if err != nil {
1079+
file.Close()
1080+
cleanupOnErr()
10771081
return nil, fmt.Errorf("writing to file: %w", err)
10781082
}
1083+
1084+
if _, err := file.Seek(0, 0); err != nil {
1085+
file.Close()
1086+
cleanupOnErr()
1087+
return nil, fmt.Errorf("seeking file: %w", err)
1088+
}
1089+
10791090
return file, nil
10801091
}

stackit/internal/services/iaas/image/resource_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package image
22

33
import (
4+
"bytes"
45
"context"
56
"fmt"
67
"net/http"
78
"net/http/httptest"
89
"net/url"
10+
"os"
911
"testing"
1012

1113
"github.com/google/go-cmp/cmp"
@@ -405,3 +407,101 @@ func Test_UploadImage(t *testing.T) {
405407
})
406408
}
407409
}
410+
411+
func Test_DownloadImage_EdgeCases(t *testing.T) {
412+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
413+
switch r.URL.Path {
414+
case "/404":
415+
w.WriteHeader(http.StatusNotFound)
416+
case "/empty":
417+
w.WriteHeader(http.StatusOK)
418+
case "/large":
419+
w.WriteHeader(http.StatusOK)
420+
_, _ = w.Write(bytes.Repeat([]byte("A"), 1024*1024))
421+
case "/drop-conn":
422+
hj, ok := w.(http.Hijacker)
423+
if !ok {
424+
http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError)
425+
return
426+
}
427+
conn, _, _ := hj.Hijack()
428+
_ = conn.Close()
429+
default:
430+
w.WriteHeader(http.StatusOK)
431+
_, _ = w.Write([]byte("dummy content"))
432+
}
433+
}))
434+
t.Cleanup(server.Close)
435+
436+
tests := []struct {
437+
name string
438+
ctx context.Context
439+
downloadURL string
440+
wantBytes []byte
441+
wantErr bool
442+
}{{
443+
name: "ok",
444+
ctx: context.Background(),
445+
downloadURL: server.URL,
446+
wantBytes: []byte("dummy content"),
447+
wantErr: false,
448+
},
449+
{
450+
name: "invalid_url_format",
451+
ctx: context.Background(),
452+
downloadURL: "http://127.0.0.1:0/invalid",
453+
wantErr: true,
454+
},
455+
{
456+
name: "status_404_not_found",
457+
ctx: context.Background(),
458+
downloadURL: server.URL + "/404",
459+
wantErr: true,
460+
},
461+
{
462+
name: "empty_body_200_ok",
463+
ctx: context.Background(),
464+
downloadURL: server.URL + "/empty",
465+
wantBytes: []byte(""),
466+
wantErr: false,
467+
},
468+
{
469+
name: "large_file_stream",
470+
ctx: context.Background(),
471+
downloadURL: server.URL + "/large",
472+
wantBytes: bytes.Repeat([]byte("A"), 1024*1024),
473+
wantErr: false,
474+
},
475+
{
476+
name: "connection_dropped_mid_stream",
477+
ctx: context.Background(),
478+
downloadURL: server.URL + "/drop-conn",
479+
wantErr: true,
480+
},
481+
}
482+
483+
for _, tt := range tests {
484+
t.Run(tt.name, func(t *testing.T) {
485+
file, err := downloadImage(tt.ctx, tt.downloadURL)
486+
if (err != nil) != tt.wantErr {
487+
t.Fatalf("downloadImage() error = %v, wantErr %v", err, tt.wantErr)
488+
}
489+
490+
if file != nil {
491+
t.Cleanup(func() {
492+
_ = file.Close()
493+
_ = os.Remove(file.Name())
494+
})
495+
496+
gotBytes, err := os.ReadFile(file.Name())
497+
if err != nil {
498+
t.Fatalf("failed to read downloaded file: %v", err)
499+
}
500+
501+
if !bytes.Equal(gotBytes, tt.wantBytes) {
502+
t.Errorf("byte mismatch: got length %d, want length %d", len(gotBytes), len(tt.wantBytes))
503+
}
504+
}
505+
})
506+
}
507+
}

0 commit comments

Comments
 (0)