diff --git a/internal/feature_test.go b/internal/feature_test.go index c9005ef..549aee4 100644 --- a/internal/feature_test.go +++ b/internal/feature_test.go @@ -29,8 +29,8 @@ const ( testCert = "storage/testdata/valid_cert.pem" testKey = "storage/testdata/valid_key.pem" - noProgress = -12345 - noMessage = "no message" + noMessage = "no message" + anyErrorMessage = "*" statusParam = "status" progressParam = "progress" @@ -154,16 +154,16 @@ func testScriptBasedSoftwareUpdatableOperations(noResume bool, t *testing.T) { defer feature.Disconnect(true) if noResume { - testDownloadInstall(feature, mc, w.GenerateSoftwareArtifacts(false, "install"), t) + testDownloadInstall(feature, mc, w.GenerateSoftwareArtifacts(false, "install"), true, "*", t) feature.serverCert = testCert - testDownloadInstall(feature, mc, wSecure.GenerateSoftwareArtifacts(true, "install"), t) + testDownloadInstall(feature, mc, wSecure.GenerateSoftwareArtifacts(true, "install"), true, "*", t) } else { testDisconnect(feature, mc, w.GenerateSoftwareArtifacts(false, "install"), t) } } func testDisconnect(feature *ScriptBasedSoftwareUpdatable, mc *mockedClient, artifacts []*hawkbit.SoftwareArtifactAction, t *testing.T) { - sua := prepareSoftwareUpdateAction(artifacts) + sua := prepareSoftwareUpdateAction(artifacts, "") testDisconnectWhileRunningOperation(feature, mc, sua, false, t) // disconnect while downloading testDisconnectWhileRunningOperation(feature, mc, sua, true, t) // disconnect while installing } @@ -177,6 +177,7 @@ func testDisconnectWhileRunningOperation(feature *ScriptBasedSoftwareUpdatable, } else { feature.downloadHandler(sua, feature.su) } + // only 1 artifact here preDisconnectEventCount := 2 // STARTED, DOWNLOADING postDisconnectEventCount := 3 // DOWNLOADING(100)/INSTALLING(100), DOWNLOADED/INSTALLED, FINISHED_SUCCESS if install { @@ -193,69 +194,220 @@ func testDisconnectWhileRunningOperation(feature *ScriptBasedSoftwareUpdatable, waitDisconnect.Wait() defer connectFeature(t, mc, feature, getDefaultFlagValue(t, flagFeatureID)) if install { - checkInstallStatusEvents(statuses, t) + checkInstallStatusEvents(0, statuses, t) } else { - checkDownloadStatusEvents(statuses, t) + checkDownloadStatusEvents(0, statuses, t) } } +// TestScriptBasedDownloadAndInstallMixedResources tests ScriptBasedSoftwareUpdatable core functionality: init, install and download operations, +// working with both downloadable and local resources +func TestScriptBasedDownloadAndInstallMixedResources(t *testing.T) { + // Prepare + storageDir := assertDirs(t, testDirFeature, false) + // Remove temporary directory at the end. + defer os.RemoveAll(storageDir) + + feature, mc, err := mockScriptBasedSoftwareUpdatable(t, &testConfig{ + clientConnected: true, featureID: getDefaultFlagValue(t, flagFeatureID), storageLocation: storageDir, mode: modeLax, + }) + if err != nil { + t.Fatalf("failed to initialize ScriptBasedSoftwareUpdatable: %v", err) + } + defer feature.Disconnect(true) + + a, aBody := "a.txt", "test" + aPath, aHash := createLocalArtifact(t, storageDir, a, aBody) + b, bBody := "b.txt", "test" + bPath, bHash := createLocalArtifact(t, storageDir, b, bBody) + + // Prepare/Close simple HTTP server used to host testing artifacts + w := storage.NewTestHTTPServer(testDefaultHost, "", 0, t) + w.Host(true, false, "", "") + w.AddInstallScript() + defer w.Close() + + installScript := w.GenerateSoftwareArtifacts(false, "install") + artifacts := []*hawkbit.SoftwareArtifactAction{ + convertLocalArtifact(aPath, a, aHash, len(aBody)), + convertLocalArtifact(bPath, b, bHash, len(bBody)), + installScript[0], + } + + testDownloadInstall(feature, mc, artifacts, true, b, t) +} + +// TestScriptBasedDownloadAndInstallLocalResources tests ScriptBasedSoftwareUpdatable core functionality: init, install and download operations, +// but working with local resources +func TestScriptBasedDownloadAndInstallLocalResources(t *testing.T) { + tmpDir := "testdata" + assertDirs(t, tmpDir, true) + defer os.RemoveAll(tmpDir) + + installScriptAlias, installScriptBody := storage.GetTestInstallScript() + installScriptPath, installScriptHash := createLocalArtifact(t, tmpDir, installScriptAlias, installScriptBody) + localResourceAlias, localResourceBody := "local.txt", "test" + localResourcePath, localResourceHash := createLocalArtifact(t, tmpDir, localResourceAlias, localResourceBody) + + t.Run("Test_without_copying_artifacts", func(t *testing.T) { + artifacts := []*hawkbit.SoftwareArtifactAction{ + convertLocalArtifact(installScriptPath, installScriptAlias, installScriptHash, len(installScriptBody)), + convertLocalArtifact(getAbsolutePath(t, localResourcePath), localResourceAlias, localResourceHash, len(localResourceBody)), + } + testScriptBasedSoftwareUpdatableOperationsLocal(t, []string{}, artifacts, modeLax, "", true) + checkFileExistsWithContent(t, filepath.Join(tmpDir, "status"), "message=My final message!") // install file was executed in correct folder + }) + + t.Run("Test_with_correct_install_dirs", func(t *testing.T) { + artifacts := []*hawkbit.SoftwareArtifactAction{ + convertLocalArtifact(installScriptAlias, installScriptAlias, installScriptHash, len(installScriptBody)), + convertLocalArtifact(getAbsolutePath(t, localResourcePath), localResourceAlias, localResourceHash, len(localResourceBody)), + } + testScriptBasedSoftwareUpdatableOperationsLocal(t, []string{tmpDir}, artifacts, modeLax, "*", true) + testScriptBasedSoftwareUpdatableOperationsLocal(t, []string{tmpDir}, artifacts, modeStrict, "*", true) + }) + + t.Run("Test_with_no_install_dirs", func(t *testing.T) { + artifacts := []*hawkbit.SoftwareArtifactAction{ + convertLocalArtifact(installScriptPath, installScriptAlias, installScriptHash, len(installScriptBody)), + convertLocalArtifact(getAbsolutePath(t, localResourcePath), localResourceAlias, localResourceHash, len(localResourceBody)), + } + testScriptBasedSoftwareUpdatableOperationsLocal(t, []string{}, artifacts, modeLax, "*", true) + testScriptBasedSoftwareUpdatableOperationsLocal(t, []string{}, artifacts, modeStrict, "*", false) + testScriptBasedSoftwareUpdatableOperationsLocal(t, []string{}, artifacts, modeScoped, "*", false) + }) + + t.Run("Test_with_incorrect_install_dirs", func(t *testing.T) { + artifacts := []*hawkbit.SoftwareArtifactAction{ + convertLocalArtifact(getAbsolutePath(t, installScriptPath), installScriptAlias, installScriptHash, len(installScriptBody)), + } + + installDirs := []string{filepath.Join(tmpDir, "test")} + assertDirs(t, installDirs[0], true) + testScriptBasedSoftwareUpdatableOperationsLocal(t, installDirs, artifacts, modeLax, "*", true) + testScriptBasedSoftwareUpdatableOperationsLocal(t, installDirs, artifacts, modeScoped, "*", false) + }) +} + +func testScriptBasedSoftwareUpdatableOperationsLocal(t *testing.T, installDirs []string, + artifacts []*hawkbit.SoftwareArtifactAction, mode string, copyArtifacts string, expectedSuccess bool) { + // Prepare + dir := assertDirs(t, testDirFeature, false) + // Remove temporary directory at the end. + defer os.RemoveAll(dir) + + feature, mc, err := mockScriptBasedSoftwareUpdatable(t, &testConfig{ + clientConnected: true, featureID: getDefaultFlagValue(t, flagFeatureID), storageLocation: dir, + installDirs: installDirs, mode: mode}) + if err != nil { + t.Fatalf("failed to initialize ScriptBasedSoftwareUpdatable: %v", err) + } + defer feature.Disconnect(true) + + testDownloadInstall(feature, mc, artifacts, expectedSuccess, copyArtifacts, t) +} + func pullStatusChanges(mc *mockedClient, expectedCount int) []interface{} { var statuses []interface{} for i := 0; i < expectedCount; i++ { lo := mc.pullLastOperationStatus() statuses = append(statuses, lo) + if lo["status"] == string(hawkbit.StatusFinishedSuccess) || lo["status"] == string(hawkbit.StatusFinishedError) { + break + } } return statuses } -func testDownloadInstall(feature *ScriptBasedSoftwareUpdatable, mc *mockedClient, artifacts []*hawkbit.SoftwareArtifactAction, t *testing.T) { - sua := prepareSoftwareUpdateAction(artifacts) +func testDownloadInstall(feature *ScriptBasedSoftwareUpdatable, mc *mockedClient, artifacts []*hawkbit.SoftwareArtifactAction, + expectedSuccess bool, copyArtifacts string, t *testing.T) { + sua := prepareSoftwareUpdateAction(artifacts, copyArtifacts) + + extraDownloadingEventsCount := getCopiedArtifactsCount(artifacts, copyArtifacts) + if extraDownloadingEventsCount > 0 { + extraDownloadingEventsCount-- // 1 artifact expected in default case + } // Try to execute a simple download operation. feature.downloadHandler(sua, feature.su) - statuses := pullStatusChanges(mc, 5) // STARTED, DOWNLOADING, DOWNLOADING(100), DOWNLOADED, FINISHED_SUCCESS - checkDownloadStatusEvents(statuses, t) + statuses := pullStatusChanges(mc, 5+extraDownloadingEventsCount) // STARTED, DOWNLOADING, DOWNLOADING(x extraDownloadingEventsCount), DOWNLOADING(100), DOWNLOADED, FINISHED_SUCCESS + if expectedSuccess { + checkDownloadStatusEvents(extraDownloadingEventsCount, statuses, t) + if copyArtifacts == "" { + if !checkNoFilesCopied(t, filepath.Join(testDirFeature, "download", "0", "0"), true) { + checkNoFilesCopied(t, filepath.Join(testDirFeature, "modules", "0"), false) + } + } + } else { + checkDownloadFailedStatusEvents(statuses, t) + } // Try to execute a simple install operation. feature.installHandler(sua, feature.su) - statuses = pullStatusChanges(mc, 8) // STARTED, DOWNLOADING, DOWNLOADING(100), DOWNLOADED, + statuses = pullStatusChanges(mc, 8+extraDownloadingEventsCount) // STARTED, DOWNLOADING, DOWNLOADING(x extraDownloadingEventsCount), DOWNLOADING(100), DOWNLOADED, // INSTALLING, INSTALLING(100), INSTALLED, FINISHED_SUCCESS - checkInstallStatusEvents(statuses, t) + if expectedSuccess { + checkInstallStatusEvents(extraDownloadingEventsCount, statuses, t) + } else { + checkDownloadFailedStatusEvents(statuses, t) + } } -func createStatus(status hawkbit.Status, progress float64, message string) map[string]interface{} { - return map[string]interface{}{ - statusParam: string(status), - progressParam: progress, - messageParam: message, +func createStatus(status hawkbit.Status, progress func(float64) bool, message string) map[string]interface{} { + result := make(map[string]interface{}) + result[statusParam] = string(status) + result[messageParam] = message + if progress != nil { + result[progressParam] = progress } + return result } -func checkDownloadStatusEvents(actualStatuses []interface{}, t *testing.T) { +func checkDownloadFailedStatusEvents(actualStatuses []interface{}, t *testing.T) { var expectedStatuses []interface{} expectedStatuses = append(expectedStatuses, - createStatus(hawkbit.StatusStarted, noProgress, noMessage), - createStatus(hawkbit.StatusDownloading, noProgress, noMessage), - createStatus(hawkbit.StatusDownloading, 100.0, noMessage), - createStatus(hawkbit.StatusDownloaded, 100.0, noMessage), - createStatus(hawkbit.StatusFinishedSuccess, noProgress, noMessage), + createStatus(hawkbit.StatusStarted, nil, noMessage), + createStatus(hawkbit.StatusDownloading, nil, noMessage), + createStatus(hawkbit.StatusFinishedError, nil, anyErrorMessage), ) checkStatusEvents(expectedStatuses, actualStatuses, t) } -func checkInstallStatusEvents(actualStatuses []interface{}, t *testing.T) { +func checkDownloadStatusEvents(extraDownloadingEventsCount int, actualStatuses []interface{}, t *testing.T) { var expectedStatuses []interface{} expectedStatuses = append(expectedStatuses, - createStatus(hawkbit.StatusStarted, noProgress, noMessage), - createStatus(hawkbit.StatusDownloading, noProgress, noMessage), - createStatus(hawkbit.StatusDownloading, 100.0, noMessage), - createStatus(hawkbit.StatusDownloaded, 100.0, noMessage), - createStatus(hawkbit.StatusInstalling, noProgress, noMessage), - createStatus(hawkbit.StatusInstalling, noProgress, "My final message!"), - createStatus(hawkbit.StatusInstalled, noProgress, "My final message!"), - createStatus(hawkbit.StatusFinishedSuccess, noProgress, "My final message!"), + createStatus(hawkbit.StatusStarted, nil, noMessage), + createStatus(hawkbit.StatusDownloading, nil, noMessage), + ) + for i := 0; i < extraDownloadingEventsCount; i++ { + expectedStatuses = append(expectedStatuses, createStatus(hawkbit.StatusDownloading, partialDownload, noMessage)) + } + expectedStatuses = append(expectedStatuses, + createStatus(hawkbit.StatusDownloading, completeDownload, noMessage), + createStatus(hawkbit.StatusDownloaded, completeDownload, noMessage), + createStatus(hawkbit.StatusFinishedSuccess, nil, noMessage), + ) + checkStatusEvents(expectedStatuses, actualStatuses, t) +} + +func checkInstallStatusEvents(extraDownloadingEventsCount int, actualStatuses []interface{}, t *testing.T) { + var expectedStatuses []interface{} + expectedStatuses = append(expectedStatuses, + createStatus(hawkbit.StatusStarted, nil, noMessage), + createStatus(hawkbit.StatusDownloading, nil, noMessage), + ) + for i := 0; i < extraDownloadingEventsCount; i++ { + expectedStatuses = append(expectedStatuses, createStatus(hawkbit.StatusDownloading, partialDownload, noMessage)) + } + expectedStatuses = append(expectedStatuses, + createStatus(hawkbit.StatusDownloading, completeDownload, noMessage), + createStatus(hawkbit.StatusDownloaded, completeDownload, noMessage), + createStatus(hawkbit.StatusInstalling, nil, noMessage), + createStatus(hawkbit.StatusInstalling, nil, "My final message!"), + createStatus(hawkbit.StatusInstalled, nil, "My final message!"), + createStatus(hawkbit.StatusFinishedSuccess, nil, "My final message!"), ) checkStatusEvents(expectedStatuses, actualStatuses, t) } @@ -269,10 +421,10 @@ func checkStatusEvents(expectedStatuses []interface{}, actualStatuses []interfac expected := el.(map[string]interface{}) actual := actualStatuses[i].(map[string]interface{}) if expected[statusParam] != actual[statusParam] { - t.Fatalf("received unexpected lastOperation status: %s != %s(actual)", expected[statusParam], actual[statusParam]) + t.Fatalf("received unexpected lastOperation status: %v != %v(actual)", expected[statusParam], actual[statusParam]) } - checkStatusParameter(expected[progressParam].(float64), actual, progressParam, - expected[progressParam].(float64) == noProgress, t) + checkStatusParameter(expected[progressParam], actual, progressParam, + expected[progressParam] == nil, t) checkStatusParameter(expected[messageParam], actual, messageParam, expected[messageParam] == noMessage, t) } } @@ -284,22 +436,26 @@ func checkStatusParameter(expectedParamValue interface{}, actualStatus map[strin if noValue { t.Fatalf("no %s expected in payload: %v", name, actualStatus) } - if expectedParamValue != receivedValue { - t.Fatalf("received unexpected lastOperation %s: %s != %s", name, receivedValue, expectedParamValue) + if checkProgressFunc, ok := expectedParamValue.(func(float64) bool); ok { + if !checkProgressFunc(receivedValue.(float64)) { + t.Fatalf("received unacceptable lastOperation %s: %v", name, receivedValue) + } + } else if expectedParamValue != anyErrorMessage && expectedParamValue != receivedValue { + t.Fatalf("received unexpected lastOperation %s: %v != %v", name, receivedValue, expectedParamValue) } } else if !noValue { t.Fatalf("no %s found in payload: %v", name, actualStatus) } } -func prepareSoftwareUpdateAction(artifacts []*hawkbit.SoftwareArtifactAction) *hawkbit.SoftwareUpdateAction { +func prepareSoftwareUpdateAction(artifacts []*hawkbit.SoftwareArtifactAction, copyArtifacts string) *hawkbit.SoftwareUpdateAction { // Prepare simple software update action. return &hawkbit.SoftwareUpdateAction{ CorrelationID: "test-correlation-id", SoftwareModules: []*hawkbit.SoftwareModuleAction{{ SoftwareModule: &hawkbit.SoftwareModuleID{Name: "test", Version: "1.0.0"}, Artifacts: artifacts, - Metadata: map[string]string{"artifact-type": "plane"}, + Metadata: map[string]string{"artifact-type": "plane", "copy-artifacts": copyArtifacts}, }}, } } diff --git a/internal/storage/download_test.go b/internal/storage/download_test.go index 0c22af6..e36f3f3 100644 --- a/internal/storage/download_test.go +++ b/internal/storage/download_test.go @@ -223,6 +223,39 @@ func testDownloadToFile(arts []*Artifact, certFile, certKey string, t *testing.T } } +// TestDownloadToFileLocalLink tests downloadToFile function, using local files as artifact links. +func TestDownloadToFileLocalLink(t *testing.T) { + size := int64(65536) + name := "local.txt" + file, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + t.Fatal("failed to create temp file", err) + } + defer os.Remove(name) + write(file, size, false) + file.Close() + testDownloadToFile([]*Artifact{ + { // A Local Artifact with MD5 checksum. + FileName: name, Size: int(size), Link: name, + HashType: "MD5", + HashValue: "ab2ce340d36bbaafe17965a3a2c6ed5b", + Local: true, + }, + { // A Local Artifact with SHA1 checksum. + FileName: name, Size: int(size), Link: name, + HashType: "SHA1", + HashValue: "cd3848697cb42f5be9902f6523ec516d21a8c677", + Local: true, + }, + { // A Local Artifact with SHA256 checksum. + FileName: name, Size: int(size), Link: name, + HashType: "SHA256", + HashValue: "4eefb9a7a40a8b314b586a00f307157043c0bbe4f59fa39cba88773680758bc3", + Local: true, + }, + }, "", "", t) +} + // TestDownloadToFileError tests downloadToFile function for some edge cases. func TestDownloadToFileError(t *testing.T) { // Prepare diff --git a/internal/storage/http_test_util.go b/internal/storage/http_test_util.go index def5a0d..e9ec2dc 100644 --- a/internal/storage/http_test_util.go +++ b/internal/storage/http_test_util.go @@ -16,6 +16,7 @@ import ( "crypto/sha256" "encoding/hex" "fmt" + "io" "net/http" "net/url" "runtime" @@ -112,11 +113,16 @@ func (w *TestHTTPServer) setAlias(alias string, body string) { // AddInstallScript adds install script HTTP alias (only once). func (w *TestHTTPServer) AddInstallScript() { + name, body := GetTestInstallScript() + w.setAlias(name, body) +} + +// GetTestInstallScript generates test install script for windows/linux +func GetTestInstallScript() (string, string) { if runtime.GOOS == "windows" { - w.setAlias("install.bat", "@echo off\n(\necho message=My final message!) > status\nping 127.0.0.1\n") - } else { - w.setAlias("install.sh", "#!/bin/sh\necho 'message=My final message!\n' > status\nsleep 5\n") + return "install.bat", "@echo off\n(\necho message=My final message!) > status\nping 127.0.0.1\n" } + return "install.sh", "#!/bin/sh\necho 'message=My final message!\n' > status\nsleep 5\n" } // Close closes the http server. @@ -199,7 +205,7 @@ func (w *TestHTTPServer) handlerSimple(writer http.ResponseWriter, request *http } // write file content. -func write(writer http.ResponseWriter, size int64, corruptFile bool) { +func write(writer io.Writer, size int64, corruptFile bool) { b := []byte("11111111111111111111111111111111111111111111111111") if corruptFile { b[0] = '0' diff --git a/internal/storage/storage_test.go b/internal/storage/storage_test.go index 10c6f96..3540d45 100644 --- a/internal/storage/storage_test.go +++ b/internal/storage/storage_test.go @@ -239,7 +239,7 @@ func TestDownloadArchiveModule(t *testing.T) { } existence(filepath.Join(path, art.FileName), true, "[initial download]", t) - // 2. Archvie module. + // 2. Archive module. if err := WriteLn(filepath.Join(path, InternalStatusName), m.Name+":"+m.Version); err != nil { t.Fatalf("fail to write module id: %v", err) } @@ -251,6 +251,16 @@ func TestDownloadArchiveModule(t *testing.T) { // 3. Download previous module with progress. path = filepath.Join(store.DownloadPath, "0", "1") progress := func(percent int) { /* Do nothing. */ } + + // Download with validation error + validationErr := fmt.Errorf("test validation error") + validationFail := func() error { + return validationErr + } + if err := store.DownloadModule(path, m, progress, "", 0, 0, validationFail); err != validationErr { + t.Errorf("unexpected validation error") + } + if err := store.DownloadModule(path, m, progress, "", 0, 0, nil); err != nil { t.Errorf("fail to download module: %v", err) } diff --git a/internal/storage/utils_test.go b/internal/storage/utils_test.go index 76f48a4..9cc0c8e 100644 --- a/internal/storage/utils_test.go +++ b/internal/storage/utils_test.go @@ -20,9 +20,11 @@ import ( "github.com/eclipse-kanto/software-update/hawkbit" ) -type ah struct { - Hash hawkbit.Hash - Protocol hawkbit.Protocol +type artifactData struct { + hash hawkbit.Hash + protocol hawkbit.Protocol + local bool + copy bool } // ----- ReadLn & WriteLn ----- ----- ----- ----- ----- ----- ----- ----- ----- @@ -300,29 +302,51 @@ func TestToModule(t *testing.T) { a2.Checksums[hawkbit.SHA1] = "sha1-value" a2.Download[hawkbit.HTTP] = &hawkbit.Links{URL: "http://test.me", MD5URL: ""} + a3 := &hawkbit.SoftwareArtifactAction{ + Filename: "test3.txt", + Size: 333, + Checksums: make(map[hawkbit.Hash]string), + Download: make(map[hawkbit.Protocol]*hawkbit.Links), + } + a3.Checksums[hawkbit.SHA1] = "sha1-value" + a3.Download[ProtocolFile] = &hawkbit.Links{URL: "/var/tmp/test3.txt", MD5URL: ""} + + a4 := &hawkbit.SoftwareArtifactAction{ + Filename: "test4.txt", + Size: 444, + Checksums: make(map[hawkbit.Hash]string), + Download: make(map[hawkbit.Protocol]*hawkbit.Links), + } + a4.Checksums[hawkbit.SHA1] = "sha1-value" + a4.Download[ProtocolFile] = &hawkbit.Links{URL: "/var/tmp/test4.txt", MD5URL: ""} + expected := hawkbit.SoftwareModuleAction{ SoftwareModule: &hawkbit.SoftwareModuleID{Name: "name", Version: "1.0.0"}, - Artifacts: []*hawkbit.SoftwareArtifactAction{a1, a2}} + Artifacts: []*hawkbit.SoftwareArtifactAction{a1, a2, a3, a4}, + Metadata: map[string]string{"copy-artifacts": "test3.txt,invalid.txt"}, + } // 1. Validate with two correct artifacts actual, err := toModule(expected) if err != nil { t.Errorf("unexpected error: %v", err) } - eah := []ah{ - {Hash: hawkbit.MD5, Protocol: hawkbit.HTTPS}, - {Hash: hawkbit.SHA1, Protocol: hawkbit.HTTP}, + eah := []artifactData{ + {hash: hawkbit.MD5, protocol: hawkbit.HTTPS, local: false, copy: false}, + {hash: hawkbit.SHA1, protocol: hawkbit.HTTP, local: false, copy: false}, + {hash: hawkbit.SHA1, protocol: ProtocolFile, local: true, copy: true}, + {hash: hawkbit.SHA1, protocol: ProtocolFile, local: true, copy: false}, } validateModule(expected, actual, eah, t) // 2. Validate with two correct artifacts and one wrong artifact - a3 := &hawkbit.SoftwareArtifactAction{ - Filename: "test3.txt", - Size: 333, + a5 := &hawkbit.SoftwareArtifactAction{ + Filename: "test5.txt", + Size: 555, Checksums: make(map[hawkbit.Hash]string), Download: make(map[hawkbit.Protocol]*hawkbit.Links), } - expected.Artifacts = append(expected.Artifacts, a3) + expected.Artifacts = append(expected.Artifacts, a5) if _, err = toModule(expected); err == nil { t.Errorf("an error was expected for wrong artifact") } @@ -344,44 +368,44 @@ func TestToArtifact(t *testing.T) { expected.Download[hawkbit.HTTP] = &hawkbit.Links{URL: "http://test.me", MD5URL: ""} // 1. Validate with MD5 and HTTP - actual, err := toArtifact(expected, true) + actual, err := toArtifact(expected, false) if err != nil { t.Errorf("unexpected error: %v", err) } - validateArtifact(expected, actual, ah{Hash: hawkbit.MD5, Protocol: hawkbit.HTTP}, t) + validateArtifact(expected, actual, artifactData{hash: hawkbit.MD5, protocol: hawkbit.HTTP}, t) // 2. Validate with SHA1 and HTTPS expected.Checksums[hawkbit.SHA1] = "sha1-value" expected.Download[hawkbit.HTTPS] = &hawkbit.Links{URL: "https://test.me", MD5URL: ""} - actual, err = toArtifact(expected, true) + actual, err = toArtifact(expected, false) if err != nil { t.Errorf("unexpected error: %v", err) } - validateArtifact(expected, actual, ah{Hash: hawkbit.SHA1, Protocol: hawkbit.HTTPS}, t) + validateArtifact(expected, actual, artifactData{hash: hawkbit.SHA1, protocol: hawkbit.HTTPS}, t) // 3. Validate with SHA256 and HTTPS expected.Checksums[hawkbit.SHA256] = "sha256-value" - actual, err = toArtifact(expected, true) + actual, err = toArtifact(expected, false) if err != nil { t.Errorf("unexpected error: %v", err) } - validateArtifact(expected, actual, ah{Hash: hawkbit.SHA256, Protocol: hawkbit.HTTPS}, t) + validateArtifact(expected, actual, artifactData{hash: hawkbit.SHA256, protocol: hawkbit.HTTPS}, t) // 4. Validate for unknown/missing Hash expected.Checksums = make(map[hawkbit.Hash]string) - if _, err = toArtifact(expected, true); err == nil { + if _, err = toArtifact(expected, false); err == nil { t.Errorf("an error was expected for unknown or missing hash") } // 5. Validate for unknown/missing link expected.Download = make(map[hawkbit.Protocol]*hawkbit.Links) expected.Download[hawkbit.FTP] = &hawkbit.Links{URL: "ftp://test.me", MD5URL: ""} - if _, err = toArtifact(expected, true); err == nil { + if _, err = toArtifact(expected, false); err == nil { t.Errorf("an error was expected for unknown or missing link") } } -func validateModule(expected hawkbit.SoftwareModuleAction, actual *Module, ahs []ah, t *testing.T) { +func validateModule(expected hawkbit.SoftwareModuleAction, actual *Module, ahs []artifactData, t *testing.T) { if expected.SoftwareModule.Name != actual.Name { t.Errorf("wrong module name: %s != %s", expected.SoftwareModule.Name, actual.Name) } @@ -396,21 +420,24 @@ func validateModule(expected hawkbit.SoftwareModuleAction, actual *Module, ahs [ } } -func validateArtifact(expected *hawkbit.SoftwareArtifactAction, actual *Artifact, ah ah, t *testing.T) { +func validateArtifact(expected *hawkbit.SoftwareArtifactAction, actual *Artifact, ah artifactData, t *testing.T) { if expected.Filename != actual.FileName { t.Errorf("wrong artifact file name: %s != %s", expected.Filename, actual.FileName) } if expected.Size != actual.Size { t.Errorf("wrong artifact size: %v != %v", expected.Size, actual.Size) } - if string(ah.Hash) != actual.HashType { - t.Errorf("wrong artifact hash type: %v != %v", string(ah.Hash), actual.HashType) + if string(ah.hash) != actual.HashType { + t.Errorf("wrong artifact hash type: %v != %v", string(ah.hash), actual.HashType) + } + if expected.Checksums[ah.hash] != actual.HashValue { + t.Errorf("wrong artifact hash value: %v != %v", expected.Checksums[ah.hash], actual.HashValue) } - if expected.Checksums[ah.Hash] != actual.HashValue { - t.Errorf("wrong artifact hash value: %v != %v", expected.Checksums[ah.Hash], actual.HashValue) + if expected.Download[ah.protocol].URL != actual.Link { + t.Errorf("wrong artifact link: %v != %v", expected.Download[ah.protocol], actual.Link) } - if expected.Download[ah.Protocol].URL != actual.Link { - t.Errorf("wrong artifact link: %v != %v", expected.Download[ah.Protocol], actual.Link) + if ah.copy != actual.Copy { + t.Errorf("wrong artifact copy policy: %v != %v", ah.copy, actual.Copy) } } diff --git a/internal/utils_test.go b/internal/utils_test.go index ac5e37b..d0c9aee 100644 --- a/internal/utils_test.go +++ b/internal/utils_test.go @@ -12,8 +12,12 @@ package feature import ( + "crypto/sha256" + "encoding/hex" "encoding/json" + "io/ioutil" "os" + "path/filepath" "strings" "testing" "time" @@ -58,7 +62,16 @@ type testConfig struct { mode string } -var testVersion = "TestVersion" +var ( + testVersion = "TestVersion" + + partialDownload = func(progress float64) bool { + return progress > 0 && progress < 100 + } + completeDownload = func(progress float64) bool { + return progress == 100 + } +) func assertDirs(t *testing.T, name string, create bool) string { if _, err := os.Stat(name); !os.IsNotExist(err) { @@ -266,3 +279,95 @@ func (token *mockedToken) Done() <-chan struct{} { func (token *mockedToken) Error() error { return token.err } + +func createLocalArtifact(t *testing.T, dir, alias, body string) (string, string) { + t.Helper() + + file := filepath.Join(dir, alias) + if err := storage.WriteLn(file, body); err != nil { + t.Fatal("error writing to file", err) + } + hType := sha256.New() + hType.Write([]byte(body)) + return file, hex.EncodeToString(hType.Sum(nil)) +} + +func convertLocalArtifact(path, name, hash string, len int) *hawkbit.SoftwareArtifactAction { + return &hawkbit.SoftwareArtifactAction{ + Filename: name, + Download: map[hawkbit.Protocol]*hawkbit.Links{ + storage.ProtocolFile: {URL: path}, + }, + Checksums: map[hawkbit.Hash]string{ + hawkbit.SHA256: hash, + }, + Size: len, + } +} + +func getAbsolutePath(t *testing.T, path string) string { + t.Helper() + + absolutePath, err := filepath.Abs(path) + if err != nil { + t.Fatalf("error getting absolute file path of %v - %v", path, err) + } + return absolutePath +} + +func checkFileExistsWithContent(t *testing.T, filename, expectedContent string) { + content, err := os.ReadFile(filename) + if err != nil { + t.Fatalf("expected file %s to be created from install script(error opening: %v)", filename, err) + } + message := strings.Trim(string(content), "\r\n") + if expectedContent != message { + t.Fatalf("wrong install script execution result, expected: %s, got: %s", expectedContent, message) + } +} + +func checkNoFilesCopied(t *testing.T, dir string, canBeMoved bool) bool { + info, err := ioutil.ReadDir(dir) + if err != nil { + if canBeMoved { + return false + } + t.Fatalf("error reading folder %s - %v", dir, err) + } + if canBeMoved && len(info) == 0 { + return false + } + if len(info) != 1 { + t.Fatalf("expected only internal status file to be located in storage, actual number of files: %v", len(info)) + } + if storage.InternalStatusName != info[0].Name() { + t.Fatalf("expected only internal status file to be located in storage, instead found: %s", info[0].Name()) + } + return true +} + +func getCopiedArtifactsCount(artifacts []*hawkbit.SoftwareArtifactAction, copy string) (count int) { + if len(copy) == 0 { + return 0 + } + if copy == "*" { + return len(artifacts) + } + toCopy := strings.FieldsFunc(copy, storage.SplitArtifacts) + for _, artifact := range artifacts { + _, local := artifact.Download[storage.ProtocolFile] + if !local || contains(toCopy, artifact.Filename) { + count++ + } + } + return count +} + +func contains(s []string, str string) bool { + for _, el := range s { + if el == str { + return true + } + } + return false +}