// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you 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 com.cloud.event; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import javax.inject.Inject; import org.apache.cloudstack.framework.events.Event; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.events.EventBus; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import com.cloud.configuration.Config; import com.cloud.event.dao.EventDao; import com.cloud.network.IpAddress; import com.cloud.projects.dao.ProjectDao; import com.cloud.user.AccountVO; import com.cloud.user.User; import com.cloud.user.UserVO; import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.UserDao; import com.cloud.utils.component.ComponentContext; import com.cloud.utils.db.EntityManager; import com.cloud.vm.VirtualMachine; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @RunWith(PowerMockRunner.class) @PrepareForTest(ComponentContext.class) public class ActionEventUtilsTest { //Predictable constants used throughout this test. public static final long EVENT_ID = 1; public static final long USER_ID = 1; public static final long ACCOUNT_ID = 1; //Keep track of the static field values between tests. //A horrid abuse of reflection required due to the strange //static/inject pattern found in ActionEventUtils. protected Map<String, Object> staticFieldValues = new HashMap<>(); //List of events published on the event bus. Handled via a mocked method. //Cleared on every run. protected List<Event> publishedEvents = new ArrayList<>(); //Mock fields. These are injected into ActionEventUtils by the setup() method. @Mock protected EventDao eventDao; @Mock protected AccountDao accountDao; @Mock protected UserDao userDao; @Mock protected ProjectDao projectDao; @Mock protected EntityManager entityMgr; @Mock protected ConfigurationDao configDao; @Mock protected EventBus eventBus; /** * This setup method injects the mocked beans into the ActionEventUtils class. * Because ActionEventUtils has static methods, we must also remember these fields * and restore them later, as otherwise strange behavior can result in other unit * tests due to the way the JVM handles static fields. * @throws Exception */ @Before public void setup() throws Exception { publishedEvents = new ArrayList<>(); staticFieldValues = new HashMap<>(); setupCommonMocks(); ActionEventUtils utils = new ActionEventUtils(); for (Field field : ActionEventUtils.class.getDeclaredFields()) { if (field.getAnnotation(Inject.class) != null) { field.setAccessible(true); try { //Inject the mocked field from this class into the ActionEventUtils //and keep track of its original value. Field mockField = this.getClass().getDeclaredField(field.getName()); field.set(utils, mockField.get(this)); Field staticField = ActionEventUtils.class.getDeclaredField("s_" + field.getName()); staticFieldValues.put(field.getName(), staticField.get(null)); } catch (Exception e) { // ignore missing fields } } } utils.init(); } /** * Set up the common specialized mocks that are needed to make the ActionEventUtils class behave in a * predictable way. This method only mocks things that are common to all the tests. Each individual test * also mocks some other methods (e.g. find user/account) by itself. */ public void setupCommonMocks() throws Exception { //Some basic mocks. Mockito.when(configDao.getValue(Config.PublishActionEvent.key())).thenReturn("true"); PowerMockito.mockStatic(ComponentContext.class); Mockito.when(ComponentContext.getComponent(EventBus.class)).thenReturn(eventBus); //Needed for persist to actually set an ID that can be returned from the ActionEventUtils //methods. Mockito.when(eventDao.persist(Mockito.any(EventVO.class))).thenAnswer(new Answer<EventVO>() { @Override public EventVO answer(InvocationOnMock invocation) throws Throwable { EventVO event = (EventVO)invocation.getArguments()[0]; Field id = event.getClass().getDeclaredField("id"); id.setAccessible(true); id.set(event, EVENT_ID); return event; } }); //Needed to record events published on the bus. Mockito.doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { Event event = (Event)invocation.getArguments()[0]; publishedEvents.add(event); return null; } }).when(eventBus).publish(Mockito.any(Event.class)); } /** * This teardown method restores the ActionEventUtils static field values to their original values, * keeping the mocked mess inside this class. */ @After public void teardown() { ActionEventUtils utils = new ActionEventUtils(); for (String fieldName : staticFieldValues.keySet()) { try { Field field = ActionEventUtils.class.getDeclaredField(fieldName); field.setAccessible(true); field.set(utils, staticFieldValues.get(fieldName)); } catch (Exception e) { e.printStackTrace(); } } utils.init(); } @Test public void testPopulateFirstClassEntities() { AccountVO account = new AccountVO("testaccount", 1L, "networkdomain", (short) 0, "uuid"); account.setId(ACCOUNT_ID); UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN); Mockito.when(accountDao.findById(ACCOUNT_ID)).thenReturn(account); Mockito.when(userDao.findById(USER_ID)).thenReturn(user); CallContext.register(user, account); //Inject some entity UUIDs into the call context String instanceUuid = UUID.randomUUID().toString(); String ipUuid = UUID.randomUUID().toString(); CallContext.current().putContextParameter(VirtualMachine.class, instanceUuid); CallContext.current().putContextParameter(IpAddress.class, ipUuid); ActionEventUtils.onActionEvent(USER_ID, ACCOUNT_ID, account.getDomainId(), "StaticNat", "Test event"); //Assertions Assert.assertNotEquals(publishedEvents.size(), 0); Assert.assertEquals(publishedEvents.size(), 1); Event event = publishedEvents.get(0); Assert.assertNotNull(event.getDescription()); JsonObject json = new JsonParser().parse(event.getDescription()).getAsJsonObject(); Assert.assertTrue(json.has("VirtualMachine")); Assert.assertTrue(json.has("IpAddress")); Assert.assertEquals(json.get("VirtualMachine").getAsString(), instanceUuid); Assert.assertEquals(json.get("IpAddress").getAsString(), ipUuid); CallContext.unregister(); } }