// Copyright 2016 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/ // // or in the "license" file accompanying this file. This file 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 datauploader contains routines upload inventory data to SSM - Inventory service package datauploader import ( "encoding/json" "errors" "testing" "github.com/aws/amazon-ssm-agent/agent/mocks/context" "github.com/aws/amazon-ssm-agent/agent/plugins/inventory/mocks/datauploader" "github.com/aws/amazon-ssm-agent/agent/plugins/inventory/model" "github.com/aws/aws-sdk-go/service/ssm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) func MockInventoryUploader() *InventoryUploader { var uploader InventoryUploader optimizer := datauploader.NewMockDefault() optimizer.On("GetContentHash", mock.AnythingOfType("string")).Return("RandomInventoryItem") optimizer.On("UpdateContentHash", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(nil) uploader.context = context.NewMockDefault() uploader.optimizer = optimizer return &uploader } func FakeInventoryItems(count int) (items []model.Item) { i := 0 for i < count { items = append(items, model.Item{ Name: "RandomInventoryItem", Content: FakeStructForTesting(), SchemaVersion: "1.0", CaptureTime: "time", }) i++ } return } func ApplicationInventoryItem() (items []model.Item) { // Omit Version which is not omitempty // Omit Epoch, InstalledTime and URL which are omitempty // Include CompType which should be omitted in all cases items = append(items, model.Item{ Name: "RandomInventoryItem", Content: model.ApplicationData{ Name: "Test1", Publisher: "Pub1", Release: "1", ApplicationType: "Foo", Architecture: "Brutalism", CompType: model.AWSComponent, }, SchemaVersion: "1.0", CaptureTime: "time", }) return } //TODO: add unit tests for ShouldUpdate scenario once content hash is implemented func TestConvertToSsmInventoryItems(t *testing.T) { var items []model.Item var inventoryItems []*ssm.InventoryItem var err error u := MockInventoryUploader() //testing positive scenario //setting up inventory.Item items = append(items, FakeInventoryItems(2)...) inventoryItems, _, err = u.ConvertToSsmInventoryItems(items) assert.Nil(t, err, "Error shouldn't be thrown for multiple inventory items") assert.Equal(t, len(items), len(inventoryItems), "Count of inventory items should be equal to input") //testing negative scenario item := model.Item{ Name: "RandomInvalidInventoryType", Content: "RandomStringIsNotSupported", } items = append(items, item) inventoryItems, _, err = u.ConvertToSsmInventoryItems(items) assert.NotNil(t, err, "Error should be thrown for unsupported Item.Content") } func TestConvertExcludedAndEmptyToSsmInventoryItems(t *testing.T) { var items []model.Item var inventoryItems []*ssm.InventoryItem var err error u := MockInventoryUploader() //testing positive scenario //setting up inventory.Item items = append(items, ApplicationInventoryItem()...) inventoryItems, _, err = u.ConvertToSsmInventoryItems(items) assert.Nil(t, err, "Error shouldn't be thrown for application inventory item") assert.Equal(t, len(items), len(inventoryItems), "Count of inventory items should be equal to input") bytes, err := json.Marshal(items[0].Content) assert.Nil(t, err, "Error shouldn't be thrown when marshalling content") // CompType not present even though it has value. Version should be present even though it doesn't. InstallTime and Url should not be present because they have no value. assert.Equal(t, "{\"Name\":\"Test1\",\"Publisher\":\"Pub1\",\"Version\":\"\",\"Release\":\"1\",\"ApplicationType\":\"Foo\",\"Architecture\":\"Brutalism\"}", string(bytes[:])) } func TestGetDirtySsmInventoryItems_empty(t *testing.T) { var items []model.Item var dirtyInventoryItems []*ssm.InventoryItem var err error u := MockInventoryUploader() //setting up inventory.Item dirtyInventoryItems, err = u.GetDirtySsmInventoryItems(items) assert.Nil(t, err, "Error shouldn't be thrown if there's no inventory items are given to check") assert.Equal(t, len(dirtyInventoryItems), 0, "Dirty inventory items should be empty when no inventory items are given to check") } func TestGetDirtySsmInventoryItems_dirtyItemFound(t *testing.T) { var items []model.Item var dirtyInventoryItems []*ssm.InventoryItem var err error u := MockInventoryUploader() //setting up inventory.Item items = append(items, ApplicationInventoryItem()...) dirtyInventoryItems, err = u.GetDirtySsmInventoryItems(items) assert.Nil(t, err, "Error shouldn't be thrown if there's an dirty inventory item found") assert.Equal(t, len(dirtyInventoryItems), 1, "Dirty inventory item found") } // Mock stands for a mocked service. type MockSSMCaller struct { mock.Mock } func NewMockSSMCaller() *MockSSMCaller { service := new(MockSSMCaller) return service } func (m *MockSSMCaller) PutInventory(input *ssm.PutInventoryInput) (output *ssm.PutInventoryOutput, err error) { args := m.Called(input) return args.Get(0).(*ssm.PutInventoryOutput), args.Error(1) } func TestGetRandomBackOffTime(t *testing.T) { c := context.NewMockDefault() instanceID := "i-12345678" backoffTime := getRandomBackOffTime(c, instanceID) assert.Equal(t, backoffTime <= Max_Time_TO_Back_Off, true) instanceID = "i-MockID" backoffTime = getRandomBackOffTime(c, instanceID) assert.Equal(t, backoffTime <= Max_Time_TO_Back_Off, true) } func TestSendDataToSSM(t *testing.T) { testSendData(t, true) testSendData(t, false) } func testSendData(t *testing.T, putInventorySucceeds bool) { var items []model.Item var inventoryItems []*ssm.InventoryItem //setting up inventory.Item item := ApplicationInventoryItem()[0] items = append(items, item) inventoryItem, _ := ConvertToSSMInventoryItem(item) hash := "aHash" inventoryItem.ContentHash = &hash inventoryItems = append(inventoryItems, inventoryItem) // create mocks and setup expectations mockSSM := NewMockSSMCaller() output := &ssm.PutInventoryOutput{} if putInventorySucceeds { mockSSM.On("PutInventory", mock.AnythingOfType("*ssm.PutInventoryInput")).Return(output, nil) } else { err := errors.New("some error") mockSSM.On("PutInventory", mock.AnythingOfType("*ssm.PutInventoryInput")).Return(output, err) } mockOptimizer := datauploader.NewMockDefault() if putInventorySucceeds { for _, item := range inventoryItems { mockOptimizer.On("UpdateContentHash", *item.TypeName, hash).Return(nil) } } // call method u := &InventoryUploader{ context: context.NewMockDefault(), ssm: mockSSM, optimizer: mockOptimizer, } u.SendDataToSSM(inventoryItems) // assert that the expectations were met mockSSM.AssertExpectations(t) mockOptimizer.AssertExpectations(t) }