// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may not // use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.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 localpackages implements the local storage for packages managed by the ConfigurePackage plugin. package localpackages import ( "errors" "io/ioutil" "path/filepath" "strings" "testing" "time" "github.com/aws/amazon-ssm-agent/agent/contracts" "github.com/aws/amazon-ssm-agent/agent/fileutil/mocks/filelock" "github.com/aws/amazon-ssm-agent/agent/jsonutil" "github.com/aws/amazon-ssm-agent/agent/mocks/log" "github.com/aws/amazon-ssm-agent/agent/plugins/configurepackage/trace" "github.com/aws/amazon-ssm-agent/agent/plugins/inventory/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) // TODO:MF: test deps, replace filesysdep test version in test_configurepackage with usage of the mocked repository const testRepoRoot = "testdata" const testLockRoot = "testlock" const testPackage = "SsmTest" var tracerMock = trace.NewTracer(log.NewMockLog()) func TestGetInstaller(t *testing.T) { repo := NewRepository() inst := repo.GetInstaller(tracerMock, contracts.Configuration{}, testPackage, "1.0.0", "{\"customArg1\":\"customVal1\", \"customArg2\":\"customVal2\"}") assert.NotNil(t, inst) } func TestGetInstallState(t *testing.T) { // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(true).Once() mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(loadFile(t, filepath.Join(testRepoRoot, testPackage, "installstate_success")), nil).Once() // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot} // Call and validate mock expectations and return value state, version := repo.GetInstallState(tracerMock, testPackage) mockFileSys.AssertExpectations(t) assert.Equal(t, Installed, state) assert.Equal(t, version, version) } func TestGetInstallStateMissing(t *testing.T) { // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(false).Once() mockFileSys.On("GetDirectoryNames", filepath.Join(testRepoRoot, testPackage)).Return(make([]string, 0), nil).Once() // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot} // Call and validate mock expectations and return value state, version := repo.GetInstallState(tracerMock, testPackage) mockFileSys.AssertExpectations(t) assert.Equal(t, None, state) assert.Equal(t, "", version) } func TestGetInstallStateCompat(t *testing.T) { // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(false).Once() mockFileSys.On("GetDirectoryNames", filepath.Join(testRepoRoot, testPackage)).Return([]string{"0.0.1"}, nil).Once() // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot} // Call and validate mock expectations and return value state, version := repo.GetInstallState(tracerMock, testPackage) mockFileSys.AssertExpectations(t) assert.Equal(t, Unknown, state) assert.Equal(t, version, version) } func TestGetInstallStateCorrupt(t *testing.T) { // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(true).Once() mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(loadFile(t, filepath.Join(testRepoRoot, testPackage, "installstate_corrupt")), nil).Once() tracerMock.BeginSection("testtrace") // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot} // Call and validate mock expectations and return value state, version := repo.GetInstallState(tracerMock, testPackage) mockFileSys.AssertExpectations(t) assert.Equal(t, Unknown, state) assert.Equal(t, "", version) } func TestGetInstallStateError(t *testing.T) { // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(true).Once() mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(make([]byte, 0), errors.New("Failed to read file")).Once() // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot} // Call and validate mock expectations and return value state, version := repo.GetInstallState(tracerMock, testPackage) mockFileSys.AssertExpectations(t) assert.Equal(t, Unknown, state) assert.Equal(t, "", version) } func TestGetInstalledVersion(t *testing.T) { // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(true).Once() mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(loadFile(t, filepath.Join(testRepoRoot, testPackage, "installstate_success")), nil).Once() // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot} // Call and validate mock expectations and return value version := repo.GetInstalledVersion(tracerMock, testPackage) mockFileSys.AssertExpectations(t) assert.Equal(t, version, version) } func TestGetInstalledVersionCompat(t *testing.T) { // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(false).Once() mockFileSys.On("GetDirectoryNames", filepath.Join(testRepoRoot, testPackage)).Return([]string{"0.0.1"}, nil).Once() // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot} // Call and validate mock expectations and return value version := repo.GetInstalledVersion(tracerMock, testPackage) mockFileSys.AssertExpectations(t) assert.Equal(t, version, version) } func TestGetInstalledVersionInstalling(t *testing.T) { // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(true).Once() mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(loadFile(t, filepath.Join(testRepoRoot, testPackage, "installstate_installing")), nil).Once() // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot} // Call and validate mock expectations and return value version := repo.GetInstalledVersion(tracerMock, testPackage) mockFileSys.AssertExpectations(t) assert.Equal(t, version, version) } func TestValidatePackage(t *testing.T) { version := "0.0.1" // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, version, "manifest.json")).Return(true).Once() mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, version, "install.ps1")).Return(true).Once() mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, testPackage, version, "manifest.json")).Return(loadFile(t, filepath.Join(testRepoRoot, testPackage, version, "manifest.json")), nil).Once() mockFileSys.On("GetFileNames", filepath.Join(testRepoRoot, testPackage, version)).Return([]string{"manifest.json", "install.json"}, nil) mockFileSys.On("GetDirectoryNames", filepath.Join(testRepoRoot, testPackage, version)).Return([]string{}, nil) // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} // Call and validate mock expectations and return value err := repo.ValidatePackage(tracerMock, testPackage, version) mockFileSys.AssertExpectations(t) assert.Nil(t, err) } func TestValidatePackage_Manifest(t *testing.T) { version := "0.0.1" // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, version, "manifest.json")).Return(true).Once() mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, version, "install.ps1")).Return(true).Once() mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, testPackage, version, "manifest.json")).Return(loadFile(t, filepath.Join(testRepoRoot, testPackage, version, "manifest.json")), nil).Once() mockFileSys.On("GetFileNames", filepath.Join(testRepoRoot, testPackage, version)).Return([]string{"manifest.json", "install.json"}, nil) mockFileSys.On("GetDirectoryNames", filepath.Join(testRepoRoot, testPackage, version)).Return([]string{}, nil) // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} // Call and validate mock expectations and return value err := repo.ValidatePackage(tracerMock, testPackage, version) mockFileSys.AssertExpectations(t) assert.Nil(t, err) } func TestValidatePackage_NoManifest(t *testing.T) { version := "0.0.1" // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, version, "manifest.json")).Return(false).Once() mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, version, "install.ps1")).Return(true).Once() mockFileSys.On("GetFileNames", filepath.Join(testRepoRoot, testPackage, version)).Return([]string{"install.json", "uninstall.json"}, nil) mockFileSys.On("GetDirectoryNames", filepath.Join(testRepoRoot, testPackage, version)).Return([]string{}, nil) // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} // Call and validate mock expectations and return value err := repo.ValidatePackage(tracerMock, testPackage, version) mockFileSys.AssertExpectations(t) assert.Nil(t, err) } func TestValidatePackageNoContent(t *testing.T) { version := "0.0.1" // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, version, "manifest.json")).Return(true).Once() mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, testPackage, version, "manifest.json")).Return(loadFile(t, filepath.Join(testRepoRoot, testPackage, version, "manifest.json")), nil).Once() mockFileSys.On("GetFileNames", filepath.Join(testRepoRoot, testPackage, version)).Return([]string{"manifest.json"}, nil) mockFileSys.On("GetDirectoryNames", filepath.Join(testRepoRoot, testPackage, version)).Return([]string{}, nil) // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} // Call and validate mock expectations and return value err := repo.ValidatePackage(tracerMock, testPackage, version) mockFileSys.AssertExpectations(t) assert.NotNil(t, err) assert.True(t, strings.EqualFold(err.Error(), "Package is incomplete")) } func TestValidatePackageCorruptManifest(t *testing.T) { version := "0.0.10" // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, version, "manifest.json")).Return(true).Once() mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, testPackage, version, "manifest.json")).Return(loadFile(t, filepath.Join(testRepoRoot, testPackage, version, "manifest.json")), nil).Once() // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} // Call and validate mock expectations and return value err := repo.ValidatePackage(tracerMock, testPackage, version) mockFileSys.AssertExpectations(t) assert.NotNil(t, err) assert.True(t, strings.HasPrefix(err.Error(), "Package manifest is invalid:")) } func TestValidatePackageManifest(t *testing.T) { data := []struct { name string manifest *PackageManifest arn string version string expectedErr bool }{ { "empty manifest", &PackageManifest{}, "arn", "version", true, }, { "empty manifest name", &PackageManifest{Name: ""}, "arn", "version", true, }, { "empty manifest version", &PackageManifest{Name: "", Version: ""}, "arn", "version", true, }, { "non matching manifest name", &PackageManifest{Name: "not-arn", Version: "version"}, "arn", "version", true, }, { "non matching manifest name", &PackageManifest{Name: "arn", Version: "not-version"}, "arn", "version", true, }, { "full arn", &PackageManifest{Name: "arn:aws:ssm:us-east-1:401613528637:package/HzsqFmONmi", Version: "version"}, "arn:aws:ssm:us-east-1:401613528637:package/HzsqFmONmi", "version", false, }, { "short arn", &PackageManifest{Name: "arn:aws:ssm:::package/HzsqFmONmi", Version: "version"}, "arn:aws:ssm:::package/HzsqFmONmi", "version", false, }, { "package name", &PackageManifest{Name: "HzsqFmONmi", Version: "version"}, "arn:aws:ssm:::package/HzsqFmONmi", "version", false, }, } for _, testdata := range data { t.Run(testdata.name, func(t *testing.T) { err := validatePackageManifest(testdata.manifest, testdata.arn, testdata.version) if testdata.expectedErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestAddPackage(t *testing.T) { version := "0.0.1" // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("MakeDirExecute", filepath.Join(testRepoRoot, testPackage, version)).Return(nil).Once() mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(true).Once() mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(loadFile(t, filepath.Join(testRepoRoot, testPackage, "installstate_success")), nil).Once() mockDownload := MockedDownloader{} mockDownload.On("Download", tracerMock, filepath.Join(testRepoRoot, testPackage, version)).Return(nil).Once() // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} // Call and validate mock expectations and return value err := repo.AddPackage(tracerMock, testPackage, version, "mock-package-service", mockDownload.Download) mockFileSys.AssertExpectations(t) mockDownload.AssertExpectations(t) assert.Nil(t, err) } func TestAddNewPackage(t *testing.T) { version := "0.0.1" // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("MakeDirExecute", filepath.Join(testRepoRoot, testPackage, version)).Return(nil).Once() mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(false).Once() mockFileSys.On("GetDirectoryNames", filepath.Join(testRepoRoot, testPackage)).Return(make([]string, 0), nil).Once() mockFileSys.On("WriteFile", filepath.Join(testRepoRoot, testPackage, "installstate"), mock.Anything).Return(nil).Once() mockDownload := MockedDownloader{} mockDownload.On("Download", tracerMock, filepath.Join(testRepoRoot, testPackage, version)).Return(nil).Once() // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} // Call and validate mock expectations and return value err := repo.AddPackage(tracerMock, testPackage, version, "mock-package-service", mockDownload.Download) mockFileSys.AssertExpectations(t) mockDownload.AssertExpectations(t) assert.Nil(t, err) } func TestAddPackageWithDownloadFailure(t *testing.T) { version := "0.0.1" // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("MakeDirExecute", filepath.Join(testRepoRoot, testPackage, version)).Return(nil).Once() mockFileSys.On("RemoveAll", filepath.Join(testRepoRoot, testPackage, version)).Return(nil).Once() mockDownload := MockedDownloader{} mockDownload.On("Download", tracerMock, filepath.Join(testRepoRoot, testPackage, version)).Return(errors.New("Download error.")).Once() // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} // Call and validate mock expectations and return value err := repo.AddPackage(tracerMock, testPackage, version, "mock-package-service", mockDownload.Download) mockFileSys.AssertExpectations(t) mockDownload.AssertExpectations(t) assert.NotNil(t, err) assert.Equal(t, err.Error(), "Download error.") } func TestRefreshPackage(t *testing.T) { version := "0.0.1" // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("MakeDirExecute", filepath.Join(testRepoRoot, testPackage, version)).Return(nil).Once() mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(true).Once() mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(loadFile(t, filepath.Join(testRepoRoot, testPackage, "installstate_success")), nil).Once() mockDownload := MockedDownloader{} mockDownload.On("Download", tracerMock, filepath.Join(testRepoRoot, testPackage, version)).Return(nil).Once() // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} // Call and validate mock expectations and return value err := repo.RefreshPackage(tracerMock, testPackage, version, "mock-package-service", mockDownload.Download) mockFileSys.AssertExpectations(t) mockDownload.AssertExpectations(t) assert.Nil(t, err) } func TestRemovePackage(t *testing.T) { version := "0.0.1" // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("RemoveAll", filepath.Join(testRepoRoot, testPackage, version)).Return(nil).Once() // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} // Call and validate mock expectations and return value err := repo.RemovePackage(tracerMock, testPackage, version) mockFileSys.AssertExpectations(t) assert.Nil(t, err) } func TestSetInstallState(t *testing.T) { initialState := PackageInstallState{Name: testPackage, Version: "0.0.1", State: None} finalState := PackageInstallState{Name: testPackage, Version: "0.0.1", State: Installing, Time: time.Now()} testSetInstall(t, initialState, Installing, finalState, "0.0.1") } func TestSetInstallStateRetry(t *testing.T) { initialState := PackageInstallState{Name: testPackage, Version: "0.0.1", State: Installing} finalState := PackageInstallState{Name: testPackage, Version: "0.0.1", State: Installing, Time: time.Now(), RetryCount: 1} testSetInstall(t, initialState, Installing, finalState, "0.0.1") } func TestSetInstallStateInstalled(t *testing.T) { initialState := PackageInstallState{Name: testPackage, Version: "0.0.1", State: Installing} finalState := PackageInstallState{Name: testPackage, Version: "0.0.1", State: Installed, Time: time.Now(), LastInstalledVersion: "0.0.1"} testSetInstall(t, initialState, Installed, finalState, "0.0.1") } func TestSetInstallStateUninstalled(t *testing.T) { initialState := PackageInstallState{Name: testPackage, Version: "0.0.1", State: Installed, Time: time.Now(), LastInstalledVersion: "0.0.1"} finalState := PackageInstallState{Name: testPackage, Version: "0.0.1", State: Uninstalled, Time: time.Now()} testSetInstall(t, initialState, Uninstalled, finalState, "0.0.1") } func TestSetInstallStateUpdating(t *testing.T) { initialState := PackageInstallState{Name: testPackage, Version: "0.0.1", State: Installed, Time: time.Now(), LastInstalledVersion: "0.0.1"} finalState := PackageInstallState{Name: testPackage, Version: "0.0.2", State: Updating, Time: time.Now(), LastInstalledVersion: "0.0.1"} testSetInstall(t, initialState, Updating, finalState, "0.0.2") } func TestSetInstallStateUpdatingToInstalled(t *testing.T) { initialState := PackageInstallState{Name: testPackage, Version: "0.0.2", State: Updating, Time: time.Now(), LastInstalledVersion: "0.0.1"} finalState := PackageInstallState{Name: testPackage, Version: "0.0.2", State: Installed, Time: time.Now(), LastInstalledVersion: "0.0.2"} testSetInstall(t, initialState, Installed, finalState, "0.0.2") } type InventoryTestData struct { Name string Version string State PackageInstallState Manifest PackageManifest } func TestGetInventoryData(t *testing.T) { installTime := time.Now() testData := InventoryTestData{ Name: "SsmTest", Version: "0.0.1", State: PackageInstallState{Name: "SsmTest", Version: "0.0.1", State: Installed, Time: installTime}, Manifest: PackageManifest{Name: "SsmTest", Version: "0.0.1", Platform: "windows", Architecture: "amd64", AppPublisher: "Amazon AWS"}, } expectedInventory := model.ApplicationData{ Name: "SsmTest", Version: "0.0.1", Architecture: "x86_64", Publisher: "Amazon AWS", CompType: model.AWSComponent, InstalledTime: installTime.Format(time.RFC3339), } testInventory(t, []InventoryTestData{testData}, []model.ApplicationData{expectedInventory}) } func TestGetInventoryDataEmpty(t *testing.T) { testInventory(t, []InventoryTestData{}, []model.ApplicationData{}) } func TestGetInventoryDataMultiple(t *testing.T) { installTime := time.Now() testData1 := InventoryTestData{ Name: "SsmTest", Version: "0.0.1", State: PackageInstallState{Name: "SsmTest", Version: "0.0.1", State: Installed, Time: installTime}, Manifest: PackageManifest{Name: "SsmTest", Version: "0.0.1", Platform: "windows", Architecture: "amd64", AppPublisher: "Amazon AWS"}, } testData2 := InventoryTestData{ Name: "Foo", Version: "1.0.1", State: PackageInstallState{Name: "Foo", Version: "1.0.1", State: Installed, Time: installTime}, Manifest: PackageManifest{Name: "Foo", Version: "1.0.1", Platform: "windows", Architecture: "amd64", AppType: "Driver"}, } expectedInventory1 := model.ApplicationData{ Name: "SsmTest", Version: "0.0.1", Architecture: "x86_64", Publisher: "Amazon AWS", CompType: model.AWSComponent, InstalledTime: installTime.Format(time.RFC3339), } expectedInventory2 := model.ApplicationData{ Name: "Foo", Version: "1.0.1", Architecture: "x86_64", CompType: model.AWSComponent, ApplicationType: "Driver", InstalledTime: installTime.Format(time.RFC3339), } testInventory(t, []InventoryTestData{testData1, testData2}, []model.ApplicationData{expectedInventory1, expectedInventory2}) } func TestGetInventoryDataComplex(t *testing.T) { installTime := time.Now() testData1 := InventoryTestData{ Name: "SsmTest", Version: "0.0.1", State: PackageInstallState{Name: "SsmTest", Version: "0.0.1", State: Installed, Time: installTime}, Manifest: PackageManifest{Name: "SsmTest", Version: "0.0.1", Platform: "windows", Architecture: "386", AppName: "SSM Test Package", AppPublisher: "Test"}, } testData2 := InventoryTestData{ // no manifest defined Name: "Foo", Version: "1.0.1", State: PackageInstallState{Name: "Foo", Version: "1.0.1", State: Installing, Time: installTime}, } testData3 := InventoryTestData{ // only name specified in the manifest Name: "SsmTest2", Version: "0.1.2", State: PackageInstallState{Name: "SsmTest2", Version: "0.1.2", State: Installed, Time: installTime}, Manifest: PackageManifest{Name: "SsmTest", Version: "0.1.2", Platform: "windows", Architecture: "386"}, } testData4 := InventoryTestData{ // invalid manifest - no name or appname specified Name: "SsmTest3", Version: "0.1.3", State: PackageInstallState{Name: "SsmTest3", Version: "0.1.3", State: Installed, Time: installTime}, Manifest: PackageManifest{Version: "0.1.3", Platform: "windows", Architecture: "386"}, } expectedInventory := []model.ApplicationData{ { Name: "SSM Test Package", Version: "0.0.1", Architecture: "i386", Publisher: "Test", InstalledTime: installTime.Format(time.RFC3339), }, { Name: "SsmTest", Version: "0.1.2", Architecture: "i386", CompType: model.AWSComponent, InstalledTime: installTime.Format(time.RFC3339), }, } testInventory(t, []InventoryTestData{testData1, testData2, testData3, testData4}, expectedInventory) } func TestGetInventoryBirdwatcherPackageData(t *testing.T) { installTime := time.Now() testData := []InventoryTestData{ { // manifest defined with only the Name Name: "_arnawsssmpackagetestbirdwatcherpackagename_30_MDYHMZE2S4YZLHSTLR4EQ6BT4ZSCW4BDXEV5C2SMMOVWDQZKUHPQ====", Version: "0.0.1", State: PackageInstallState{Name: "arn:aws:ssm:::package/TestBirdwatcherPackageName", Version: "0.0.1", State: Installed, Time: installTime}, Manifest: PackageManifest{Name: "TestBirdwatcherPackageName", Version: "0.0.1", Platform: "windows", Architecture: "386", AppPublisher: "Test"}, }, } expectedInventory := model.ApplicationData{ Name: "TestBirdwatcherPackageName", Version: "0.0.1", Architecture: "i386", Publisher: "Test", InstalledTime: installTime.Format(time.RFC3339), } testInventory(t, testData, []model.ApplicationData{expectedInventory}) } func TestGetInventoryError(t *testing.T) { // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("GetDirectoryNames", filepath.Join(testRepoRoot)).Return([]string{}, errors.New("Failed")).Once() // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot} // Call and validate mock expectations and return value inventory := repo.GetInventoryData(log.NewMockLog()) mockFileSys.AssertExpectations(t) assert.True(t, len(inventory) == 0) } func testInventory(t *testing.T, testData []InventoryTestData, expected []model.ApplicationData) { mockPackages := make([]string, len(testData)) i := 0 // Setup mock with expectations mockFileSys := MockedFileSys{} for _, testItem := range testData { mockPackages[i] = testItem.Name i++ mockFileSys.On("Exists", filepath.Join(testRepoRoot, testItem.Name, "installstate")).Return(true).Once() stateContent, _ := jsonutil.Marshal(testItem.State) mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, testItem.Name, "installstate")).Return([]byte(stateContent), nil).Once() if (testItem.Manifest != PackageManifest{}) { mockFileSys.On("Exists", filepath.Join(testRepoRoot, normalizeDirectory(testItem.State.Name), testItem.Version, "manifest.json")).Return(true).Once() manifestContent, _ := jsonutil.Marshal(testItem.Manifest) mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, normalizeDirectory(testItem.State.Name), testItem.Version, "manifest.json")).Return([]byte(manifestContent), nil).Once() } } mockFileSys.On("GetDirectoryNames", filepath.Join(testRepoRoot)).Return(mockPackages, nil).Once() // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} // Call and validate mock expectations and return value inventory := repo.GetInventoryData(log.NewMockLog()) mockFileSys.AssertExpectations(t) assert.True(t, len(inventory) == len(expected)) for i, expectedInventory := range expected { assert.Equal(t, expectedInventory, inventory[i]) } } func testSetInstall(t *testing.T, initialState PackageInstallState, newState InstallState, expectedFinalState PackageInstallState, newVersion string) { initialJson, _ := jsonutil.Marshal(initialState) // Setup mock with expectations mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, "installstate")).Return(true).Once() mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, testPackage, "installstate")).Return([]byte(initialJson), nil).Once() mockFileSys.On("WriteFile", filepath.Join(testRepoRoot, testPackage, "installstate"), mock.Anything).Return(nil).Once() // Instantiate repository with mock repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} // Call and validate mock expectations and return value err := repo.SetInstallState(tracerMock, testPackage, newVersion, newState) mockFileSys.AssertExpectations(t) assert.Nil(t, err) var actualFinalState PackageInstallState jsonutil.Unmarshal(mockFileSys.ContentWritten, &actualFinalState) assertStateEqual(t, expectedFinalState, actualFinalState) } func TestLoadTraces(t *testing.T) { mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, "traces")).Return(true) mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, testPackage, "traces")).Return([]byte( `[{"Operation": "foo", "Exitcode": 1, "Start": 123, "Stop": 456}]`), nil) mockFileSys.On("RemoveAll", filepath.Join(testRepoRoot, testPackage, "traces")).Return(nil) repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} err := repo.LoadTraces(tracerMock, testPackage) assert.Nil(t, err) mockFileSys.AssertExpectations(t) assert.Equal(t, int64(123), tracerMock.Traces()[0].Start) } func TestLoadTracesNoneExist(t *testing.T) { mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, "traces")).Return(false) repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} err := repo.LoadTraces(tracerMock, testPackage) assert.Nil(t, err) mockFileSys.AssertExpectations(t) } func TestLoadTracesCorrupted(t *testing.T) { mockFileSys := MockedFileSys{} mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, "traces")).Return(true) mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, testPackage, "traces")).Return([]byte("/!"), nil) mockFileSys.On("RemoveAll", filepath.Join(testRepoRoot, testPackage, "traces")).Return(nil) repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} err := repo.LoadTraces(tracerMock, testPackage) assert.Error(t, err) mockFileSys.AssertExpectations(t) } func TestPersistTraces(t *testing.T) { mockFileSys := MockedFileSys{} mockFileSys.On("WriteFile", filepath.Join(testRepoRoot, testPackage, "traces"), mock.Anything).Return(nil) repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} err := repo.PersistTraces(tracerMock, testPackage) assert.NoError(t, err) mockFileSys.AssertExpectations(t) } func TestPersistLoadTracesRoundtrip(t *testing.T) { mockFileSys := MockedFileSys{} repo := localRepository{filesysdep: &mockFileSys, repoRoot: testRepoRoot, lockRoot: testLockRoot, fileLocker: &filelock.FileLockerNoop{}} var file string mockFileSys.On("WriteFile", filepath.Join(testRepoRoot, testPackage, "traces"), mock.Anything).Return(nil).Run(func(args mock.Arguments) { file = args.Get(1).(string) }) err := repo.PersistTraces(tracerMock, testPackage) assert.NoError(t, err) mockFileSys.On("Exists", filepath.Join(testRepoRoot, testPackage, "traces")).Return(true) mockFileSys.On("ReadFile", filepath.Join(testRepoRoot, testPackage, "traces")).Return([]byte(file), nil) mockFileSys.On("RemoveAll", filepath.Join(testRepoRoot, testPackage, "traces")).Return(nil) err = repo.LoadTraces(tracerMock, testPackage) assert.NoError(t, err) mockFileSys.AssertExpectations(t) } // assertStateEqual compares two PackageInstallState and makes sure they are the same (ignoring the time field) func assertStateEqual(t *testing.T, expected PackageInstallState, actual PackageInstallState) { assert.Equal(t, expected.Name, actual.Name) assert.Equal(t, expected.Version, actual.Version) assert.Equal(t, expected.State, actual.State) assert.Equal(t, expected.LastInstalledVersion, actual.LastInstalledVersion) assert.Equal(t, expected.RetryCount, actual.RetryCount) if (expected.Time != time.Time{}) { assert.True(t, actual.Time != time.Time{}) } else { assert.True(t, actual.Time == time.Time{}) } } // Load specified file from file system func loadFile(t *testing.T, fileName string) (result []byte) { var err error if result, err = ioutil.ReadFile(fileName); err != nil { t.Fatal(err) } return } type MockedDownloader struct { mock.Mock } func (downloadMock *MockedDownloader) Download(tracer trace.Tracer, targetDirectory string) error { args := downloadMock.Called(tracer, targetDirectory) return args.Error(0) } type MockedFileSys struct { mock.Mock ContentWritten string } func (fileMock *MockedFileSys) MakeDirExecute(destinationDir string) (err error) { args := fileMock.Called(destinationDir) return args.Error(0) } func (fileMock *MockedFileSys) GetDirectoryNames(srcPath string) (directories []string, err error) { args := fileMock.Called(srcPath) return args.Get(0).([]string), args.Error(1) } func (fileMock *MockedFileSys) GetFileNames(srcPath string) (files []string, err error) { args := fileMock.Called(srcPath) return args.Get(0).([]string), args.Error(1) } func (fileMock *MockedFileSys) Exists(filePath string) bool { args := fileMock.Called(filePath) return args.Bool(0) } func (fileMock *MockedFileSys) RemoveAll(path string) error { args := fileMock.Called(path) return args.Error(0) } func (fileMock *MockedFileSys) ReadFile(filename string) ([]byte, error) { args := fileMock.Called(filename) return args.Get(0).([]byte), args.Error(1) } func (fileMock *MockedFileSys) WriteFile(filename string, content string) error { args := fileMock.Called(filename, content) fileMock.ContentWritten += content return args.Error(0) }