org.zenoss.zep.dao.impl.EventSummaryDaoImplIT.java Source code

Java tutorial

Introduction

Here is the source code for org.zenoss.zep.dao.impl.EventSummaryDaoImplIT.java

Source

/*****************************************************************************
 * 
 * Copyright (C) Zenoss, Inc. 2010-2011, 2014 all rights reserved.
 * 
 * This content is made available according to terms specified in
 * License.zenoss under the directory where your Zenoss product is installed.
 * 
 ****************************************************************************/

package org.zenoss.zep.dao.impl;

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.zenoss.protobufs.model.Model.ModelElementType;
import org.zenoss.protobufs.zep.Zep.Event;
import org.zenoss.protobufs.zep.Zep.Event.Builder;
import org.zenoss.protobufs.zep.Zep.EventActor;
import org.zenoss.protobufs.zep.Zep.EventAuditLog;
import org.zenoss.protobufs.zep.Zep.EventDetail;
import org.zenoss.protobufs.zep.Zep.EventDetail.EventDetailMergeBehavior;
import org.zenoss.protobufs.zep.Zep.EventDetailSet;
import org.zenoss.protobufs.zep.Zep.EventNote;
import org.zenoss.protobufs.zep.Zep.EventSeverity;
import org.zenoss.protobufs.zep.Zep.EventStatus;
import org.zenoss.protobufs.zep.Zep.EventSummary;
import org.zenoss.protobufs.zep.Zep.EventTag;
import org.zenoss.protobufs.zep.Zep.SyslogPriority;
import org.zenoss.zep.ClearFingerprintGenerator;
import org.zenoss.zep.ZepConstants;
import org.zenoss.zep.ZepException;
import org.zenoss.zep.dao.ConfigDao;
import org.zenoss.zep.dao.EventArchiveDao;
import org.zenoss.zep.dao.EventSummaryDao;
import org.zenoss.zep.dao.impl.compat.DatabaseCompatibility;
import org.zenoss.zep.impl.EventPreCreateContextImpl;
import org.zenoss.zep.plugins.EventPreCreateContext;

import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.*;
import static org.zenoss.zep.dao.impl.EventConstants.*;

@ContextConfiguration({ "classpath:zep-config.xml" })
public class EventSummaryDaoImplIT extends AbstractTransactionalJUnit4SpringContextTests {

    private static Random random = new Random();

    @Autowired
    public EventSummaryDao eventSummaryDao;

    @Autowired
    public EventArchiveDao eventArchiveDao;

    @Autowired
    public DatabaseCompatibility databaseCompatibility;

    @Autowired
    public ConfigDao configDao;

    private EventSummary createSummaryNew(Event event) throws ZepException {
        return createSummary(event, EventStatus.STATUS_NEW);
    }

    private EventSummary createSummary(Event event, EventStatus status) throws ZepException {
        Event eventStatus = Event.newBuilder(event).setStatus(status).build();
        String uuid = eventSummaryDao.create(eventStatus, new EventPreCreateContextImpl());
        return eventSummaryDao.findByUuid(uuid);
    }

    private EventSummary createSummaryClear(Event event, Set<String> clearClasses) throws ZepException {
        EventPreCreateContextImpl eventContext = new EventPreCreateContextImpl();
        eventContext.setClearClasses(clearClasses);
        String uuid = eventSummaryDao.create(event, eventContext);
        return eventSummaryDao.findByUuid(uuid);
    }

    private static void compareEvents(Event event, Event eventFromDb) {
        Event event1 = Event.newBuilder().mergeFrom(event).clearUuid().clearCreatedTime().clearTags()
                .addAllTags(EventDaoHelper.buildTags(event)).build();
        Event event2 = Event.newBuilder().mergeFrom(eventFromDb).clearUuid().clearCreatedTime().build();
        assertEquals(event1, event2);
    }

    private static void compareSummary(EventSummary expected, EventSummary actual) {
        expected = EventSummary.newBuilder(expected).clearStatus().clearStatusChangeTime().clearUpdateTime()
                .build();
        actual = EventSummary.newBuilder(expected).clearStatus().clearStatusChangeTime().clearUpdateTime().build();
        assertEquals(expected, actual);
    }

    @Test
    public void testSummaryInsert() throws ZepException, InterruptedException {
        Event event = EventTestUtils.createSampleEvent();
        EventSummary eventSummaryFromDb = createSummaryNew(event);
        Event eventFromSummary = eventSummaryFromDb.getOccurrence(0);
        compareEvents(event, eventFromSummary);
        assertEquals(1, eventSummaryFromDb.getCount());
        assertEquals(EventStatus.STATUS_NEW, eventSummaryFromDb.getStatus());
        assertEquals(eventFromSummary.getCreatedTime(), eventSummaryFromDb.getFirstSeenTime());
        assertEquals(eventFromSummary.getCreatedTime(), eventSummaryFromDb.getStatusChangeTime());
        assertEquals(eventFromSummary.getCreatedTime(), eventSummaryFromDb.getLastSeenTime());
        assertFalse(eventSummaryFromDb.hasCurrentUserUuid());
        assertFalse(eventSummaryFromDb.hasCurrentUserName());
        assertFalse(eventSummaryFromDb.hasClearedByEventUuid());

        /*
         * Create event with same fingerprint but again with new message,
         * summary, details.
         */
        Event.Builder newEventBuilder = Event.newBuilder().mergeFrom(event);
        newEventBuilder.setUuid(UUID.randomUUID().toString());
        newEventBuilder.setCreatedTime(System.currentTimeMillis());
        newEventBuilder.setMessage(event.getMessage() + random.nextInt(500));
        newEventBuilder.setSummary(event.getSummary() + random.nextInt(1000));
        newEventBuilder.clearDetails();
        newEventBuilder.addDetails(createDetail("newname1", "newvalue1", "newvalue2"));
        Event newEvent = newEventBuilder.build();

        EventSummary newEventSummaryFromDb = createSummaryNew(newEvent);

        assertEquals(eventSummaryFromDb.getUuid(), newEventSummaryFromDb.getUuid());
        Event newEventFromSummary = newEventSummaryFromDb.getOccurrence(0);
        assertTrue(newEventSummaryFromDb.getLastSeenTime() > newEventSummaryFromDb.getFirstSeenTime());
        assertEquals(eventSummaryFromDb.getFirstSeenTime(), newEventSummaryFromDb.getFirstSeenTime());
        assertEquals(newEventFromSummary.getCreatedTime(), newEventSummaryFromDb.getLastSeenTime());
        assertEquals(newEvent.getCreatedTime(), newEventSummaryFromDb.getLastSeenTime());
        // Verify status didn't change (two NEW events)
        assertEquals(event.getCreatedTime(), newEventSummaryFromDb.getStatusChangeTime());
        assertEquals(newEvent.getMessage(), newEventFromSummary.getMessage());
        assertEquals(newEvent.getSummary(), newEventFromSummary.getSummary());

        List<EventDetail> combined = new ArrayList<EventDetail>();
        combined.addAll(event.getDetailsList());
        combined.addAll(newEvent.getDetailsList());
        assertEquals(combined, newEventFromSummary.getDetailsList());
    }

