package tw.com.unit; import com.amazonaws.services.cloudformation.model.StackStatus; import org.apache.commons.cli.MissingArgumentException; import org.easymock.EasyMock; import org.easymock.EasyMockRunner; import org.easymock.EasyMockSupport; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import tw.com.NotificationProvider; import tw.com.SNSMonitor; import tw.com.SetsDeltaIndex; import tw.com.entity.DeletionsPending; import tw.com.entity.StackNameAndId; import tw.com.entity.StackNotification; import tw.com.exceptions.*; import tw.com.repository.CheckStackExists; import java.util.LinkedList; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @RunWith(EasyMockRunner.class) public class TestSNSStackMonitor extends EasyMockSupport implements CheckStackExists, SetsDeltaIndex { private SNSMonitor monitor; private NotificationProvider eventSource; private StackNameAndId stackNameAndId; private String stackName; private String stackId; private boolean isStackFound; private int deltaIndexResult; private static final String STACK_RESOURCE_TYPE = "AWS::CloudFormation::Stack"; private static final int LIMIT = 50; @Before public void beforeEachTestRuns() { eventSource = createMock(NotificationProvider.class); monitor = new SNSMonitor(eventSource, this); stackName = "aStackName"; stackId = "aStackId"; stackNameAndId = new StackNameAndId(stackName, stackId); } @Test public void shouldTestWaitForStackCreationDoneEvents() throws InterruptedException, MissingArgumentException, CfnAssistException { isStackFound= true; String inProgress = StackStatus.CREATE_IN_PROGRESS.toString(); String complete = StackStatus.CREATE_COMPLETE.toString(); setExpectationsForInitAndReady(); setEventStreamExpectations(inProgress, complete); replayAll(); monitor.init(); String result = monitor.waitForCreateFinished(stackNameAndId); assertEquals(complete, result); verifyAll(); } @Test public void shouldTestWaitForStackDeletionDoneEvents() throws InterruptedException, MissingArgumentException, CfnAssistException { isStackFound= true; String inProgress = StackStatus.DELETE_IN_PROGRESS.toString(); String complete = StackStatus.DELETE_COMPLETE.toString(); setExpectationsForInitAndReady(); setEventStreamExpectations(inProgress, complete); replayAll(); monitor.init(); String result = monitor.waitForDeleteFinished(stackNameAndId); assertEquals(complete, result); verifyAll(); } @Test public void shouldTestWaitForStackDeletionStackAlreadyGone() throws InterruptedException, MissingArgumentException, CfnAssistException { String complete = StackStatus.DELETE_COMPLETE.toString(); isStackFound= false; setExpectationsForInitAndReady(); replayAll(); monitor.init(); String result = monitor.waitForDeleteFinished(stackNameAndId); assertEquals(complete, result); verifyAll(); } @Test public void shouldTestWaitForStackRollbackDoneEvents() throws InterruptedException, MissingArgumentException, CfnAssistException { isStackFound= true; String inProgress = StackStatus.ROLLBACK_IN_PROGRESS.toString(); String complete = StackStatus.ROLLBACK_COMPLETE.toString(); setExpectationsForInitAndReady(); setEventStreamExpectations(inProgress, complete); replayAll(); monitor.init(); String result = monitor.waitForRollbackComplete(stackNameAndId); assertEquals(complete, result); verifyAll(); } @Test public void shouldThrowWhenExpectedStatusNotWithinTimeout() throws MissingArgumentException, InterruptedException, CfnAssistException { isStackFound= true; String inProgress = StackStatus.CREATE_IN_PROGRESS.toString(); setExpectationsForInitAndReady(); setExpectationsRepondInProgressUntilLimit(inProgress); replayAll(); monitor.init(); try { monitor.waitForRollbackComplete(stackNameAndId); fail("should have thrown"); } catch(WrongStackStatus expectedException) { // noop } verifyAll(); } private void setExpectationsRepondInProgressUntilLimit(String inProgress) throws NotReadyException { List<StackNotification> theEvents = new LinkedList<>(); addMatchingEvent(theEvents, inProgress, stackName, stackId); for(int count=0; count<=LIMIT; count++) { EasyMock.expect(eventSource.receiveNotifications()).andReturn(theEvents); } } @Test public void shouldThrowIfNotifIsNotInit() throws WrongNumberOfStacksException, WrongStackStatus, InterruptedException, MissingArgumentException { isStackFound= true; EasyMock.expect(eventSource.isInit()).andReturn(false); replayAll(); try { monitor.waitForCreateFinished(stackNameAndId); fail("should have thrown"); } catch(NotReadyException expected) { // no op } verifyAll(); } @Test public void ShouldCopeWithListOfNonExistanceStacksToDeleteSNS() throws CfnAssistException, MissingArgumentException, InterruptedException { isStackFound=false; setExpectationsForInitAndReady(); replayAll(); DeletionsPending pending = createPendingDeletes(); monitor.init(); monitor.waitForDeleteFinished(pending, this); assertEquals(2, deltaIndexResult); verifyAll(); } @Test public void shouldMonitorMultiplePendingDeletes() throws InterruptedException, CfnAssistException, MissingArgumentException { isStackFound = true; String targetStatus = StackStatus.DELETE_COMPLETE.toString(); DeletionsPending pending = createPendingDelete(); eventSource.init(); EasyMock.expectLastCall(); EasyMock.expect(eventSource.isInit()).andReturn(true); setEventStreamExpectations(StackStatus.DELETE_IN_PROGRESS.toString(), targetStatus, "stackC", "id3"); setEventStreamExpectations(StackStatus.DELETE_IN_PROGRESS.toString(), targetStatus, "stackB", "id2"); setEventStreamExpectations(StackStatus.DELETE_IN_PROGRESS.toString(), targetStatus, "stackA", "id1"); replayAll(); monitor.init(); monitor.waitForDeleteFinished(pending, this); assertEquals(0, deltaIndexResult); verifyAll(); } @Test public void shouldMonitorMultiplePendingDeletesOutOfOrder() throws InterruptedException, CfnAssistException, MissingArgumentException { isStackFound = true; String targetStatus = StackStatus.DELETE_COMPLETE.toString(); DeletionsPending pending = createPendingDelete(); eventSource.init(); EasyMock.expectLastCall(); EasyMock.expect(eventSource.isInit()).andReturn(true); setEventStreamExpectations(StackStatus.DELETE_IN_PROGRESS.toString(), targetStatus, "stackB", "id2"); setEventStreamExpectations(StackStatus.DELETE_IN_PROGRESS.toString(), targetStatus, "stackA", "id1"); setEventStreamExpectations(StackStatus.DELETE_IN_PROGRESS.toString(), targetStatus, "stackC", "id3"); replayAll(); monitor.init(); monitor.waitForDeleteFinished(pending, this); assertEquals(0, deltaIndexResult); verifyAll(); } @Test public void shouldMonitorMultiplePendingDeletesNotAll() throws InterruptedException, CfnAssistException, MissingArgumentException { isStackFound = true; String targetStatus = StackStatus.DELETE_COMPLETE.toString(); DeletionsPending pending = createPendingDelete(); eventSource.init(); EasyMock.expectLastCall(); EasyMock.expect(eventSource.isInit()).andReturn(true); setEventStreamExpectations(StackStatus.DELETE_IN_PROGRESS.toString(), targetStatus, "stackB", "id2"); setEventStreamExpectations(StackStatus.DELETE_IN_PROGRESS.toString(), targetStatus, "stackC", "id3"); setExpectationsRepondInProgressUntilLimit(StackStatus.DELETE_IN_PROGRESS.toString()); replayAll(); monitor.init(); monitor.waitForDeleteFinished(pending, this); assertEquals(1, deltaIndexResult); verifyAll(); } // TOOD Deletions pending, highest or lowest?? @Test public void shouldMonitorMultiplePendingDeletesSomeMissing() throws InterruptedException, CfnAssistException, MissingArgumentException { isStackFound = true; String targetStatus = StackStatus.DELETE_COMPLETE.toString(); DeletionsPending pending = createPendingDelete(); eventSource.init(); EasyMock.expectLastCall(); EasyMock.expect(eventSource.isInit()).andReturn(true); setEventStreamExpectations(StackStatus.DELETE_IN_PROGRESS.toString(), targetStatus, "stackB", "id2"); setExpectationsRepondInProgressUntilLimit(StackStatus.DELETE_IN_PROGRESS.toString()); replayAll(); monitor.init(); monitor.waitForDeleteFinished(pending, this); assertEquals(1, deltaIndexResult); verifyAll(); } private DeletionsPending createPendingDelete() { DeletionsPending pending = new DeletionsPending(); pending.add(3, new StackNameAndId("stackC", "id3")); pending.add(2, new StackNameAndId("stackB", "id2")); pending.add(1, new StackNameAndId("stackA", "id1")); return pending; } private DeletionsPending createPendingDeletes() { DeletionsPending pending = new DeletionsPending(); pending.add(3, new StackNameAndId("alreadyGone1","11")); // final index should end up as 2 pending.add(4, new StackNameAndId("alreadyGone2","12")); pending.add(5, new StackNameAndId("alreadyGone3","13")); return pending; } private void setEventStreamExpectations(String inProgress, String complete) throws NotReadyException { setEventStreamExpectations(inProgress, complete, stackName, stackId); } private void setEventStreamExpectations(String inProgress, String complete, String theName, String theId) throws NotReadyException { List<StackNotification> theEvents = new LinkedList<>(); addMatchingEvent(theEvents, inProgress, theName, theId); addNonMatchingEvent(theEvents, complete, theName, theId); addMatchingEvent(theEvents, inProgress, theName, theId); addNonMatchingEvent(theEvents, inProgress, theName, theId); EasyMock.expect(eventSource.receiveNotifications()).andReturn(theEvents); List<StackNotification> moreEvents = new LinkedList<>(); moreEvents.addAll(theEvents); addNonMatchingEvent(moreEvents, complete, theName, theId); addMatchingEvent(moreEvents, complete, theName, theId); EasyMock.expect(eventSource.receiveNotifications()).andReturn(moreEvents); } private void setExpectationsForInitAndReady() throws MissingArgumentException, FailedToCreateQueueException, InterruptedException { eventSource.init(); EasyMock.expectLastCall(); EasyMock.expect(eventSource.isInit()).andReturn(true); } private void addMatchingEvent(List<StackNotification> theEvents, String status, String theName, String theId) { theEvents.add(new StackNotification(theName, status, theId, STACK_RESOURCE_TYPE, "reason")); } private void addNonMatchingEvent(List<StackNotification> theEvents, String status, String theName, String theId) { theEvents.add(new StackNotification(theName, status, theId, "someOtherType", "reason")); } @Override public boolean stackExists(String stackName) throws WrongNumberOfStacksException { return isStackFound; } @Override public void setDeltaIndex(Integer newDelta) throws CannotFindVpcException { deltaIndexResult = newDelta; } }