karmada/operator/pkg/util/util_test.go

470 lines
12 KiB
Go

/*
Copyright 2023 The Karmada Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"archive/tar"
"bytes"
"compress/gzip"
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"k8s.io/utils/ptr"
"sigs.k8s.io/yaml"
)
// mockReader is a simple io.Reader that returns an error after being called.
type mockReader struct {
data []byte
err error
}
func (m *mockReader) Read(p []byte) (n int, err error) {
if m.data == nil {
return 0, m.err
}
n = copy(p, m.data)
m.data = m.data[n:]
return n, m.err
}
type mockRoundTripper struct {
response *http.Response
err error
}
func (m *mockRoundTripper) RoundTrip(*http.Request) (*http.Response, error) {
if m.err != nil {
return nil, m.err
}
return m.response, nil
}
func TestRead(t *testing.T) {
tests := []struct {
name string
downloader *Downloader
data string
prep func(downloader *Downloader, data string) error
wantErr bool
errMsg string
}{
{
name: "Read_FailedToReadFromDataSource_ReadFailed",
downloader: &Downloader{
Reader: &mockReader{
err: errors.New("unexpected read error"),
},
},
prep: func(*Downloader, string) error {
return nil
},
wantErr: true,
errMsg: "unexpected read error",
},
{
name: "Read_FailedToReadWithEOF_ReadFailed",
downloader: &Downloader{
Reader: &mockReader{
err: io.EOF,
},
},
prep: func(*Downloader, string) error {
return nil
},
wantErr: true,
errMsg: "EOF",
},
{
name: "Read_FromValidDataSource_ReadSucceeded",
downloader: &Downloader{
Current: 3,
Total: 10,
},
data: "test data",
prep: func(downloader *Downloader, data string) error {
downloader.Reader = &mockReader{data: []byte(data)}
return nil
},
wantErr: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if err := test.prep(test.downloader, test.data); err != nil {
t.Fatalf("failed to prep before reading the data, got: %v", err)
}
buffer := test.downloader.Reader.(*mockReader).data
_, err := test.downloader.Read(buffer)
if err == nil && test.wantErr {
t.Fatal("expected an error, but got none")
}
if err != nil && !test.wantErr {
t.Errorf("unexpected error, got: %v", err)
}
if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) {
t.Errorf("expected error message %s to be in %s", test.errMsg, err.Error())
}
if string(buffer) != test.data {
t.Errorf("expected read buffer data to be %s, but got %s", test.data, string(buffer))
}
})
}
}
func TestDownloadFile(t *testing.T) {
tests := []struct {
name string
url string
filePath string
prep func(url, filePath string) error
verify func(filePath string) error
wantErr bool
errMsg string
}{
{
name: "DownloadFile_UrlIsNotFound_FailedToGetResponse",
url: "not-found-url",
prep: func(url, _ string) error {
httpClient = http.Client{
Transport: &mockRoundTripper{
err: fmt.Errorf("failed to get url %s, url is not found", url),
},
Timeout: time.Second,
}
return nil
},
verify: func(string) error { return nil },
wantErr: true,
errMsg: "failed to get url not-found-url, url is not found",
},
{
name: "DownloadFile_ServiceIsUnavailable_FailedToReachTheService",
url: "https://www.example.com/test-file",
prep: func(_, _ string) error {
httpClient = http.Client{
Transport: &mockRoundTripper{
response: &http.Response{
StatusCode: http.StatusServiceUnavailable,
},
},
Timeout: time.Second,
}
return nil
},
verify: func(string) error { return nil },
wantErr: true,
errMsg: "failed to download file",
},
{
name: "DownloadFile_FileDownloaded_",
url: "https://www.example.com/test-file",
filePath: filepath.Join(os.TempDir(), "temp-download-file.txt"),
prep: func(_, filePath string) error {
// Create temp download filepath.
tempFile, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("failed to create temp download file: %w", err)
}
defer tempFile.Close()
// Create HTTP client.
httpClient = http.Client{
Transport: &mockRoundTripper{
response: &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte("Hello, World!"))),
ContentLength: int64(len("Hello, World!")),
},
},
Timeout: time.Second,
}
return nil
},
verify: func(filePath string) error {
// Read the content of the downloaded file.
content, err := os.ReadFile(filePath)
if err != nil {
return fmt.Errorf("failed to read file: %w", err)
}
// Verify the content of the file.
expected := "Hello, World!"
if string(content) != expected {
return fmt.Errorf("unexpected file content: got %q, want %q", string(content), expected)
}
if err := os.Remove(filePath); err != nil {
return fmt.Errorf("failed to clean up %s", filePath)
}
return nil
},
wantErr: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if err := test.prep(test.url, test.filePath); err != nil {
t.Fatalf("failed to prep before downloading the file, got: %v", err)
}
err := DownloadFile(test.url, test.filePath)
if err == nil && test.wantErr {
t.Fatal("expected an error, but got none")
}
if err != nil && !test.wantErr {
t.Errorf("unexpected error, got: %v", err)
}
if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) {
t.Errorf("expected error message %s to be in %s", test.errMsg, err.Error())
}
if err := test.verify(test.filePath); err != nil {
t.Errorf("failed to verify the actual of download of file: %v", err)
}
})
}
}
func TestUnpack(t *testing.T) {
tests := []struct {
name string
tarFile string
regularFile string
targetPath *string
prep func(tarFile, regularFile string, targetPath *string) error
verify func(regularFile string, targetPath string) error
wantErr bool
errMsg string
}{
{
name: "Unpack_InvalidGzipFileHeader_InvalidHeader",
tarFile: "invalid.tar.gz",
targetPath: ptr.To(""),
prep: func(tarFile, _ string, targetPath *string) error {
var err error
*targetPath, err = os.MkdirTemp("", "test-unpack-*")
if err != nil {
return err
}
f, err := os.Create(filepath.Join(*targetPath, tarFile))
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString("Invalid gzip content")
return err
},
verify: func(_, targetPath string) error {
return os.RemoveAll(targetPath)
},
wantErr: true,
errMsg: gzip.ErrHeader.Error(),
},
{
name: "Unpack_ValidTarGzipped_UnpackedSuccessfully",
tarFile: "valid.tar.gz",
regularFile: "test-file.txt",
targetPath: ptr.To(""),
prep: verifyValidTarGzipped,
verify: func(regularFile string, targetPath string) error {
fileExpected := filepath.Join(targetPath, "test", regularFile)
_, err := os.Stat(fileExpected)
if err != nil {
return fmt.Errorf("failed to find the file %s, got error: %v", fileExpected, err)
}
return os.RemoveAll(targetPath)
},
wantErr: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if err := test.prep(test.tarFile, test.regularFile, test.targetPath); err != nil {
t.Fatalf("failed to prep before unpacking the tar file: %v", err)
}
err := Unpack(filepath.Join(*test.targetPath, test.tarFile), *test.targetPath)
if err == nil && test.wantErr {
t.Fatal("expected an error, but got none")
}
if err != nil && !test.wantErr {
t.Errorf("unexpected error, got: %v", err)
}
if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) {
t.Errorf("expected error message %s to be in %s", test.errMsg, err.Error())
}
if err := test.verify(test.regularFile, *test.targetPath); err != nil {
t.Errorf("failed to verify unpacking process, got: %v", err)
}
})
}
}
func TestListFileWithSuffix(t *testing.T) {
suffix := ".yaml"
files := ListFileWithSuffix(".", suffix)
want := 2
got := len(files)
if want != got {
t.Errorf("Expected %d ,but got :%d", want, got)
}
for _, f := range files {
if !strings.HasSuffix(f.AbsPath, suffix) {
t.Errorf("Want suffix with %s , but not exist, path is:%s", suffix, f.AbsPath)
}
}
}
// verifyValidTarGzipped creates a tar.gz file in a temporary directory.
// The archive contains a "test" directory and a file with the specified name,
// containing the message "Hello, World!".
func verifyValidTarGzipped(tarFile, regularFile string, targetPath *string) error {
var err error
*targetPath, err = os.MkdirTemp("", "test-unpack-*")
if err != nil {
return err
}
f, err := os.Create(filepath.Join(*targetPath, tarFile))
if err != nil {
return err
}
defer f.Close()
// Create a gzip writer.
gw := gzip.NewWriter(f)
defer gw.Close()
// Create a tar writer.
tw := tar.NewWriter(gw)
defer tw.Close()
// Add a directory to the tar.
if err := tw.WriteHeader(&tar.Header{
Name: "test" + string(filepath.Separator),
Typeflag: tar.TypeDir,
Mode: 0755,
}); err != nil {
return err
}
// Add a file to the tar.
message := "Hello, World!"
if err := tw.WriteHeader(&tar.Header{
Name: filepath.Join("test", regularFile),
Mode: 0644,
Size: int64(len(message)),
Typeflag: tar.TypeReg,
}); err != nil {
return err
}
if _, err := tw.Write([]byte(message)); err != nil {
return err
}
return nil
}
func TestReplaceYamlForRegs(t *testing.T) {
tests := []struct {
name string
content string
replacements map[*regexp.Regexp]string
expectedContent string
}{
{
name: "simple replacement",
content: `
url: "https://{{name}}.{{namespace}}.svc:443/convert"
caBundle: "{{caBundle}}"
`,
replacements: map[*regexp.Regexp]string{
regexp.MustCompile("{{caBundle}}"): "testCaBundle",
regexp.MustCompile("{{name}}"): "testName",
regexp.MustCompile("{{namespace}}"): "testNamespace",
},
expectedContent: `
url: "https://testName.testNamespace.svc:443/convert"
caBundle: "testCaBundle"
`,
},
{
name: "partial replacement",
content: `
url: "https://{{name}}.{{namespace}}.svc:443/convert"
caBundle: "{{caBundle}}"
`,
replacements: map[*regexp.Regexp]string{
regexp.MustCompile("{{caBundle}}"): "testCaBundle",
regexp.MustCompile("{{namespace}}"): "testNamespace",
},
expectedContent: `
url: "https://{{name}}.testNamespace.svc:443/convert"
caBundle: "testCaBundle"
`,
},
{
name: "redundant replacement",
content: `
url: "https://{{name}}.{{namespace}}.svc:443/convert"
caBundle: "{{caBundle}}"
`,
replacements: map[*regexp.Regexp]string{
regexp.MustCompile("{{caBundle}}"): "testCaBundle",
regexp.MustCompile("{{name}}"): "testName",
regexp.MustCompile("{{namespace}}"): "testNamespace",
regexp.MustCompile("{{foo}}"): "foo",
},
expectedContent: `
url: "https://testName.testNamespace.svc:443/convert"
caBundle: "testCaBundle"
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpFile, err := os.CreateTemp("", "example")
if err != nil {
t.Fatalf("failed to create temp file: %v", err)
}
defer os.Remove(tmpFile.Name())
if _, err := tmpFile.Write([]byte(tt.content)); err != nil {
t.Fatalf("failed to write temp file: %v", err)
}
if err := tmpFile.Close(); err != nil {
t.Fatalf("failed to close temp file: %v", err)
}
result, err := ReplaceYamlForRegs(tmpFile.Name(), tt.replacements)
expectedJSON, expectedErr := yaml.YAMLToJSON([]byte(tt.expectedContent))
assert.Equal(t, result, expectedJSON)
assert.Equal(t, err, expectedErr)
})
}
}