    @Test
    public void testSummaryMultiThreadDedup() throws ZepException, InterruptedException, ExecutionException {
        // Attempts to create the same event from multiple threads - verifies we get the appropriate de-duping behavior
        // for the count and that we are holding the lock on the database appropriately.
        int poolSize = 10;
        final CyclicBarrier barrier = new CyclicBarrier(poolSize);
        ExecutorService executorService = Executors.newFixedThreadPool(poolSize);
        ExecutorCompletionService<String> ecs = new ExecutorCompletionService<String>(executorService);
        final Event event = EventTestUtils.createSampleEvent();
        final EventPreCreateContext context = new EventPreCreateContextImpl();
        for (int i = 0; i < poolSize; i++) {
            ecs.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    barrier.await();
                    return eventSummaryDao.create(event, context);
                }
            });
        }
        String uuid = null;
        for (int i = 0; i < poolSize; i++) {
            String thisUuid = ecs.take().get();
            if (uuid == null) {
                assertNotNull(thisUuid);
                uuid = thisUuid;
            } else {
                assertEquals(uuid, thisUuid);
            }
        }
        // Now look up the event and make sure the count is equal to the number of submitted workers
        assertEquals(poolSize, this.eventSummaryDao.findByUuid(uuid).getCount());
    }

    @Test
    public void testAcknowledgedToNew() throws ZepException {
        /*
         * Verify acknowledged events aren't changed to new with a new
         * occurrence.
         */
        Event event = Event.newBuilder(EventTestUtils.createSampleEvent())
                .setStatus(EventStatus.STATUS_ACKNOWLEDGED).build();
        eventSummaryDao.create(event, new EventPreCreateContextImpl());
        Event.Builder newEventBuilder = Event.newBuilder().mergeFrom(event);
        newEventBuilder.setUuid(UUID.randomUUID().toString());
        newEventBuilder.setCreatedTime(event.getCreatedTime() + 50L);
        Event newEvent = newEventBuilder.build();

        EventSummary newEventSummaryFromDb = createSummaryNew(newEvent);

        assertEquals(EventStatus.STATUS_ACKNOWLEDGED, newEventSummaryFromDb.getStatus());
        assertEquals(event.getCreatedTime(), newEventSummaryFromDb.getStatusChangeTime());
    }

    @Test
    public void testAcknowledgedToSuppressed() throws ZepException {
        /*
         * Verify acknowledged events aren't changed to suppressed with a new
         * occurrence.
         */
        Event event = Event.newBuilder(EventTestUtils.createSampleEvent())
                .setStatus(EventStatus.STATUS_ACKNOWLEDGED).build();
        eventSummaryDao.create(event, new EventPreCreateContextImpl());
        Event.Builder newEventBuilder = Event.newBuilder().mergeFrom(event);
        newEventBuilder.setUuid(UUID.randomUUID().toString());
        newEventBuilder.setCreatedTime(event.getCreatedTime() + 50L);
        newEventBuilder.setStatus(EventStatus.STATUS_SUPPRESSED);
        Event newEvent = newEventBuilder.build();

        EventSummary newEventSummaryFromDb = createSummaryNew(newEvent);

        assertEquals(EventStatus.STATUS_ACKNOWLEDGED, newEventSummaryFromDb.getStatus());
        assertEquals(event.getCreatedTime(), newEventSummaryFromDb.getStatusChangeTime());
    }

    private static String createRandomMaxString(int length) {
        final String alphabet = "abcdefghijklmnopqrstuvwxyz";
        final char[] chars = new char[length];
        for (int i = 0; i < chars.length; i++) {
            chars[i] = alphabet.charAt(random.nextInt(alphabet.length()));
        }
        return new String(chars);
    }

    @Test
    public void testSummaryMaxInsert() throws ZepException, InterruptedException {
        Event.Builder eventBuilder = Event.newBuilder(createUniqueEvent());
        EventActor.Builder actorBuilder = EventActor.newBuilder(eventBuilder.getActor());
        eventBuilder.setFingerprint(createRandomMaxString(MAX_FINGERPRINT + 1));
        actorBuilder.setElementIdentifier(createRandomMaxString(MAX_ELEMENT_IDENTIFIER + 1));
        actorBuilder.setElementTitle(createRandomMaxString(MAX_ELEMENT_TITLE + 1));
        actorBuilder.setElementSubIdentifier(createRandomMaxString(MAX_ELEMENT_SUB_IDENTIFIER + 1));
        actorBuilder.setElementSubTitle(createRandomMaxString(MAX_ELEMENT_SUB_TITLE + 1));
        eventBuilder.setEventClass(createRandomMaxString(MAX_EVENT_CLASS + 1));
        eventBuilder.setEventClassKey(createRandomMaxString(MAX_EVENT_CLASS_KEY + 1));
        eventBuilder.setEventKey(createRandomMaxString(MAX_EVENT_KEY + 1));
        eventBuilder.setMonitor(createRandomMaxString(MAX_MONITOR + 1));
        eventBuilder.setAgent(createRandomMaxString(MAX_AGENT + 1));
        eventBuilder.setEventGroup(createRandomMaxString(MAX_EVENT_GROUP + 1));
        eventBuilder.setSummary(createRandomMaxString(MAX_SUMMARY + 1));
        eventBuilder.setMessage(createRandomMaxString(MAX_MESSAGE + 1));
        eventBuilder.setActor(actorBuilder.build());
        final Event event = eventBuilder.build();
        final EventSummary summary = createSummaryNew(event);
        final Event eventFromDb = summary.getOccurrence(0);
        final EventActor actorFromDb = eventFromDb.getActor();
        assertEquals(MAX_FINGERPRINT, eventFromDb.getFingerprint().length());
        assertEquals(MAX_ELEMENT_IDENTIFIER, actorFromDb.getElementIdentifier().length());
        assertEquals(MAX_ELEMENT_TITLE, actorFromDb.getElementTitle().length());
        assertEquals(MAX_ELEMENT_SUB_IDENTIFIER, actorFromDb.getElementSubIdentifier().length());
        assertEquals(MAX_ELEMENT_SUB_TITLE, actorFromDb.getElementSubTitle().length());
        assertEquals(MAX_EVENT_CLASS, eventFromDb.getEventClass().length());
        assertEquals(MAX_EVENT_CLASS_KEY, eventFromDb.getEventClassKey().length());
        assertEquals(MAX_EVENT_KEY, eventFromDb.getEventKey().length());
        assertEquals(MAX_MONITOR, eventFromDb.getMonitor().length());
        assertEquals(MAX_AGENT, eventFromDb.getAgent().length());
        assertEquals(MAX_EVENT_GROUP, eventFromDb.getEventGroup().length());
        assertEquals(MAX_SUMMARY, eventFromDb.getSummary().length());
        assertEquals(MAX_MESSAGE, eventFromDb.getMessage().length());

        this.eventSummaryDao.acknowledge(Collections.singletonList(summary.getUuid()), UUID.randomUUID().toString(),
                createRandomMaxString(MAX_CURRENT_USER_NAME + 1));
        final EventSummary summaryFromDb = this.eventSummaryDao.findByUuid(summary.getUuid());
        assertEquals(MAX_CURRENT_USER_NAME, summaryFromDb.getCurrentUserName().length());
    }

    public static Event createUniqueEvent() {
        Random r = new Random();
        Event.Builder eventBuilder = Event.newBuilder();
        eventBuilder.setUuid(UUID.randomUUID().toString());
        eventBuilder.setCreatedTime(System.currentTimeMillis());
        eventBuilder.addDetails(createDetail("foo", "bar", "baz"));
        eventBuilder.addDetails(createDetail("foo2", "bar2", "baz2"));
        eventBuilder.setActor(EventTestUtils.createSampleActor());
        eventBuilder.setAgent("agent");
        eventBuilder.setEventClass("/Unknown");
        eventBuilder.setEventClassKey("eventClassKey");
        eventBuilder.setEventClassMappingUuid(UUID.randomUUID().toString());
        eventBuilder.setEventGroup("event group");
        eventBuilder.setEventKey("event key");
        eventBuilder.setFingerprint("my|dedupid|foo|" + r.nextInt());
        eventBuilder.setMessage("my message");
        eventBuilder.setMonitor("monitor");
        eventBuilder.setNtEventCode(r.nextInt(50000));
        eventBuilder.setSeverity(EventSeverity.SEVERITY_CRITICAL);
        eventBuilder.setSummary("summary message");
        eventBuilder.setSyslogFacility(11);
        eventBuilder.setSyslogPriority(SyslogPriority.SYSLOG_PRIORITY_DEBUG);
        return eventBuilder.build();
    }

    @Test
    public void testListByUuid() throws ZepException {
        Set<String> uuidsToSearch = new HashSet<String>();
        for (int i = 0; i < 10; i++) {
            String uuid = createSummaryNew(createUniqueEvent()).getUuid();
            if ((i % 2) == 0) {
                uuidsToSearch.add(uuid);
            }
        }

        List<EventSummary> result = eventSummaryDao.findByUuids(new ArrayList<String>(uuidsToSearch));
        assertEquals(uuidsToSearch.size(), result.size());
        for (EventSummary event : result) {
            assertTrue(uuidsToSearch.contains(event.getUuid()));
        }
    }

    @Test
    public void testReopen() throws ZepException {
        EventSummary summary = createSummaryNew(createUniqueEvent());
        long origStatusChange = summary.getStatusChangeTime();
        long origUpdateTime = summary.getUpdateTime();
        assertEquals(EventStatus.STATUS_NEW, summary.getStatus());
        assertFalse(summary.hasCurrentUserUuid());
        assertFalse(summary.hasCurrentUserName());

        String userUuid = UUID.randomUUID().toString();
        String userName = "user" + random.nextInt(500);

        int numUpdated = eventSummaryDao.close(Collections.singletonList(summary.getUuid()), userUuid, userName);
        assertEquals(1, numUpdated);
        summary = eventSummaryDao.findByUuid(summary.getUuid());
        assertEquals(EventStatus.STATUS_CLOSED, summary.getStatus());
        assertTrue(summary.getStatusChangeTime() > origStatusChange);
        assertTrue(summary.getUpdateTime() > origUpdateTime);
        origStatusChange = summary.getStatusChangeTime();
        origUpdateTime = summary.getUpdateTime();

        /* Now reopen event */
        numUpdated = eventSummaryDao.reopen(Collections.singletonList(summary.getUuid()), userUuid, userName);
        assertEquals(1, numUpdated);
        EventSummary origSummary = summary;
        summary = eventSummaryDao.findByUuid(summary.getUuid());
        assertFalse(summary.hasCurrentUserUuid());
        assertFalse(summary.hasCurrentUserName());
        assertEquals(EventStatus.STATUS_NEW, summary.getStatus());
        assertTrue(summary.getStatusChangeTime() > origStatusChange);
        assertTrue(summary.getUpdateTime() > origUpdateTime);

        compareSummary(origSummary, summary);
    }

    @Test
    public void testAcknowledge() throws ZepException {
        EventSummary summary = createSummaryNew(createUniqueEvent());
        long origStatusChange = summary.getStatusChangeTime();
        long origUpdateTime = summary.getUpdateTime();
        assertEquals(EventStatus.STATUS_NEW, summary.getStatus());
        assertFalse(summary.hasCurrentUserUuid());
        assertFalse(summary.hasCurrentUserName());

        String userUuid = UUID.randomUUID().toString();
        String userName = "user" + random.nextInt(500);
        EventSummary origSummary = summary;
        int numUpdated = eventSummaryDao.acknowledge(Collections.singletonList(summary.getUuid()), userUuid,
                userName);
        assertEquals(1, numUpdated);
        summary = eventSummaryDao.findByUuid(summary.getUuid());
        assertEquals(userUuid, summary.getCurrentUserUuid());
        assertEquals(userName, summary.getCurrentUserName());
        assertEquals(EventStatus.STATUS_ACKNOWLEDGED, summary.getStatus());
        assertTrue(summary.getStatusChangeTime() > origStatusChange);
        assertTrue(summary.getUpdateTime() > origUpdateTime);

        compareSummary(origSummary, summary);

        /* Try acknowleding event again with the same user / uuid */
        assertEquals(0,
                eventSummaryDao.acknowledge(Collections.singletonList(summary.getUuid()), userUuid, userName));

        /* Change uuid and verify acknowledgement goes through */
        userUuid = UUID.randomUUID().toString();
        assertEquals(1,
                eventSummaryDao.acknowledge(Collections.singletonList(summary.getUuid()), userUuid, userName));

        /* Change username and verify acknowledgement goes through */
        userName = userName + "_1";
        assertEquals(1,
                eventSummaryDao.acknowledge(Collections.singletonList(summary.getUuid()), userUuid, userName));

        /* Change uuid to null and verify acknowledgement goes through */
        userUuid = null;
        assertEquals(1,
                eventSummaryDao.acknowledge(Collections.singletonList(summary.getUuid()), userUuid, userName));

        /* Change username to null and verify acknowledgement goes through */
        userName = null;
        assertEquals(1,
                eventSummaryDao.acknowledge(Collections.singletonList(summary.getUuid()), userUuid, userName));

        /* Acknowledge again with both set to null - shouldn't change */
        assertEquals(0,
                eventSummaryDao.acknowledge(Collections.singletonList(summary.getUuid()), userUuid, userName));
    }

    @Test
    public void testAcknowledgeNullUserNameUuid() throws ZepException {
        EventSummary summary = createSummaryNew(createUniqueEvent());
        long origStatusChange = summary.getStatusChangeTime();
        long origUpdateTime = summary.getUpdateTime();
        assertEquals(EventStatus.STATUS_NEW, summary.getStatus());
        assertFalse(summary.hasCurrentUserUuid());
        assertFalse(summary.hasCurrentUserName());

        EventSummary origSummary = summary;
        int numUpdated = eventSummaryDao.acknowledge(Collections.singletonList(summary.getUuid()), null, null);
        assertEquals(1, numUpdated);
        summary = eventSummaryDao.findByUuid(summary.getUuid());
        assertFalse(summary.hasCurrentUserUuid());
        assertFalse(summary.hasCurrentUserName());
        assertEquals(EventStatus.STATUS_ACKNOWLEDGED, summary.getStatus());
        assertTrue(summary.getStatusChangeTime() > origStatusChange);
        assertTrue(summary.getUpdateTime() > origUpdateTime);

        compareSummary(origSummary, summary);
    }

    @Test
    public void testSuppress() throws ZepException {
        EventSummary summary = createSummaryNew(createUniqueEvent());
        long origStatusChange = summary.getStatusChangeTime();
        long origUpdateTime = summary.getUpdateTime();
        assertEquals(EventStatus.STATUS_NEW, summary.getStatus());
        assertFalse(summary.hasCurrentUserUuid());
        assertFalse(summary.hasCurrentUserName());

        EventSummary origSummary = summary;
        int numUpdated = eventSummaryDao.suppress(Collections.singletonList(summary.getUuid()));
        assertEquals(1, numUpdated);
        summary = eventSummaryDao.findByUuid(summary.getUuid());
        assertEquals(EventStatus.STATUS_SUPPRESSED, summary.getStatus());
        assertTrue(summary.getStatusChangeTime() > origStatusChange);
        assertTrue(summary.getUpdateTime() > origUpdateTime);

        compareSummary(origSummary, summary);
    }

    @Test
    public void testClose() throws ZepException {
        EventSummary summary = createSummaryNew(createUniqueEvent());
        long origStatusChange = summary.getStatusChangeTime();
        long origUpdateTime = summary.getUpdateTime();
        assertEquals(EventStatus.STATUS_NEW, summary.getStatus());
        assertFalse(summary.hasCurrentUserUuid());
        assertFalse(summary.hasCurrentUserName());

        String userUuid = UUID.randomUUID().toString();
        String userName = "user" + random.nextInt(500);
        EventSummary origSummary = summary;
        int numUpdated = eventSummaryDao.close(Collections.singletonList(summary.getUuid()), userUuid, userName);
        assertEquals(1, numUpdated);
        summary = eventSummaryDao.findByUuid(summary.getUuid());
        assertEquals(EventStatus.STATUS_CLOSED, summary.getStatus());
        assertTrue(summary.getStatusChangeTime() > origStatusChange);
        assertTrue(summary.getUpdateTime() > origUpdateTime);
        assertFalse(summary.hasCurrentUserUuid());
        assertFalse(summary.hasCurrentUserName());

        compareSummary(origSummary, summary);
    }

    @Test
    public void testAddNote() throws ZepException {
        EventSummary summary = createSummaryNew(createUniqueEvent());
        assertEquals(0, summary.getNotesCount());
        EventNote note = EventNote.newBuilder().setMessage("My Note").setUserName("pkw")
                .setUserUuid(UUID.randomUUID().toString()).build();
        assertEquals(1, eventSummaryDao.addNote(summary.getUuid(), note));
        EventNote note2 = EventNote.newBuilder().setMessage("My Note 2").setUserName("kww")
                .setUserUuid(UUID.randomUUID().toString()).build();
        assertEquals(1, eventSummaryDao.addNote(summary.getUuid(), note2));
        summary = eventSummaryDao.findByUuid(summary.getUuid());
        assertEquals(2, summary.getNotesCount());
        // Notes returned in descending order to match previous behavior
        assertEquals("My Note 2", summary.getNotes(0).getMessage());
        assertEquals("kww", summary.getNotes(0).getUserName());
        assertEquals("My Note", summary.getNotes(1).getMessage());
        assertEquals("pkw", summary.getNotes(1).getUserName());
    }

    @Test
    public void testUpdateDetailsReplace() throws ZepException {
        Event newEvent = createUniqueEvent();
        Event.Builder builder = Event.newBuilder(newEvent).clearDetails();
        builder.addDetails(createDetail("A", "A"));
        builder.addDetails(createDetail("B", "B"));
        builder.addDetails(createDetail("C", "C"));
        newEvent = builder.build();

        EventSummary summary = createSummaryNew(newEvent);
        Event storedEvent = summary.getOccurrence(0);
        assertEquals(3, storedEvent.getDetailsCount());

        List<EventDetail> newDetailsList = new ArrayList<EventDetail>();
        newDetailsList.add(createDetail("B", "B1"));
        newDetailsList.add(createDetail("C"));
        newDetailsList.add(createDetail("D", "D"));
        EventDetailSet newDetails = EventDetailSet.newBuilder().addAllDetails(newDetailsList).build();

        // ensure update works correctly
        assertEquals(1, eventSummaryDao.updateDetails(summary.getUuid(), newDetails));

        // verify new contents of details
        summary = eventSummaryDao.findByUuid(summary.getUuid());

        List<EventDetail> resultDetails = summary.getOccurrence(0).getDetailsList();
        assertEquals(3, resultDetails.size());

        Map<String, List<String>> resultDetailsMap = detailsToMap(resultDetails);
        assertEquals(Collections.singletonList("A"), resultDetailsMap.get("A"));
        assertEquals(Collections.singletonList("B1"), resultDetailsMap.get("B"));
        assertEquals(Collections.singletonList("D"), resultDetailsMap.get("D"));
    }

    @Test
    public void testUpdateDetailsAppend() throws ZepException {
        Event newEvent = createUniqueEvent();
        Event.Builder builder = Event.newBuilder(newEvent).clearDetails();
        builder.addDetails(createDetail("A", "A"));
        builder.addDetails(createDetail("B", "B"));
        builder.addDetails(createDetail("C", "C"));
        newEvent = builder.build();

        EventSummary summary = createSummaryNew(newEvent);
        Event storedEvent = summary.getOccurrence(0);
        assertEquals(3, storedEvent.getDetailsCount());

        List<EventDetail> newDetailsList = new ArrayList<EventDetail>();
        newDetailsList.add(createDetail("A", EventDetailMergeBehavior.APPEND, "A1", "A2"));
        EventDetailSet newDetails = EventDetailSet.newBuilder().addAllDetails(newDetailsList).build();

        // ensure update works correctly
        assertEquals(1, eventSummaryDao.updateDetails(summary.getUuid(), newDetails));

        // verify new contents of details
        summary = eventSummaryDao.findByUuid(summary.getUuid());

        List<EventDetail> resultDetails = summary.getOccurrence(0).getDetailsList();
        assertEquals(3, resultDetails.size());

        Map<String, List<String>> resultDetailsMap = detailsToMap(resultDetails);
        assertEquals(Arrays.asList("A", "A1", "A2"), resultDetailsMap.get("A"));
        assertEquals(Collections.singletonList("B"), resultDetailsMap.get("B"));
        assertEquals(Collections.singletonList("C"), resultDetailsMap.get("C"));
    }

    @Test
    public void testUpdateDetailsUnique() throws ZepException {
        Event newEvent = createUniqueEvent();
        Event.Builder builder = Event.newBuilder(newEvent).clearDetails();
        builder.addDetails(createDetail("A", "A1", "A2", "A3"));
        builder.addDetails(createDetail("B", "B"));
        builder.addDetails(createDetail("C", "C"));
        newEvent = builder.build();

        EventSummary summary = createSummaryNew(newEvent);
        Event storedEvent = summary.getOccurrence(0);
        assertEquals(3, storedEvent.getDetailsCount());

        List<EventDetail> newDetailsList = new ArrayList<EventDetail>();
        newDetailsList.add(createDetail("A", EventDetailMergeBehavior.UNIQUE, "A4", "A1", "A5", "A2"));
        EventDetailSet newDetails = EventDetailSet.newBuilder().addAllDetails(newDetailsList).build();

        // ensure update works correctly
        assertEquals(1, eventSummaryDao.updateDetails(summary.getUuid(), newDetails));

        // verify new contents of details
        summary = eventSummaryDao.findByUuid(summary.getUuid());

        List<EventDetail> resultDetails = summary.getOccurrence(0).getDetailsList();
        assertEquals(3, resultDetails.size());

        Map<String, List<String>> resultDetailsMap = detailsToMap(resultDetails);
        assertEquals(Arrays.asList("A1", "A2", "A3", "A4", "A5"), resultDetailsMap.get("A"));
        assertEquals(Collections.singletonList("B"), resultDetailsMap.get("B"));
        assertEquals(Collections.singletonList("C"), resultDetailsMap.get("C"));
    }

    private Event createOldEvent(long duration, TimeUnit unit, EventSeverity severity) {
        Event.Builder eventBuilder = Event.newBuilder(createUniqueEvent());
        eventBuilder.setCreatedTime(System.currentTimeMillis() - unit.toMillis(duration));
        eventBuilder.setSeverity(severity);
        return eventBuilder.build();
    }

    @Test
    public void testAgeEvents() throws ZepException {
        EventSummary warning = createSummaryNew(
                createOldEvent(5, TimeUnit.MINUTES, EventSeverity.SEVERITY_WARNING));
        EventSummary error = createSummaryNew(createOldEvent(5, TimeUnit.MINUTES, EventSeverity.SEVERITY_ERROR));
        EventSummary info = createSummaryNew(createOldEvent(5, TimeUnit.MINUTES, EventSeverity.SEVERITY_INFO));

        /* Aging should have aged WARNING and INFO events and left ERROR */
        int numAged = eventSummaryDao.ageEvents(4, TimeUnit.MINUTES, EventSeverity.SEVERITY_ERROR, 100, false);
        assertEquals(2, numAged);
        assertEquals(error, eventSummaryDao.findByUuid(error.getUuid()));
        /* Compare ignoring status change time */
        EventSummary summaryWarning = eventSummaryDao.findByUuid(warning.getUuid());
        assertTrue(summaryWarning.getStatusChangeTime() > warning.getStatusChangeTime());
        assertEquals(EventStatus.STATUS_AGED, summaryWarning.getStatus());
        EventSummary summaryInfo = eventSummaryDao.findByUuid(info.getUuid());
        assertTrue(summaryInfo.getStatusChangeTime() > info.getStatusChangeTime());
        assertEquals(EventStatus.STATUS_AGED, summaryInfo.getStatus());
        compareSummary(summaryWarning, warning);
        compareSummary(summaryInfo, info);

        int archived = eventSummaryDao.archive(4, TimeUnit.MINUTES, 100);
        assertEquals(2, archived);
        assertNull(eventSummaryDao.findByUuid(warning.getUuid()));
        assertNull(eventSummaryDao.findByUuid(info.getUuid()));
        /* Compare ignoring status change time */
        EventSummary archivedWarning = eventArchiveDao.findByUuid(warning.getUuid());
        assertTrue(archivedWarning.getStatusChangeTime() > warning.getStatusChangeTime());
        EventSummary archivedInfo = eventArchiveDao.findByUuid(info.getUuid());
        assertTrue(archivedInfo.getStatusChangeTime() > info.getStatusChangeTime());
        compareSummary(archivedWarning, summaryWarning);
        compareSummary(archivedInfo, summaryInfo);

        /* Aging with inclusive severity should age WARNING, INFO, and ERROR */
        numAged = eventSummaryDao.ageEvents(4, TimeUnit.MINUTES, EventSeverity.SEVERITY_ERROR, 100, true);
        assertEquals(1, numAged);
        EventSummary summaryError = eventSummaryDao.findByUuid(error.getUuid());
        assertEquals(EventStatus.STATUS_AGED, summaryError.getStatus());
    }

    @Test
    public void testArchiveEventsEmpty() throws ZepException {
        // Make sure we don't fail if there are no events to archive
        int numArchived = this.eventSummaryDao.archive(0L, TimeUnit.SECONDS, 500);
        assertEquals(0, numArchived);
    }

    @Test
    public void testClearEvents() throws ZepException {
        Event event = createUniqueEvent();
        EventSummary normalEvent = createSummaryNew(
                Event.newBuilder(event).setSeverity(EventSeverity.SEVERITY_WARNING).setEventKey("MyKey1").build());
        EventSummary clearEvent = createSummaryClear(
                Event.newBuilder(createUniqueEvent()).setSeverity(EventSeverity.SEVERITY_CLEAR)
                        .setActor(event.getActor()).setEventKey("MyKey1").build(),
                Collections.singleton(normalEvent.getOccurrence(0).getEventClass()));
        assertEquals(EventStatus.STATUS_CLOSED, clearEvent.getStatus());
        EventSummary normalEventSummary = eventSummaryDao.findByUuid(normalEvent.getUuid());
        assertEquals(EventStatus.STATUS_CLEARED, normalEventSummary.getStatus());
        assertEquals(clearEvent.getUuid(), normalEventSummary.getClearedByEventUuid());
        assertTrue(normalEventSummary.getStatusChangeTime() > normalEvent.getStatusChangeTime());
        compareSummary(normalEvent, normalEventSummary);
    }

    @Test
    public void testClearEventsCustomGenerator() throws ZepException {
        Event event = Event.newBuilder(createUniqueEvent()).setSeverity(EventSeverity.SEVERITY_WARNING)
                .setEventKey("MyKey2").build();
        Event clear = Event.newBuilder(createUniqueEvent()).setSeverity(EventSeverity.SEVERITY_CLEAR)
                .setActor(event.getActor()).setEventKey("MyKey2").build();
        ClearFingerprintGenerator generator = new ClearFingerprintGenerator() {
            @Override
            public String generateClearFingerprint(Event event) {
                return EventDaoUtils.DEFAULT_GENERATOR.generateClearFingerprint(event) + "|MyCustomField";
            }

            @Override
            public List<String> generateClearFingerprints(Event event, Set<String> clearClasses) {
                final List<String> original = EventDaoUtils.DEFAULT_GENERATOR.generateClearFingerprints(event,
                        clearClasses);
                final List<String> updated = new ArrayList<String>(original.size());
                for (String originalFingerprint : original) {
                    updated.add(originalFingerprint + "|MyCustomField");
                }
                return updated;
            }
        };
        EventPreCreateContextImpl ctx = new EventPreCreateContextImpl();
        ctx.setClearFingerprintGenerator(generator);
        EventSummary normalEvent = eventSummaryDao.findByUuid(eventSummaryDao.create(event, ctx));
        ctx.getClearClasses().add(event.getEventClass());
        EventSummary clearEvent = eventSummaryDao.findByUuid(eventSummaryDao.create(clear, ctx));
        assertEquals(EventStatus.STATUS_CLOSED, clearEvent.getStatus());
        EventSummary normalEventSummary = eventSummaryDao.findByUuid(normalEvent.getUuid());
        assertEquals(EventStatus.STATUS_CLEARED, normalEventSummary.getStatus());
        assertEquals(clearEvent.getUuid(), normalEventSummary.getClearedByEventUuid());
        assertTrue(normalEventSummary.getStatusChangeTime() > normalEvent.getStatusChangeTime());
        compareSummary(normalEvent, normalEventSummary);
    }

    @Test
    public void testMergeDuplicateDetails() throws ZepException {
        String name = "dup1";
        String val1 = "dupval";
        String val2 = "dupval2";
        String val3 = "dupval3";
        Event.Builder eventBuilder = Event.newBuilder(createUniqueEvent()).clearDetails();
        eventBuilder.addDetails(createDetail(name, val1));
        eventBuilder.addDetails(createDetail(name, val2, val3));
        Event event = eventBuilder.build();
        EventSummary summary = createSummaryNew(event);
        assertEquals(1, summary.getOccurrence(0).getDetailsCount());
        EventDetail detailFromDb = summary.getOccurrence(0).getDetails(0);
        assertEquals(name, detailFromDb.getName());
        assertEquals(Arrays.asList(val1, val2, val3), detailFromDb.getValueList());
    }

    @Test
    public void testFilterDuplicateTags() throws ZepException {
        Event.Builder eventBuilder = Event.newBuilder(createUniqueEvent()).clearTags();
        String uuid = UUID.randomUUID().toString();
        eventBuilder.addTags(EventTag.newBuilder().setType(ModelElementType.DEVICE.name()).addUuid(uuid).build());
        eventBuilder.addTags(EventTag.newBuilder().setType(ModelElementType.DEVICE.name()).addUuid(uuid).build());
        eventBuilder.addTags(EventTag.newBuilder().setType(ModelElementType.DEVICE.name()).addUuid(uuid).build());
        Event event = eventBuilder.build();
        EventSummary summary = createSummaryNew(event);
        int numFound = 0;
        for (EventTag tag : summary.getOccurrence(0).getTagsList()) {
            for (String tagUuid : tag.getUuidList()) {
                if (tagUuid.equals(uuid)) {
                    numFound++;
                }
            }
        }
        assertEquals(1, numFound);
    }

    @Test
    public void testChangeSeverity() throws ZepException {
        // Verify that severity changes when new event with same fingerprint comes in with new severity
        Event.Builder firstBuilder = Event.newBuilder(createUniqueEvent());
        firstBuilder.setSeverity(EventSeverity.SEVERITY_WARNING);
        Event first = firstBuilder.build();

        EventSummary firstSummary = createSummaryNew(first);

        Event.Builder secondBuilder = Event.newBuilder(first);
        secondBuilder.setCreatedTime(first.getCreatedTime() + 10);
        secondBuilder.setSeverity(EventSeverity.SEVERITY_INFO);
        Event second = secondBuilder.build();

        EventSummary secondSummary = createSummaryNew(second);
        assertEquals(firstSummary.getUuid(), secondSummary.getUuid());
        assertEquals(2, secondSummary.getCount());
        assertEquals(EventSeverity.SEVERITY_INFO, secondSummary.getOccurrence(0).getSeverity());
    }

    @Test
    public void testReidentifyDevice() throws ZepException {
        Event.Builder builder = Event.newBuilder(createUniqueEvent());
        EventActor.Builder actorBuilder = EventActor.newBuilder(builder.getActor());
        actorBuilder.clearElementSubIdentifier().clearElementSubTypeId().clearElementSubUuid();
        actorBuilder.clearElementUuid();
        builder.setActor(actorBuilder.build());

        EventSummary summary = createSummaryNew(builder.build());
        Event occurrence = summary.getOccurrence(0);
        assertFalse(occurrence.getActor().hasElementUuid());

        final String elementUuid = UUID.randomUUID().toString();
        int numRows = this.eventSummaryDao.reidentify(occurrence.getActor().getElementTypeId(),
                occurrence.getActor().getElementIdentifier(), elementUuid, occurrence.getActor().getElementTitle(),
                null);
        assertEquals(1, numRows);
        EventSummary summaryFromDb = this.eventSummaryDao.findByUuid(summary.getUuid());
        assertTrue(summaryFromDb.getUpdateTime() > summary.getUpdateTime());
        assertEquals(elementUuid, summaryFromDb.getOccurrence(0).getActor().getElementUuid());
    }

    @Test
    public void testReidentifyLongTitle() throws ZepException {
        Event.Builder builder = Event.newBuilder(createUniqueEvent());
        EventActor.Builder actorBuilder = EventActor.newBuilder(builder.getActor());
        actorBuilder.clearElementSubIdentifier().clearElementSubTypeId().clearElementSubUuid();
        actorBuilder.clearElementUuid().clearElementTitle();
        builder.setActor(actorBuilder.build());

        EventSummary summary = createSummaryNew(builder.build());
        Event occurrence = summary.getOccurrence(0);
        assertFalse(occurrence.getActor().hasElementUuid());

        final String elementUuid = UUID.randomUUID().toString();
        final String elementTitle = createRandomMaxString(EventConstants.MAX_ELEMENT_TITLE + 1);
        int numRows = this.eventSummaryDao.reidentify(occurrence.getActor().getElementTypeId(),
                occurrence.getActor().getElementIdentifier(), elementUuid, elementTitle, null);
        assertEquals(1, numRows);
        EventSummary summaryFromDb = this.eventSummaryDao.findByUuid(summary.getUuid());
        assertTrue(summaryFromDb.getUpdateTime() > summary.getUpdateTime());
        assertEquals(elementUuid, summaryFromDb.getOccurrence(0).getActor().getElementUuid());
        assertEquals(EventConstants.MAX_ELEMENT_TITLE,
                summaryFromDb.getOccurrence(0).getActor().getElementTitle().length());
        assertEquals(elementTitle.substring(0, EventConstants.MAX_ELEMENT_TITLE),
                summaryFromDb.getOccurrence(0).getActor().getElementTitle());
    }

    @Test
    public void testReidentifyComponent()
            throws ZepException, NoSuchAlgorithmException, UnsupportedEncodingException {
        Event.Builder builder = Event.newBuilder(createUniqueEvent());
        EventActor.Builder actorBuilder = EventActor.newBuilder(builder.getActor());
        actorBuilder.clearElementSubUuid();
        builder.setActor(actorBuilder.build());

        EventSummary summary = createSummaryNew(builder.build());
        Event occurrence = summary.getOccurrence(0);
        EventActor actor = occurrence.getActor();
        assertFalse(occurrence.getActor().hasElementSubUuid());

        final String elementSubUuid = UUID.randomUUID().toString();
        int numRows = this.eventSummaryDao.reidentify(actor.getElementSubTypeId(), actor.getElementSubIdentifier(),
                elementSubUuid, actor.getElementSubTitle(), actor.getElementUuid());
        assertEquals(1, numRows);
        EventSummary summaryFromDb = this.eventSummaryDao.findByUuid(summary.getUuid());
        assertTrue(summaryFromDb.getUpdateTime() > summary.getUpdateTime());
        assertEquals(elementSubUuid, summaryFromDb.getOccurrence(0).getActor().getElementSubUuid());
        // Ensure clear_fingerprint_hash was updated
        String clearHashString = EventDaoUtils.join('|', elementSubUuid, occurrence.getEventClass(),
                occurrence.getEventKey());
        byte[] clearHash = DaoUtils.sha1(clearHashString);
        Map<String, Object> fields = Collections.singletonMap(COLUMN_UUID,
                databaseCompatibility.getUUIDConverter().toDatabaseType(summary.getUuid()));
        byte[] clearHashFromDb = this.simpleJdbcTemplate.query(
                "SELECT clear_fingerprint_hash FROM event_summary WHERE uuid=:uuid", new RowMapper<byte[]>() {
                    @Override
                    public byte[] mapRow(ResultSet rs, int rowNum) throws SQLException {
                        return rs.getBytes("clear_fingerprint_hash");
                    }
                }, fields).get(0);
        assertArrayEquals(clearHash, clearHashFromDb);
    }

    @Test
    public void testDeidentifyDevice() throws ZepException {
        EventSummary summary = createSummaryNew(createUniqueEvent());
        Event occurrence = summary.getOccurrence(0);
        EventActor actor = occurrence.getActor();
        assertTrue(occurrence.getActor().hasElementUuid());

        int numRows = this.eventSummaryDao.deidentify(actor.getElementUuid());
        assertEquals(1, numRows);
        EventSummary summaryFromDb = this.eventSummaryDao.findByUuid(summary.getUuid());
        assertTrue(summaryFromDb.getUpdateTime() > summary.getUpdateTime());
        assertFalse(summaryFromDb.getOccurrence(0).getActor().hasElementUuid());
    }

    @Test
    public void testDeidentifyComponent()
            throws ZepException, NoSuchAlgorithmException, UnsupportedEncodingException {
        EventSummary summary = createSummaryNew(createUniqueEvent());
        Event occurrence = summary.getOccurrence(0);
        EventActor actor = occurrence.getActor();
        assertTrue(occurrence.getActor().hasElementSubUuid());

        int numRows = this.eventSummaryDao.deidentify(actor.getElementSubUuid());
        assertEquals(1, numRows);
        EventSummary summaryFromDb = this.eventSummaryDao.findByUuid(summary.getUuid());
        assertTrue(summaryFromDb.getUpdateTime() > summary.getUpdateTime());
        assertFalse(summaryFromDb.getOccurrence(0).getActor().hasElementSubUuid());
        // Ensure clear_fingerprint_hash was updated
        String clearHashString = EventDaoUtils.join('|', actor.getElementIdentifier(),
                actor.getElementSubIdentifier(), occurrence.getEventClass(), occurrence.getEventKey());
        byte[] clearHash = DaoUtils.sha1(clearHashString);
        Map<String, Object> fields = Collections.singletonMap(COLUMN_UUID,
                databaseCompatibility.getUUIDConverter().toDatabaseType(summary.getUuid()));
        byte[] clearHashFromDb = this.simpleJdbcTemplate.query(
                "SELECT clear_fingerprint_hash FROM event_summary WHERE uuid=:uuid", new RowMapper<byte[]>() {
                    @Override
                    public byte[] mapRow(ResultSet rs, int rowNum) throws SQLException {
                        return rs.getBytes("clear_fingerprint_hash");
                    }
                }, fields).get(0);
        assertArrayEquals(clearHash, clearHashFromDb);
    }

    private static Map<String, List<String>> detailsToMap(List<EventDetail> details) {
        Map<String, List<String>> detailsMap = new HashMap<String, List<String>>(details.size());
        for (EventDetail detail : details) {
            detailsMap.put(detail.getName(), detail.getValueList());
        }
        return detailsMap;
    }

    private static EventDetail createDetail(String name, String... values) {
        return createDetail(name, null, values);
    }

    private static EventDetail createDetail(String name, EventDetailMergeBehavior mergeBehavior, String... values) {
        EventDetail.Builder detailBuilder = EventDetail.newBuilder();
        detailBuilder.setName(name);
        if (mergeBehavior != null) {
            detailBuilder.setMergeBehavior(mergeBehavior);
        }
        for (String value : values) {
            detailBuilder.addValue(value);
        }
        return detailBuilder.build();
    }

    @Test
    public void testMergeDetailsReplace() throws ZepException {
        Event.Builder eventBuilder = Event.newBuilder(createUniqueEvent());
        eventBuilder.clearDetails();
        eventBuilder.addDetails(createDetail("foo", "bar", "baz"));
        eventBuilder.addDetails(createDetail("foo2", "bar2", "baz2"));
        final Event event = eventBuilder.build();

        EventSummary summary = createSummaryNew(event);
        compareEvents(event, summary.getOccurrence(0));

        /* Add a new detail, don't specify an old one, and replace an existing one */
        Event.Builder newEventBuilder = Event.newBuilder(event);
        newEventBuilder.clearDetails();
        /* Update foo */
        newEventBuilder.addDetails(createDetail("foo", "foobar", "foobaz"));
        /* Don't specify foo2 */
        /* Add a new detail foo3 */
        newEventBuilder.addDetails(createDetail("foo3", "foobar3", "foobaz3"));
        final Event newEvent = newEventBuilder.build();

        EventSummary newSummary = createSummaryNew(newEvent);
        assertEquals(2, newSummary.getCount());
        Map<String, List<String>> detailsMap = detailsToMap(newSummary.getOccurrence(0).getDetailsList());
        assertEquals(Arrays.asList("foobar", "foobaz"), detailsMap.get("foo"));
        assertEquals(Arrays.asList("bar2", "baz2"), detailsMap.get("foo2"));
        assertEquals(Arrays.asList("foobar3", "foobaz3"), detailsMap.get("foo3"));
    }

    @Test
    public void testMergeDetailsAppend() throws ZepException {
        Event.Builder eventBuilder = Event.newBuilder(createUniqueEvent());
        eventBuilder.clearDetails();
        eventBuilder.addDetails(createDetail("foo", "bar", "baz"));
        eventBuilder.addDetails(createDetail("foo2", "bar2", "baz2"));
        final Event event = eventBuilder.build();

        EventSummary summary = createSummaryNew(event);
        compareEvents(event, summary.getOccurrence(0));

        /* Add a new detail, don't specify an old one, and replace an existing one */
        Event.Builder newEventBuilder = Event.newBuilder(event);
        newEventBuilder.clearDetails();
        /* Update foo */
        newEventBuilder.addDetails(createDetail("foo", EventDetailMergeBehavior.APPEND, "bar", "foobar", "foobaz"));
        /* Don't specify foo2 */
        /* Add a new detail foo3 */
        newEventBuilder.addDetails(createDetail("foo3", "foobar3", "foobaz3"));
        final Event newEvent = newEventBuilder.build();

        EventSummary newSummary = createSummaryNew(newEvent);
        assertEquals(2, newSummary.getCount());
        Map<String, List<String>> detailsMap = detailsToMap(newSummary.getOccurrence(0).getDetailsList());
        assertEquals(Arrays.asList("bar", "baz", "bar", "foobar", "foobaz"), detailsMap.get("foo"));
        assertEquals(Arrays.asList("bar2", "baz2"), detailsMap.get("foo2"));
        assertEquals(Arrays.asList("foobar3", "foobaz3"), detailsMap.get("foo3"));
    }

    @Test
    public void testMergeDetailsUnique() throws ZepException {
        Event.Builder eventBuilder = Event.newBuilder(createUniqueEvent());
        eventBuilder.clearDetails();
        eventBuilder.addDetails(createDetail("foo", "bar", "baz"));
        eventBuilder.addDetails(createDetail("foo2", "bar2", "baz2"));
        final Event event = eventBuilder.build();

        EventSummary summary = createSummaryNew(event);
        compareEvents(event, summary.getOccurrence(0));

        /* Add a new detail, don't specify an old one, and replace an existing one */
        Event.Builder newEventBuilder = Event.newBuilder(event);
        newEventBuilder.clearDetails();
        /* Update foo */
        newEventBuilder.addDetails(createDetail("foo", EventDetailMergeBehavior.UNIQUE, "baz", "foobar", "foobaz"));
        /* Don't specify foo2 */
        /* Add a new detail foo3 */
        newEventBuilder.addDetails(createDetail("foo3", "foobar3", "foobaz3"));
        final Event newEvent = newEventBuilder.build();

        EventSummary newSummary = createSummaryNew(newEvent);
        assertEquals(2, newSummary.getCount());
        Map<String, List<String>> detailsMap = detailsToMap(newSummary.getOccurrence(0).getDetailsList());
        assertEquals(Arrays.asList("bar", "baz", "foobar", "foobaz"), detailsMap.get("foo"));
        assertEquals(Arrays.asList("bar2", "baz2"), detailsMap.get("foo2"));
        assertEquals(Arrays.asList("foobar3", "foobaz3"), detailsMap.get("foo3"));
    }

    @Test
    public void testArchive() throws ZepException {
        EventSummary summary1 = createSummaryNew(createUniqueEvent());
        EventSummary summary2 = createSummary(createUniqueEvent(), EventStatus.STATUS_CLOSED);
        EventSummary summary3 = createSummary(createUniqueEvent(), EventStatus.STATUS_CLEARED);

        List<String> uuids = Arrays.asList(summary1.getUuid(), summary2.getUuid(), summary3.getUuid());
        int numArchived = this.eventSummaryDao.archive(uuids);
        assertEquals(uuids.size() - 1, numArchived);
        List<EventSummary> summaries = this.eventSummaryDao.findByUuids(uuids);
        assertEquals(1, summaries.size());
        assertEquals(summary1, summaries.get(0));

        List<EventSummary> archived = this.eventArchiveDao.findByUuids(uuids);
        assertEquals(2, archived.size());
        boolean found2 = false, found3 = false;
        for (EventSummary archivedSummary : archived) {
            if (summary2.getUuid().equals(archivedSummary.getUuid())) {
                found2 = true;
            } else if (summary3.getUuid().equals(archivedSummary.getUuid())) {
                found3 = true;
            }
        }
        assertTrue(found2 && found3);
    }

    @Test
    public void testMergeDetailsNull() throws ZepException {
        Event firstEvent = Event.newBuilder(createUniqueEvent()).clearDetails().build();

        EventSummary firstSummary = createSummaryNew(firstEvent);
        assertEquals(0, firstSummary.getOccurrence(0).getDetailsCount());

        Event secondEvent = Event.newBuilder(firstEvent).addDetails(createDetail("detail1", "A"))
                .setCreatedTime(System.currentTimeMillis()).build();
        EventSummary secondSummary = createSummaryNew(secondEvent);
        assertEquals(firstSummary.getUuid(), secondSummary.getUuid());
        assertEquals(1, secondSummary.getOccurrence(0).getDetailsCount());
        assertEquals(secondEvent.getDetailsList(), secondSummary.getOccurrence(0).getDetailsList());
    }

    public static EventSummary createRandomSummary() {
        Random r = new Random();
        Event event = EventTestUtils.createSampleEvent();
        EventSummary.Builder summaryBuilder = EventSummary.newBuilder();
        summaryBuilder.addOccurrence(event);
        summaryBuilder.addAuditLog(EventAuditLog.newBuilder().setNewStatus(EventStatus.STATUS_ACKNOWLEDGED)
                .setTimestamp(System.currentTimeMillis()).setUserName("pkw")
                .setUserUuid(UUID.randomUUID().toString()));
        summaryBuilder.addAuditLog(EventAuditLog.newBuilder().setNewStatus(EventStatus.STATUS_CLOSED)
                .setTimestamp(System.currentTimeMillis()).setUserName("pkw2")
                .setUserUuid(UUID.randomUUID().toString()));
        summaryBuilder.setClearedByEventUuid(UUID.randomUUID().toString());
        summaryBuilder.setCount(r.nextInt(1000) + 1);
        summaryBuilder.setStatus(EventStatus.STATUS_CLEARED);
        summaryBuilder.setCurrentUserName("pkw3");
        summaryBuilder.setCurrentUserUuid(UUID.randomUUID().toString());
        summaryBuilder.setFirstSeenTime(System.currentTimeMillis() - 5000);
        summaryBuilder.setLastSeenTime(System.currentTimeMillis());
        summaryBuilder.setStatusChangeTime(System.currentTimeMillis());
        summaryBuilder.setUuid(UUID.randomUUID().toString());
        summaryBuilder.addNotes(EventNote.newBuilder().setCreatedTime(System.currentTimeMillis() - 100)
                .setMessage("Note 1").setUserName("pkw4").setUserUuid(UUID.randomUUID().toString()));
        summaryBuilder.addNotes(EventNote.newBuilder().setCreatedTime(System.currentTimeMillis())
                .setMessage("Note 2").setUserName("pkw5").setUserUuid(UUID.randomUUID().toString()));
        return summaryBuilder.build();
    }

    public static void compareImported(EventSummary original, EventSummary imported) {
        // First, verify migrate_update_time == update_time in imported event
        final long updateTime = imported.getUpdateTime();
        int migratedDetailIndex = -1;
        int isArchiveDetailIndex = -1;
        for (int detailIndex = 0; detailIndex < imported.getOccurrence(0).getDetailsCount(); detailIndex++) {
            EventDetail detail = imported.getOccurrence(0).getDetails(detailIndex);
            if (ZepConstants.DETAIL_MIGRATE_UPDATE_TIME.equals(detail.getName())) {
                // No duplicate details
                assertEquals(-1, migratedDetailIndex);
                migratedDetailIndex = detailIndex;
                assertEquals(Arrays.asList(Long.toString(updateTime)), detail.getValueList());
            } else if ("is_archive".equals(detail.getName())) {
                assertEquals(-1, isArchiveDetailIndex);
                isArchiveDetailIndex = detailIndex;
            }
        }
        assertTrue(migratedDetailIndex >= 0);

        // Import process creates a detail and also updates the update_time on the protobuf - clear these fields
        // so that comparison can work on all the rest of the fields.
        EventSummary.Builder importedBuilder = EventSummary.newBuilder(imported);
        Event.Builder occurrenceBuilder = importedBuilder.getOccurrenceBuilder(0);
        importedBuilder.clearUpdateTime();
        if (isArchiveDetailIndex >= 0) {
            int a = Math.min(migratedDetailIndex, isArchiveDetailIndex);
            int b = Math.max(migratedDetailIndex, isArchiveDetailIndex) - 1;
            occurrenceBuilder.removeDetails(a);
            occurrenceBuilder.removeDetails(b);
        } else {
            occurrenceBuilder.removeDetails(migratedDetailIndex);
        }

        // Original event has a generated UUID on the occurrence but we don't store an occurrence for migrated events -
        // clear it and the created_time to enable comparison.
        EventSummary.Builder originalBuilder = EventSummary.newBuilder(original);
        Event.Builder originalOccurrenceBuilder = originalBuilder.getOccurrenceBuilder(0);
        originalOccurrenceBuilder.clearUuid();
        originalOccurrenceBuilder.setCreatedTime(occurrenceBuilder.getCreatedTime());
        originalOccurrenceBuilder.clearTags().addAllTags(EventDaoHelper.buildTags(original.getOccurrence(0)));

        assertEquals(originalBuilder.build(), importedBuilder.build());
    }

    @Test
    public void testImportEvent() throws ZepException {
        EventSummary summary = EventSummaryDaoImplIT.createRandomSummary();
        this.eventSummaryDao.importEvent(summary);
        compareImported(summary, this.eventSummaryDao.findByUuid(summary.getUuid()));
        // Verify we can't insert an event more than once
        try {
            this.eventSummaryDao.importEvent(summary);
            fail("Expected duplicate import event to fail");
        } catch (DuplicateKeyException e) {
            // Expected
        }
    }

    @Test
    public void testDedupOutOfOrder() throws ZepException {
        Event event = EventTestUtils.createSampleEvent();
        EventSummary eventSummaryFromDb = createSummaryNew(event);
        Event eventFromSummary = eventSummaryFromDb.getOccurrence(0);
        compareEvents(event, eventFromSummary);

        Event firstEvent = Event.newBuilder(event).setCreatedTime(event.getCreatedTime() - 10000)
                .setSummary("New summary entry").build();
        EventSummary updatedEventSummaryFromDb = createSummaryNew(firstEvent);
        Event updatedEventFromSummary = updatedEventSummaryFromDb.getOccurrence(0);
        compareEvents(event, updatedEventFromSummary);
        assertEquals(firstEvent.getCreatedTime(), updatedEventSummaryFromDb.getFirstSeenTime());
        assertEquals(event.getCreatedTime(), updatedEventSummaryFromDb.getLastSeenTime());
        assertEquals(2, updatedEventSummaryFromDb.getCount());

        Event middleEvent = Event.newBuilder(event).setCreatedTime(event.getCreatedTime() - 5000)
                .setSummary("New summary entry").build();
        updatedEventSummaryFromDb = createSummaryNew(middleEvent);
        updatedEventFromSummary = updatedEventSummaryFromDb.getOccurrence(0);
        compareEvents(event, updatedEventFromSummary);
        assertEquals(firstEvent.getCreatedTime(), updatedEventSummaryFromDb.getFirstSeenTime());
        assertEquals(event.getCreatedTime(), updatedEventSummaryFromDb.getLastSeenTime());
        assertEquals(3, updatedEventSummaryFromDb.getCount());

        // Compare old and new summary items to verify the old event didn't change anything but update time and
        // first seen time.
        EventSummary oldSummaryForComparison = EventSummary.newBuilder(eventSummaryFromDb).clearUpdateTime()
                .clearFirstSeenTime().clearCount().build();
        EventSummary newSummaryForComparison = EventSummary.newBuilder(updatedEventSummaryFromDb).clearUpdateTime()
                .clearFirstSeenTime().clearCount().build();
        assertEquals(oldSummaryForComparison, newSummaryForComparison);
    }

    @Test
    public void testDedupDetailsOutOfOrder() throws ZepException {
        Event.Builder eventBuilder = Event.newBuilder(EventTestUtils.createSampleEvent());
        eventBuilder.clearDetails();
        eventBuilder.addDetails(EventDetail.newBuilder().setName("a").addAllValue(Arrays.asList("b", "c")));
        eventBuilder.addDetails(EventDetail.newBuilder().setName("b").addAllValue(Arrays.asList("b1", "b2")));
        Event event = eventBuilder.build();
        EventSummary eventSummaryFromDb = createSummaryNew(event);

        Event earlierEvent = Event.newBuilder(event).setCreatedTime(event.getCreatedTime() - 5000).clearDetails()
                .addDetails(EventDetail.newBuilder().setName("a").addAllValue(Arrays.asList("c", "d")))
                .addDetails(EventDetail.newBuilder().setName("c").addAllValue(Arrays.asList("c1", "c2"))).build();
        EventSummary updatedEventSummaryFromDb = createSummaryNew(earlierEvent);
        Event updatedEvent = updatedEventSummaryFromDb.getOccurrence(0);
        assertEquals(3, updatedEvent.getDetailsCount());

        // Detail values from the later event take precedence over the ones from the earlier event
        Map<String, List<String>> detailsMap = detailsToMap(updatedEvent.getDetailsList());
        assertEquals(Arrays.asList("b", "c"), detailsMap.get("a"));
        assertEquals(Arrays.asList("b1", "b2"), detailsMap.get("b"));
        assertEquals(Arrays.asList("c1", "c2"), detailsMap.get("c"));
    }

    private EventDetail createDetailOfSize(String name, int detailLength, String prefix,
            EventDetailMergeBehavior behavior) {
        EventDetail.Builder detailBuilder = EventDetail.newBuilder();
        detailBuilder.setName(name);
        detailBuilder.addValue(prefix + createRandomMaxString(detailLength));

        if (behavior != null) {
            detailBuilder.setMergeBehavior(behavior);
        }
        return detailBuilder.build();
    }

    private int getStringStepSize() throws ZepException {
        // Maximum bytes divided by 2 will give the ballpark length of the largest
        // string. Subdivide this length by 4 so we get a decent step length
        // and can control when we go over the maximum size.
        return (int) configDao.getConfig().getEventMaxSizeBytes() / 8;
    }

    /**
     * Test that normal events with large details do not get modified if they are
     * smaller than the event_max_size_bytes parameter.
     *
     * @throws ZepException
     */
    @Test
    public void testMaxEventSizeCreationNormal() throws ZepException {
        int stringStepSize = getStringStepSize();

        List<EventDetail> baseDetails = new ArrayList<EventDetail>();

        baseDetails.add(
                createDetailOfSize("foo_append", stringStepSize, "validDetails-", EventDetailMergeBehavior.APPEND));
        baseDetails.add(createDetailOfSize("bar_replace", stringStepSize, "validDetails-",
                EventDetailMergeBehavior.REPLACE));
        baseDetails.add(
                createDetailOfSize("baz_unique", stringStepSize, "validDetails-", EventDetailMergeBehavior.UNIQUE));

        Builder baseEventBuilder = Event.newBuilder(EventTestUtils.createSampleEvent()).clearDetails();
        baseEventBuilder.addAllDetails(baseDetails);

        Event validEvent = baseEventBuilder.build();
        EventSummary validSummary = createSummaryNew(validEvent);

        // Verify large, but not too large, details get created and indexed
        assertTrue(validSummary != null);

        // Verify that the details haven't been changed as they were saved
        assertEquals(validSummary.getOccurrence(0).getDetailsList(), baseDetails);
    }

    /**
     * Test that during creation events with details that are too large have their
     * non-Zenoss details truncated.
     *
     * @throws ZepException
     */
    @Test
    public void testMaxEventSizeCreationLarge() throws ZepException {
        int stringStepSize = getStringStepSize();

        List<EventDetail> baseDetails = new ArrayList<EventDetail>();

        baseDetails.add(createDetailOfSize("foo_append", stringStepSize * 50, "tooLarge-",
                EventDetailMergeBehavior.APPEND));
        baseDetails.add(createDetailOfSize("bar_replace", stringStepSize * 50, "tooLarge-",
                EventDetailMergeBehavior.REPLACE));
        baseDetails.add(createDetailOfSize("baz_unique", stringStepSize * 50, "tooLarge-",
                EventDetailMergeBehavior.UNIQUE));

        // Note: The event for this is a new sample event. This is to verify that
        // when creating a new event, if the details data is too large, the event
        // will have it's non-zenoss details truncated completely.
        final Builder invalidEventBuilder = Event.newBuilder(EventTestUtils.createSampleEvent()).clearDetails();
        invalidEventBuilder.addAllDetails(baseDetails);

        // Add a legitimate zenoss detail and make sure it's not removed.
        invalidEventBuilder.addDetails(
                EventDetail.newBuilder().setName(ZepConstants.DETAIL_DEVICE_PRODUCTION_STATE).addValue("1000"));

        EventSummary invalidSummary = createSummaryNew(invalidEventBuilder.build());

        for (EventDetail detail : invalidSummary.getOccurrence(0).getDetailsList()) {
            assertTrue("Detail should be a zenoss detail: " + detail.getName(),
                    detail.getName().startsWith("zenoss."));
        }

        final Map<String, List<String>> createdDetails = detailsToMap(
                invalidSummary.getOccurrence(0).getDetailsList());
        assertTrue("Created event should contain Zenoss details (production state).",
                createdDetails.keySet().contains(ZepConstants.DETAIL_DEVICE_PRODUCTION_STATE));

    }

    /**
     * Sets up an event for testing with detail size tests.
     *
     * @param stringStepSize The length of the string value for each detail.
     * @return Event The original event to use in tests.
     * @throws ZepException
     */
    private Event setupEventForDetails(int stringStepSize) throws ZepException {
        final List<EventDetail> baseDetails = new ArrayList<EventDetail>();
        baseDetails.add(
                createDetailOfSize("foo_append", stringStepSize, "baseDetail-", EventDetailMergeBehavior.APPEND));
        baseDetails.add(
                createDetailOfSize("bar_replace", stringStepSize, "baseDetail-", EventDetailMergeBehavior.REPLACE));
        baseDetails.add(
                createDetailOfSize("baz_unique", stringStepSize, "baseDetail-", EventDetailMergeBehavior.UNIQUE));

        final Builder baseEventBuilder = Event.newBuilder(EventTestUtils.createSampleEvent()).clearDetails();
        baseEventBuilder.addAllDetails(baseDetails);
        final Event originalEvent = baseEventBuilder.build();
        final EventSummary originalSummary = createSummaryNew(originalEvent);

        return originalEvent;
    }

    /**
     * Three cases to test for UPDATE scenario:
     *
     *  1.) new details + old details > max size && new details <= max size
     *      drop old details, add new details
     *
     *  2.) new details + old details > max size && new details > max size
     *      drop old details, drop non-zenoss new details
     *
     *  3.) new details + old details <= max size
     *      merge details like normal
     */

    /**
     * Verify that old details get dropped if new details are less than max
     * size and combined details are greater than max size.
     *
     * @throws org.zenoss.zep.ZepException
     */
    @Test
    public void testMaxEventSizeUpdateDropOld() throws ZepException {
        final int stringStepSize = getStringStepSize();
        final Event originalEvent = setupEventForDetails(stringStepSize);

        final List<EventDetail> additionalDetails = new ArrayList<EventDetail>();
        additionalDetails.add(createDetailOfSize("foo_append", stringStepSize, "additionalDetail-",
                EventDetailMergeBehavior.APPEND));
        additionalDetails.add(createDetailOfSize("bar_replace", stringStepSize, "additionalDetail-",
                EventDetailMergeBehavior.REPLACE));
        additionalDetails.add(createDetailOfSize("baz_unique", stringStepSize, "additionalDetail-",
                EventDetailMergeBehavior.UNIQUE));

        final Event.Builder newEventBuilder = Event.newBuilder(originalEvent);
        newEventBuilder.setCreatedTime(newEventBuilder.getCreatedTime() + 1);
        newEventBuilder.clearDetails();
        newEventBuilder.addAllDetails(additionalDetails);

        final Event newEvent = newEventBuilder.build();
        assertTrue("New details don't contain additional details at all?",
                detailsToMap(newEvent.getDetailsList()).containsKey("foo_append"));

        final EventSummary newSummary = createSummaryNew(newEventBuilder.build());

        assertEquals("Old details should have been dropped.", additionalDetails,
                newSummary.getOccurrence(0).getDetailsList());
    }

    /**
     * Verify that new details that are too large have non-Zenoss details dropped.
     *
     * If combined new and old details are too large and the new details alone
     * are too large, drop the old details as well as the non-Zenoss new details.
     *
     * @throws org.zenoss.zep.ZepException
     */
    @Test
    public void testMaxEventSizeUpdateDropNew() throws ZepException {
        final int stringStepSize = getStringStepSize();
        final Event originalEvent = setupEventForDetails(stringStepSize);

        final List<EventDetail> largeDetails = new ArrayList<EventDetail>();
        largeDetails.add(createDetailOfSize("foo_append", stringStepSize * 30, "largeDetail-",
                EventDetailMergeBehavior.APPEND));
        largeDetails.add(createDetailOfSize("bar_replace", stringStepSize * 30, "largeDetail-",
                EventDetailMergeBehavior.REPLACE));
        largeDetails.add(createDetailOfSize("baz_unique", stringStepSize * 30, "largeDetail-",
                EventDetailMergeBehavior.UNIQUE));

        final Event.Builder largeEventBuilder = Event.newBuilder(originalEvent);
        largeEventBuilder.setCreatedTime(largeEventBuilder.getCreatedTime() + 2);
        largeEventBuilder.clearDetails();
        largeEventBuilder.addAllDetails(largeDetails);

        // Add a legitimate Zenoss detail and verify that it's not removed.
        largeEventBuilder.addDetails(
                EventDetail.newBuilder().setName(ZepConstants.DETAIL_DEVICE_PRODUCTION_STATE).addValue("1000"));

        final EventSummary largeSummary = createSummaryNew(largeEventBuilder.build());

        // Only Zenoss details should remain...
        for (EventDetail detail : largeSummary.getOccurrence(0).getDetailsList()) {
            assertTrue("Detail should be a zenoss detail: " + detail.getName(),
                    detail.getName().startsWith("zenoss."));
        }

        final Map<String, List<String>> createdDetails = detailsToMap(
                largeSummary.getOccurrence(0).getDetailsList());
        // ...and verify that our SPECIFIC detail was not somehow removed.
        assertTrue("Created event should contain Zenoss details (production state).",
                createdDetails.keySet().contains(ZepConstants.DETAIL_DEVICE_PRODUCTION_STATE));

    }

    /**
     * Verify that new details and old details get merged correctly if less than
     * max size. This is normal behavior.
     *
     * @throws org.zenoss.zep.ZepException
     */
    @Test
    public void testMaxEventSizeUpdateMerge() throws ZepException {
        final int stringStepSize = getStringStepSize();
        final Event originalEvent = setupEventForDetails(stringStepSize);

        // This step size is much smaller because I want to add each detail and
        // make sure it doesn't go over the max size limit.
        final int additionalDetailStepSize = 8;

        final List<EventDetail> normalDetails = new ArrayList<EventDetail>();
        normalDetails.add(createDetailOfSize("foo_append", additionalDetailStepSize, "normalDetail-",
                EventDetailMergeBehavior.APPEND));
        normalDetails.add(createDetailOfSize("bar_replace", additionalDetailStepSize, "normalDetail-",
                EventDetailMergeBehavior.REPLACE));
        normalDetails.add(createDetailOfSize("baz_unique", additionalDetailStepSize, "normalDetail-",
                EventDetailMergeBehavior.UNIQUE));

        final Event.Builder normalEventBuilder = Event.newBuilder(originalEvent);
        normalEventBuilder.setCreatedTime(normalEventBuilder.getCreatedTime() + 3);
        normalEventBuilder.clearDetails();
        normalEventBuilder.addAllDetails(normalDetails);
        final EventSummary normalSummary = createSummaryNew(normalEventBuilder.build());

        Map<String, List<String>> createdDetails = detailsToMap(normalSummary.getOccurrence(0).getDetailsList());
        Map<String, List<String>> mapNormalDetails = detailsToMap(normalDetails);
        Map<String, List<String>> mapOriginalDetails = detailsToMap(originalEvent.getDetailsList());

        // assert that normalDetails and originalDetails values for foo_append are present
        assertTrue("Created details should contain normal details 'foo_append' value.",
                createdDetails.get("foo_append").contains(mapNormalDetails.get("foo_append").get(0)));
        assertTrue("Created details should contain original details 'foo_append' value.",
                createdDetails.get("foo_append").contains(mapOriginalDetails.get("foo_append").get(0)));

        // assert that only normalDetails value for bar_replace is present
        assertTrue("Created details should contain normal details 'bar_replace' value.",
                createdDetails.get("bar_replace").contains(mapNormalDetails.get("bar_replace").get(0)));
        assertFalse("Created details should NOT contain original details 'bar_replace' value.",
                createdDetails.get("bar_replace").contains(mapOriginalDetails.get("bar_replace").get(0)));

        // assert that normalDetails and originalDetails values for baz_unique are present.
        assertTrue("Created details should contain normal details 'baz_unique' value.",
                createdDetails.get("baz_unique").contains(mapNormalDetails.get("baz_unique").get(0)));
        assertTrue("Created details should contain original details 'baz_unique' value.",
                createdDetails.get("baz_unique").contains(mapOriginalDetails.get("baz_unique").get(0)));
    }

    @Test
    public void testMaxEventSizeUpdateOutOfOrder() throws ZepException {
        final int stringStepSize = getStringStepSize();
        final Event newestEvent = setupEventForDetails(stringStepSize);

        final List<EventDetail> oldDetails = new ArrayList<EventDetail>();
        oldDetails.add(
                createDetailOfSize("foo_append", stringStepSize, "oldDetail-", EventDetailMergeBehavior.APPEND));
        oldDetails.add(
                createDetailOfSize("bar_replace", stringStepSize, "oldDetail-", EventDetailMergeBehavior.REPLACE));
        oldDetails.add(
                createDetailOfSize("baz_unique", stringStepSize, "oldDetail-", EventDetailMergeBehavior.UNIQUE));

        final Event.Builder oldEventBuilder = Event.newBuilder(newestEvent);
        oldEventBuilder.setCreatedTime(oldEventBuilder.getCreatedTime() - 5000);
        oldEventBuilder.clearDetails();
        oldEventBuilder.addAllDetails(oldDetails);

        final EventSummary oldSummary = createSummaryNew(oldEventBuilder.build());

        assertEquals("Oldest details should have been dropped.", newestEvent.getDetailsList(),
                oldSummary.getOccurrence(0).getDetailsList());
    }

    @Test
    public void testAgeEligibleEventCount() throws ZepException {
        long initial = eventSummaryDao.getAgeEligibleEventCount(0L, TimeUnit.SECONDS,
                EventSeverity.SEVERITY_CRITICAL, true);
        createSummaryNew(EventTestUtils.createSampleEvent());
        long actual = eventSummaryDao.getAgeEligibleEventCount(0L, TimeUnit.SECONDS,
                EventSeverity.SEVERITY_CRITICAL, true);
        assertEquals(initial + 1L, actual);

        // Test condition where aging is disabled
        assertEquals(0, eventSummaryDao.getAgeEligibleEventCount(0L, TimeUnit.SECONDS, EventSeverity.SEVERITY_CLEAR,
                false));
    }

    @Test
    public void testArchiveEligibleEventCount() throws ZepException {
        long initial = eventSummaryDao.getArchiveEligibleEventCount(0L, TimeUnit.SECONDS);
        EventSummary summary = createSummaryNew(EventTestUtils.createSampleEvent());
        String userUuid = UUID.randomUUID().toString();
        String userName = "user" + random.nextInt(500);
        eventSummaryDao.close(Collections.singletonList(summary.getUuid()), userUuid, userName);
        long actual = eventSummaryDao.getArchiveEligibleEventCount(0L, TimeUnit.SECONDS);
        assertEquals(initial + 1L, actual);
    }

    @Test
    public void testNoExceptionWhenNoClearHashes() throws ZepException {
        Event event = Event.newBuilder(EventTestUtils.createSampleEvent()).setSeverity(EventSeverity.SEVERITY_CLEAR)
                .build();
        eventSummaryDao.create(event, new EventPreCreateContext() {
            @Override
            public Set<String> getClearClasses() {
                return Collections.emptySet();
            }

            @Override
            public void setClearClasses(Set<String> clearClasses) {
            }

            @Override
            public ClearFingerprintGenerator getClearFingerprintGenerator() {
                return new ClearFingerprintGenerator() {
                    @Override
                    public String generateClearFingerprint(Event event) {
                        return null;
                    }

                    @Override
                    public List<String> generateClearFingerprints(Event event, Set<String> clearClasses) {
                        return Collections.emptyList();
                    }
                };
            }

            @Override
            public void setClearFingerprintGenerator(ClearFingerprintGenerator clearFingerprintGenerator) {
            }
        });
    }

    @Test
    public void testIncomingEventCount() throws ZepException {
        Event event = Event.newBuilder(EventTestUtils.createSampleEvent()).setCount(500).build();
        String uuid = eventSummaryDao.create(event, new EventPreCreateContextImpl());
        String uuid2 = eventSummaryDao.create(Event.newBuilder(event).setCount(1000).build(),
                new EventPreCreateContextImpl());
        assertEquals(uuid, uuid2);

        EventSummary eventSummary = eventSummaryDao.findByUuid(uuid);
        assertEquals(1500, eventSummary.getCount());
    }

    @Test
    public void testIncomingFirstTime() throws ZepException {
        long oneHourAgo = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(60);
        Event event = Event.newBuilder(EventTestUtils.createSampleEvent()).setFirstSeenTime(oneHourAgo).setCount(5)
                .build();
        String uuid = eventSummaryDao.create(event, new EventPreCreateContextImpl());
        long twoHoursAgo = oneHourAgo - TimeUnit.MINUTES.toMillis(60);
        Event event2 = Event.newBuilder(event).setFirstSeenTime(twoHoursAgo).setCount(5).build();
        String uuid2 = eventSummaryDao.create(event2, new EventPreCreateContextImpl());

        assertEquals(uuid, uuid2);
        EventSummary eventSummary = eventSummaryDao.findByUuid(uuid);
        assertEquals(10, eventSummary.getCount());
        assertEquals(twoHoursAgo, eventSummary.getFirstSeenTime());
    }

}