org.jasig.schedassist.model.VisibleScheduleBuilderTest.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.schedassist.model.VisibleScheduleBuilderTest.java

Source

/**
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig 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 org.jasig.schedassist.model;

import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeSet;

import junit.framework.Assert;
import net.fortuna.ical4j.data.CalendarBuilder;
import net.fortuna.ical4j.data.ParserException;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.Component;
import net.fortuna.ical4j.model.ComponentList;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.ParameterList;
import net.fortuna.ical4j.model.Recur;
import net.fortuna.ical4j.model.TimeZoneRegistry;
import net.fortuna.ical4j.model.TimeZoneRegistryFactory;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.parameter.Cn;
import net.fortuna.ical4j.model.parameter.CuType;
import net.fortuna.ical4j.model.parameter.PartStat;
import net.fortuna.ical4j.model.parameter.Rsvp;
import net.fortuna.ical4j.model.parameter.XParameter;
import net.fortuna.ical4j.model.property.Attendee;
import net.fortuna.ical4j.model.property.RRule;

import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.schedassist.NullAffiliationSourceImpl;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;

/**
 * Test bench for {@link VisibleScheduleBuilder}.
 * 
 * @author Nicholas Blair, nblair@doit.wisc.edu
 * @version $Id: VisibleScheduleBuilderTest.java 2934 2010-12-09 16:21:58Z npblair $
 */
public class VisibleScheduleBuilderTest {

    private Log LOG = LogFactory.getLog(this.getClass());

    private DefaultEventUtilsImpl eventUtils = new DefaultEventUtilsImpl(new NullAffiliationSourceImpl());
    private VisibleScheduleBuilder builder = new VisibleScheduleBuilder(eventUtils);

    static {
        // Many of the values in the assertions expect America/Chicago
        TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
    }

    private final net.fortuna.ical4j.model.TimeZone americaChicago;

    /**
     * 
     * @throws ParseException 
     */
    public VisibleScheduleBuilderTest() throws ParseException {
        TimeZoneRegistry registry = TimeZoneRegistryFactory.getInstance().createRegistry();
        americaChicago = registry.getTimeZone("America/Chicago");
    }

    /**
     * Assert empty calendar and empty Advising Schedule throw no errors
     * @throws Exception
     */
    @Test
    public void testEmptyCalendar() throws Exception {
        Calendar calendar = new Calendar(new ComponentList());
        AvailableSchedule schedule = new AvailableSchedule(new TreeSet<AvailableBlock>());

        MockScheduleOwner owner = new MockScheduleOwner(new MockCalendarAccount(), 1);

        VisibleScheduleBuilder builder = new VisibleScheduleBuilder();
        VisibleSchedule newCalendar = builder.calculateVisibleSchedule(new Date(), DateUtils.addDays(new Date(), 7),
                calendar, schedule, owner);
        Assert.assertEquals(0, newCalendar.getSize());
    }

    /**
     * Create visible schedule for Aug 4 2008.
     * Create a calendar with an appointment on Aug 5 2008.
     * Send the two into VisibleScheduleBuilder, and assert no conflicts found.
     * 
     * @throws Exception
     */
    @Test
    public void testCalendarNoConflict() throws Exception {
        MockCalendarAccount person = new MockCalendarAccount();
        person.setEmailAddress("someowner@wisc.edu");
        person.setDisplayName("Some Owner");
        MockScheduleOwner owner = new MockScheduleOwner(person, 1);

        // Aug 4 2008 9:30 AM to 3:30 PM
        AvailableBlock block = AvailableBlockBuilder.createBlock(makeDateTime("20080804-0930"),
                makeDateTime("20080804-1530"));
        Set<AvailableBlock> blocks = AvailableBlockBuilder.expand(block, 30);
        AvailableSchedule schedule = new AvailableSchedule(blocks);

        // Aug 5 2008 10:30 AM to 11:30 AM 
        DateTime eventStart = new net.fortuna.ical4j.model.DateTime(makeDateTime("20080805-1030"));
        eventStart.setTimeZone(americaChicago);
        DateTime eventEnd = new DateTime(makeDateTime("20080805-1130"));
        eventEnd.setTimeZone(americaChicago);
        VEvent someEvent = new VEvent(eventStart, eventEnd, "some event");
        ParameterList parameterList = new ParameterList();
        parameterList.add(PartStat.ACCEPTED);
        parameterList.add(CuType.INDIVIDUAL);
        parameterList.add(Rsvp.FALSE);
        parameterList.add(new Cn(person.getDisplayName()));
        Attendee attendee = new Attendee(parameterList, "mailto:" + person.getEmailAddress());
        someEvent.getProperties().add(attendee);
        ComponentList components = new ComponentList();
        components.add(someEvent);

        // week start is Sun Aug 3
        VisibleSchedule visibleSchedule = builder.calculateVisibleSchedule(makeDateTime("20080803-0000"),
                makeDateTime("20080810-0000"), new Calendar(components), schedule, owner);

        ComponentList componentList = visibleSchedule.getCalendar().getComponents(Component.VEVENT);
        // there should be 12 events in the result
        Assert.assertEquals(12, componentList.size());
        Assert.assertEquals(12, visibleSchedule.getFreeCount());
        Assert.assertEquals(0, visibleSchedule.getBusyCount());
        Assert.assertEquals(0, visibleSchedule.getAttendingCount());

    }

    /**
     * 
     * @throws Exception
     */
    @Test
    public void testSimpleConflict() throws Exception {
        MockCalendarAccount person = new MockCalendarAccount();
        person.setEmailAddress("someowner@wisc.edu");
        person.setDisplayName("Some Owner");
        MockScheduleOwner owner = new MockScheduleOwner(person, 1);

        AvailableBlock block = AvailableBlockBuilder.createBlock(makeDateTime("20091104-0900"),
                makeDateTime("20091104-1000"));
        Set<AvailableBlock> blocks = AvailableBlockBuilder.expand(block, 30);
        Assert.assertEquals(2, blocks.size());
        AvailableSchedule schedule = new AvailableSchedule(blocks);

        DateTime eventStart = new net.fortuna.ical4j.model.DateTime(makeDateTime("20091104-0900"));
        eventStart.setTimeZone(americaChicago);
        DateTime eventEnd = new net.fortuna.ical4j.model.DateTime(makeDateTime("20091104-0930"));
        eventEnd.setTimeZone(americaChicago);
        VEvent someEvent = new VEvent(eventStart, eventEnd, "conflict event");

        ParameterList parameterList = new ParameterList();
        parameterList.add(PartStat.ACCEPTED);
        parameterList.add(CuType.INDIVIDUAL);
        parameterList.add(Rsvp.FALSE);
        parameterList.add(new Cn(person.getDisplayName()));
        Attendee attendee = new Attendee(parameterList, "mailto:" + person.getEmailAddress());
        someEvent.getProperties().add(attendee);

        ComponentList components = new ComponentList();
        components.add(someEvent);

        VisibleSchedule visibleSchedule = builder.calculateVisibleSchedule(makeDateTime("20091104-0830"),
                makeDateTime("20091104-1030"), new Calendar(components), schedule, owner);

        ComponentList componentList = visibleSchedule.getCalendar().getComponents(Component.VEVENT);
        // free from 9:30 to 10:30, busy from 10:30 to 11:30, free from 11:30 to 3:30 PM

        Assert.assertEquals(2, componentList.size());

        Assert.assertEquals(1, visibleSchedule.getFreeCount());
        Assert.assertEquals(1, visibleSchedule.getBusyCount());
        Assert.assertEquals(0, visibleSchedule.getAttendingCount());
    }

    /**
     * 
     * @throws Exception
     */
    @Test
    public void testRecurringConflict() throws Exception {
        MockCalendarAccount person = new MockCalendarAccount();
        person.setEmailAddress("someowner@wisc.edu");
        person.setDisplayName("Some Owner");
        MockScheduleOwner owner = new MockScheduleOwner(person, 1);

        SortedSet<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("9:00 AM", "10:00 AM", "W",
                makeDate("20120201"), makeDate("20120307"));
        blocks = AvailableBlockBuilder.expand(blocks, 30);
        AvailableSchedule schedule = new AvailableSchedule(blocks);

        DateTime eventStart = new net.fortuna.ical4j.model.DateTime(makeDateTime("20120201-0900"));
        eventStart.setTimeZone(americaChicago);
        DateTime eventEnd = new net.fortuna.ical4j.model.DateTime(makeDateTime("20120201-0930"));
        eventEnd.setTimeZone(americaChicago);
        VEvent someEvent = new VEvent(eventStart, eventEnd, "recurring conflict event");
        // recur Wed 2/01 through Wed 2/29, leaving 3/07 out of recurrence
        Recur recur = new Recur(Recur.WEEKLY, new net.fortuna.ical4j.model.Date(makeDate("20120301")));
        RRule rrule = new RRule(recur);
        someEvent.getProperties().add(rrule);

        ParameterList parameterList = new ParameterList();
        parameterList.add(PartStat.ACCEPTED);
        parameterList.add(CuType.INDIVIDUAL);
        parameterList.add(Rsvp.FALSE);
        parameterList.add(new Cn(person.getDisplayName()));
        Attendee attendee = new Attendee(parameterList, "mailto:" + person.getEmailAddress());
        someEvent.getProperties().add(attendee);

        ComponentList components = new ComponentList();
        components.add(someEvent);

        VisibleSchedule visibleSchedule = builder.calculateVisibleSchedule(makeDate("20120201"),
                makeDate("20120308"), new Calendar(components), schedule, owner);

        ComponentList componentList = visibleSchedule.getCalendar().getComponents(Component.VEVENT);
        // free from 9:30 to 10:30, busy from 10:30 to 11:30, free from 11:30 to 3:30 PM

        Assert.assertEquals(12, componentList.size());

        Assert.assertEquals(7, visibleSchedule.getFreeCount());
        Assert.assertEquals(5, visibleSchedule.getBusyCount());
        Assert.assertEquals(0, visibleSchedule.getAttendingCount());
    }

    /**
     * Create an advising schedule for Aug 4 2008.
     * Create a calendar with an appointment on Aug 4 2008.
     * Send the two into FreeBusyBuilder, and assert correct conflicts found.
     * 
     * @throws Exception
     */
    @Test
    public void testOneAppointmentConflictsTwoBlocks() throws Exception {
        MockCalendarAccount person = new MockCalendarAccount();
        person.setEmailAddress("someowner@wisc.edu");
        person.setDisplayName("Some Owner");
        MockScheduleOwner owner = new MockScheduleOwner(person, 1);

        AvailableBlock block = AvailableBlockBuilder.createBlock(makeDateTime("20091109-0930"),
                makeDateTime("20091109-1230"));
        Set<AvailableBlock> blocks = AvailableBlockBuilder.expand(block, 30);
        AvailableSchedule schedule = new AvailableSchedule(blocks);

        DateTime eventStart = new net.fortuna.ical4j.model.DateTime(makeDateTime("20091109-1030"));
        eventStart.setTimeZone(americaChicago);
        DateTime eventEnd = new net.fortuna.ical4j.model.DateTime(makeDateTime("20091109-1130"));
        eventEnd.setTimeZone(americaChicago);
        VEvent someEvent = new VEvent(eventStart, eventEnd, "some event");
        ParameterList parameterList = new ParameterList();
        parameterList.add(PartStat.ACCEPTED);
        parameterList.add(CuType.INDIVIDUAL);
        parameterList.add(Rsvp.FALSE);
        parameterList.add(new Cn(person.getDisplayName()));
        Attendee attendee = new Attendee(parameterList, "mailto:" + person.getEmailAddress());
        someEvent.getProperties().add(attendee);
        ComponentList components = new ComponentList();
        components.add(someEvent);

        VisibleSchedule calendar = builder.calculateVisibleSchedule(makeDateTime("20091109-0000"),
                makeDateTime("20091110-0000"), new Calendar(components), schedule, owner);
        Assert.assertEquals(6, calendar.getSize());

        Assert.assertEquals(4, calendar.getFreeCount());

        Assert.assertEquals(2, calendar.getBusyCount());
        for (AvailableBlock busy : calendar.getBusyList()) {
            if (!busy.getStartTime().equals(makeDateTime("20091109-1030"))
                    && !busy.getStartTime().equals(makeDateTime("20091109-1100"))) {
                Assert.fail("busyEvents contains unexpected block " + busy);
            }
        }
    }

    /**
     * Same test as testCalendarConflict1, however all dates are incremented by 1.
     * Week start (argument to {@link VisibleScheduleBuilder} constructor) is set to Mon Aug 4.
     * 
     * @throws Exception
     */
    @Test
    public void testCalendarConflictShiftWeekStart() throws Exception {
        MockCalendarAccount person = new MockCalendarAccount();
        person.setEmailAddress("someowner@wisc.edu");
        person.setDisplayName("Some Owner");
        MockScheduleOwner owner = new MockScheduleOwner(person, 1);

        // Aug 5 2008 9:30 AM to 3:30 PM
        AvailableBlock block = AvailableBlockBuilder.createBlock(makeDateTime("20080805-0930"),
                makeDateTime("20080805-1530"));
        Set<AvailableBlock> blocks = AvailableBlockBuilder.expand(block, 30);
        AvailableSchedule schedule = new AvailableSchedule(blocks);

        // Aug 5 2008 10:30 AM to 11:30 AM 
        DateTime eventStart = new net.fortuna.ical4j.model.DateTime(makeDateTime("20080805-1030"));
        eventStart.setTimeZone(americaChicago);
        DateTime eventEnd = new net.fortuna.ical4j.model.DateTime(makeDateTime("20080805-1130"));
        eventEnd.setTimeZone(americaChicago);
        VEvent someEvent = new VEvent(eventStart, eventEnd, "some event");

        ParameterList parameterList = new ParameterList();
        parameterList.add(PartStat.ACCEPTED);
        parameterList.add(CuType.INDIVIDUAL);
        parameterList.add(Rsvp.FALSE);
        parameterList.add(new Cn(person.getDisplayName()));
        Attendee attendee = new Attendee(parameterList, "mailto:" + person.getEmailAddress());
        someEvent.getProperties().add(attendee);
        ComponentList components = new ComponentList();
        components.add(someEvent);

        // week start is Mon Aug 4!
        VisibleSchedule calendar = builder.calculateVisibleSchedule(makeDateTime("20080804-0000"),
                makeDateTime("20080810-0000"), new Calendar(components), schedule, owner);
        ComponentList componentList = calendar.getCalendar().getComponents(Component.VEVENT);
        // free from 9:30 to 10:30, busy from 10:30 to 11:30, free from 11:30 to 3:30 PM

        Assert.assertEquals(12, componentList.size());
        Assert.assertEquals(10, calendar.getFreeCount());
        Assert.assertEquals(2, calendar.getBusyCount());
        Assert.assertEquals(0, calendar.getAttendingCount());
        // 9-9:30 AM, 9:30-10 AM, 10-10:30 AM should be "free"
        // 10:30-11 AM and 11-11:30AM should be "busy"
        // 11:30 AM on should be free
        for (AvailableBlock busy : calendar.getBusyList()) {
            if (!busy.getStartTime().equals(makeDateTime("20080805-1030"))
                    && !busy.getStartTime().equals(makeDateTime("20080805-1100"))) {
                Assert.fail("busyEvents contains startdate of " + busy.getStartTime());
            }
        }
    }

    /**
     * 
     * @throws Exception
     */
    @Test
    public void testFindAttending() throws Exception {
        // construct visitor
        MockCalendarAccount person1 = new MockCalendarAccount();
        person1.setEmailAddress("somevisitor@wisc.edu");
        person1.setDisplayName("Some Visitor");
        MockScheduleVisitor visitor = new MockScheduleVisitor(person1);

        // construct owner
        MockCalendarAccount person2 = new MockCalendarAccount();
        person2.setEmailAddress("someowner@wisc.edu");
        person2.setDisplayName("Some Owner");
        MockScheduleOwner owner = new MockScheduleOwner(person2, 1);

        // Aug 4 2008 9:30 AM to 3:30 PM
        AvailableBlock block = AvailableBlockBuilder.createBlock(makeDateTime("20080804-0930"),
                makeDateTime("20080804-1530"));
        Set<AvailableBlock> blocks = AvailableBlockBuilder.expand(block, 30);
        AvailableSchedule schedule = new AvailableSchedule(blocks);

        // some other non-available appointment Aug 4 2008 10:30 AM to 11:30 AM 
        DateTime eventStart = new net.fortuna.ical4j.model.DateTime(makeDateTime("20080804-1030"));
        eventStart.setTimeZone(americaChicago);
        DateTime eventEnd = new net.fortuna.ical4j.model.DateTime(makeDateTime("20080804-1130"));
        eventEnd.setTimeZone(americaChicago);
        VEvent someEvent = new VEvent(eventStart, eventEnd, "some event");

        ParameterList parameterList = new ParameterList();
        parameterList.add(PartStat.ACCEPTED);
        parameterList.add(CuType.INDIVIDUAL);
        parameterList.add(Rsvp.FALSE);
        parameterList.add(new Cn(person2.getDisplayName()));
        Attendee attendee = new Attendee(parameterList, "mailto:" + person2.getEmailAddress());
        someEvent.getProperties().add(attendee);
        ComponentList components = new ComponentList();
        components.add(someEvent);

        // make available appointment with visitor from 1:00 PM to 1:30 PM
        VEvent availableAppointment = this.eventUtils.constructAvailableAppointment(
                AvailableBlockBuilder.createBlock(makeDateTime("20080804-1300"), makeDateTime("20080804-1330")),
                owner, visitor, "advising appt");
        components.add(availableAppointment);

        // week start is Sun Aug 3
        VisibleSchedule calendar = this.builder.calculateVisibleSchedule(makeDateTime("20080803-0000"),
                makeDateTime("20080810-0000"), new Calendar(components), schedule, owner, visitor);

        ComponentList componentList = calendar.getCalendar().getComponents(Component.VEVENT);
        // free from 9:30 to 10:30, busy from 10:30 to 11:30, free from 11:30 to 1:00 PM, student appt from 1-1:30 PM, free to 3:30 PM

        Assert.assertEquals(12, componentList.size());
        // 9-9:30 AM, 9:30-10 AM, 10-10:30 AM should be "free"
        // 10:30-11 AM and 11-11:30AM should be "busy"
        // 11:30 AM on should be free

        Assert.assertEquals(9, calendar.getFreeCount());

        Assert.assertEquals(2, calendar.getBusyCount());
        for (AvailableBlock busy : calendar.getBusyList()) {
            if (!busy.getStartTime().equals(makeDateTime("20080804-1030"))
                    && !busy.getStartTime().equals(makeDateTime("20080804-1100"))) {
                Assert.fail("busyEvents contains startdate of " + busy.getStartTime());
            }
        }

        Assert.assertEquals(1, calendar.getAttendingCount());
        AvailableBlock attendingEvent = calendar.getAttendingList().get(0);
        Assert.assertEquals(attendingEvent.getStartTime(), makeDateTime("20080804-1300"));
        Assert.assertEquals(attendingEvent.getEndTime(), makeDateTime("20080804-1330"));
    }

    /**
     * 
     * @throws Exception
     */
    @Test
    public void testAlternateMeetingDurations() throws Exception {
        MockCalendarAccount person = new MockCalendarAccount();
        person.setEmailAddress("someowner@wisc.edu");
        person.setDisplayName("Some Owner");
        MockScheduleOwner owner = new MockScheduleOwner(person, 1);

        // 4 week available schedule
        Set<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("9:00 AM", "12:00 PM", "MRF",
                makeDateTime("20091102-0000"), makeDateTime("20091127-0000"), 1, null);

        TreeSet<AvailableBlock> expanded = new TreeSet<AvailableBlock>(AvailableBlockBuilder.expand(blocks, 30));
        //Assert.assertEquals(72, expanded.size());
        LOG.info("expanded set first: " + expanded.first() + ", last: " + expanded.last());
        AvailableSchedule schedule = new AvailableSchedule(expanded);

        // event conflict is thursday of 2nd week
        DateTime eventStart = new net.fortuna.ical4j.model.DateTime(makeDateTime("20091112-0930"));
        eventStart.setTimeZone(americaChicago);
        DateTime eventEnd = new net.fortuna.ical4j.model.DateTime(makeDateTime("20091112-1030"));
        eventEnd.setTimeZone(americaChicago);
        VEvent someEvent = new VEvent(eventStart, eventEnd, "some event");

        ParameterList parameterList = new ParameterList();
        parameterList.add(PartStat.ACCEPTED);
        parameterList.add(CuType.INDIVIDUAL);
        parameterList.add(Rsvp.FALSE);
        parameterList.add(new Cn(person.getDisplayName()));
        Attendee attendee = new Attendee(parameterList, "mailto:" + person.getEmailAddress());
        someEvent.getProperties().add(attendee);
        ComponentList components = new ComponentList();
        components.add(someEvent);

        VisibleSchedule visible = this.builder.calculateVisibleSchedule(makeDateTime("20091102-0000"),
                makeDateTime("20091130-0000"), new Calendar(components), schedule, owner);

        Assert.assertEquals(2, visible.getBusyCount());
        Assert.assertEquals(70, visible.getFreeCount());
        Assert.assertEquals(72, visible.getSize());

        owner.setPreference(Preferences.DURATIONS, MeetingDurations.FIFTEEN.getKey());
        TreeSet<AvailableBlock> expanded2 = new TreeSet<AvailableBlock>(AvailableBlockBuilder.expand(blocks, 15));
        VisibleSchedule visible2 = this.builder.calculateVisibleSchedule(makeDateTime("20091102-0000"),
                makeDateTime("20091130-0000"), new Calendar(components), new AvailableSchedule(expanded2), owner);
        Assert.assertEquals(4, visible2.getBusyCount());
        Assert.assertEquals(140, visible2.getFreeCount());
        Assert.assertEquals(144, visible2.getSize());

        owner.setPreference(Preferences.DURATIONS, MeetingDurations.FORTYFIVE.getKey());
        TreeSet<AvailableBlock> expanded3 = new TreeSet<AvailableBlock>(AvailableBlockBuilder.expand(blocks, 45));
        VisibleSchedule visible3 = this.builder.calculateVisibleSchedule(makeDateTime("20091102-0000"),
                makeDateTime("20091130-0000"), new Calendar(components), new AvailableSchedule(expanded3), owner);

        Assert.assertEquals(2, visible3.getBusyCount());
        Assert.assertEquals(46, visible3.getFreeCount());
        Assert.assertEquals(48, visible3.getSize());

    }

    /**
     * 
     * @throws Exception
     */
    @Test
    public void testConflictEventShorterThanPreferredDuration() throws Exception {
        MockCalendarAccount person = new MockCalendarAccount();
        person.setEmailAddress("someowner@wisc.edu");
        person.setDisplayName("Some Owner");
        MockScheduleOwner owner = new MockScheduleOwner(person, 1);

        Set<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("9:00 AM", "12:00 PM", "TW",
                makeDateTime("20091103-0000"), makeDateTime("20091105-0000"), 1, null);
        Assert.assertEquals(2, blocks.size());
        AvailableSchedule schedule = new AvailableSchedule(blocks);

        DateTime eventStart = new net.fortuna.ical4j.model.DateTime(makeDateTime("20091103-1000"));
        eventStart.setTimeZone(americaChicago);
        DateTime eventEnd = new net.fortuna.ical4j.model.DateTime(makeDateTime("20091103-1030"));
        eventEnd.setTimeZone(americaChicago);
        VEvent someEvent = new VEvent(eventStart, eventEnd, "some conflicting appointment");

        ParameterList parameterList = new ParameterList();
        parameterList.add(PartStat.ACCEPTED);
        parameterList.add(CuType.INDIVIDUAL);
        parameterList.add(Rsvp.FALSE);
        parameterList.add(new Cn(person.getDisplayName()));
        Attendee attendee = new Attendee(parameterList, "mailto:" + person.getEmailAddress());
        someEvent.getProperties().add(attendee);
        ComponentList components = new ComponentList();
        components.add(someEvent);

        owner.setPreference(Preferences.DURATIONS, MeetingDurations.FORTYFIVE.getKey());
        VisibleSchedule visible = this.builder.calculateVisibleSchedule(makeDateTime("20091102-0000"),
                makeDateTime("20091106-1600"), new Calendar(components), schedule, owner);

        Assert.assertEquals(8, visible.getSize());
        Assert.assertEquals(1, visible.getBusyCount());
        Assert.assertEquals(7, visible.getFreeCount());

        AvailableBlock busyBlock = visible.getBusyList().get(0);
        Assert.assertEquals(makeDateTime("20091103-0945"), busyBlock.getStartTime());
        Assert.assertEquals(makeDateTime("20091103-1030"), busyBlock.getEndTime());

    }

    /**
     * Verify that start and end times of blocks stay consistent for calculateVisibleSchedule.
     * 
     * @throws Exception
     */
    @Test
    public void testOffsetStart() throws Exception {
        Set<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("9:00 AM", "1:00 PM", "MWF",
                makeDateTime("20090720-0000"), makeDateTime("20090725-0000"), 1, null);

        TreeSet<AvailableBlock> expanded = new TreeSet<AvailableBlock>(AvailableBlockBuilder.expand(blocks, 30));
        Assert.assertEquals(makeDateTime("20090720-0900"), expanded.first().getStartTime());
        Assert.assertEquals(makeDateTime("20090720-0930"), expanded.first().getEndTime());
        Assert.assertEquals(makeDateTime("20090724-1230"), expanded.last().getStartTime());
        Assert.assertEquals(makeDateTime("20090724-1300"), expanded.last().getEndTime());
        AvailableSchedule schedule = new AvailableSchedule(expanded);

        Calendar emptyCalendar = new Calendar();

        MockScheduleOwner owner = new MockScheduleOwner(new MockCalendarAccount(), 1);
        owner.setPreference(Preferences.DURATIONS, MeetingDurations.THIRTY.getKey());
        VisibleSchedule control = this.builder.calculateVisibleSchedule(makeDateTime("20090719-0000"),
                makeDateTime("20090726-0000"), emptyCalendar, schedule, owner);
        Assert.assertEquals(24, control.getFreeCount());
        // verify that all the free blocks start on the half hour
        SortedMap<AvailableBlock, AvailableStatus> blockMap = control.getBlockMap();
        for (Entry<AvailableBlock, AvailableStatus> entry : blockMap.entrySet()) {
            if (AvailableStatus.FREE.equals(entry.getValue())) {
                AvailableBlock freeBlock = entry.getKey();
                java.util.Calendar startTimeCal = java.util.Calendar.getInstance();
                startTimeCal.setTime(freeBlock.getStartTime());
                Assert.assertTrue(startTimeCal.get(java.util.Calendar.MINUTE) % 30 == 0);
                java.util.Calendar endTimeCal = java.util.Calendar.getInstance();
                endTimeCal.setTime(freeBlock.getEndTime());
                Assert.assertTrue(endTimeCal.get(java.util.Calendar.MINUTE) % 30 == 0);
            }
        }

        // now start the offset in between a block
        VisibleSchedule startsInBlock = this.builder.calculateVisibleSchedule(makeDateTime("20090720-1005"),
                makeDateTime("20090727-1005"), emptyCalendar, schedule, owner);
        Assert.assertEquals(21, startsInBlock.getFreeCount());
        // verify that all the free blocks start on the half hour
        SortedMap<AvailableBlock, AvailableStatus> blockMap2 = startsInBlock.getBlockMap();
        for (Entry<AvailableBlock, AvailableStatus> entry : blockMap2.entrySet()) {
            if (AvailableStatus.FREE.equals(entry.getValue())) {
                AvailableBlock freeBlock = entry.getKey();
                java.util.Calendar startTimeCal = java.util.Calendar.getInstance();
                startTimeCal.setTime(freeBlock.getStartTime());
                Assert.assertTrue(startTimeCal.get(java.util.Calendar.MINUTE) % 30 == 0);
                java.util.Calendar endTimeCal = java.util.Calendar.getInstance();
                endTimeCal.setTime(freeBlock.getEndTime());
                Assert.assertTrue(endTimeCal.get(java.util.Calendar.MINUTE) % 30 == 0);
            }
        }
    }

    /**
     * Same as {@link #testOffsetStart()}, only use 40 minute blocks.
     */
    @Test
    public void testOffsetStart40() throws Exception {
        MeetingDurations fortyMinuteDurations = new MeetingDurations("40", 40, 40);

        Set<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("9:00 AM", "1:00 PM", "MWF",
                makeDateTime("20090720-0000"), makeDateTime("20090725-0000"), 1, null);

        TreeSet<AvailableBlock> expanded = new TreeSet<AvailableBlock>(AvailableBlockBuilder.expand(blocks, 40));
        Assert.assertEquals(makeDateTime("20090720-0900"), expanded.first().getStartTime());
        Assert.assertEquals(makeDateTime("20090720-0940"), expanded.first().getEndTime());
        Assert.assertEquals(makeDateTime("20090724-1220"), expanded.last().getStartTime());
        Assert.assertEquals(makeDateTime("20090724-1300"), expanded.last().getEndTime());
        AvailableSchedule schedule = new AvailableSchedule(expanded);

        Calendar emptyCalendar = new Calendar();

        MockScheduleOwner owner = new MockScheduleOwner(new MockCalendarAccount(), 1);
        owner.setPreference(Preferences.DURATIONS, fortyMinuteDurations.getKey());
        VisibleSchedule control = this.builder.calculateVisibleSchedule(makeDateTime("20090719-0000"),
                makeDateTime("20090726-0000"), emptyCalendar, schedule, owner);
        Assert.assertEquals(18, control.getFreeCount());
        // verify that all the free blocks start on the twenty minute marks
        for (AvailableBlock freeBlock : control.getFreeList()) {
            java.util.Calendar startTimeCal = java.util.Calendar.getInstance();
            startTimeCal.setTime(freeBlock.getStartTime());
            Assert.assertTrue(startTimeCal.get(java.util.Calendar.MINUTE) % 20 == 0);
            java.util.Calendar endTimeCal = java.util.Calendar.getInstance();
            endTimeCal.setTime(freeBlock.getEndTime());
            Assert.assertTrue(endTimeCal.get(java.util.Calendar.MINUTE) % 20 == 0);
        }

        // now start the offset in between a block
        VisibleSchedule startsInBlock = this.builder.calculateVisibleSchedule(makeDateTime("20090720-1005"),
                makeDateTime("20090727-1005"), emptyCalendar, schedule, owner);
        Assert.assertEquals(16, startsInBlock.getFreeList().size());
        // verify that all the free blocks start on the twenty minute marks
        for (AvailableBlock freeBlock : startsInBlock.getFreeList()) {
            java.util.Calendar startTimeCal = java.util.Calendar.getInstance();
            startTimeCal.setTime(freeBlock.getStartTime());
            Assert.assertTrue(startTimeCal.get(java.util.Calendar.MINUTE) % 20 == 0);
            java.util.Calendar endTimeCal = java.util.Calendar.getInstance();
            endTimeCal.setTime(freeBlock.getEndTime());
            Assert.assertTrue(endTimeCal.get(java.util.Calendar.MINUTE) % 20 == 0);
        }
    }

    /**
     * 
     * @throws Exception
     */
    @Test
    public void testCalculateVisibleScheduleVisitorLimit4WithAttending() throws Exception {
        // construct owner
        MockCalendarAccount person1 = new MockCalendarAccount();
        person1.setEmailAddress("someowner@wisc.edu");
        person1.setDisplayName("Some Owner");
        MockScheduleOwner owner = new MockScheduleOwner(person1, 1);

        MockCalendarAccount person2 = new MockCalendarAccount();
        person2.setEmailAddress("somevisitor@wisc.edu");
        person2.setDisplayName("Some Visitor");
        MockScheduleVisitor visitor = new MockScheduleVisitor(person2);

        final Date targetMeetingStart = makeDateTime("20091117-1300");
        final Date targetMeetingEnd = makeDateTime("20091117-1330");

        VEvent availableAppointment = this.eventUtils.constructAvailableAppointment(
                AvailableBlockBuilder.createBlock(targetMeetingStart, targetMeetingEnd, 4), owner, visitor,
                "test event description");

        ComponentList components = new ComponentList();
        components.add(availableAppointment);
        Calendar calendar = new Calendar(components);
        SortedSet<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("1:00 PM", "2:00 PM", "TR",
                makeDateTime("20091115-0000"), makeDateTime("20091121-0000"), 4, null);

        AvailableSchedule schedule = new AvailableSchedule(blocks);
        VisibleSchedule visibleSchedule = this.builder.calculateVisibleSchedule(makeDateTime("20091115-0000"),
                makeDateTime("20091121-0000"), calendar, schedule, owner, visitor);

        Assert.assertNotNull(visibleSchedule);
        // verify attending shows up
        List<AvailableBlock> visitor1AttendingList = visibleSchedule.getAttendingList();
        Assert.assertEquals(1, visitor1AttendingList.size());
        AvailableBlock visitor1Block = visitor1AttendingList.get(0);
        Assert.assertEquals(targetMeetingStart, visitor1Block.getStartTime());
        Assert.assertEquals(targetMeetingEnd, visitor1Block.getEndTime());
        Assert.assertEquals(AvailableStatus.ATTENDING, visibleSchedule.getBlockMap().get(visitor1Block));

        // construct 2nd visitor
        MockCalendarAccount person3 = new MockCalendarAccount();
        person3.setEmailAddress("othervisitor@wisc.edu");
        person3.setDisplayName("Other Visitor");
        MockScheduleVisitor visitor2 = new MockScheduleVisitor(person3);

        VisibleSchedule visibleSchedule2 = this.builder.calculateVisibleSchedule(makeDateTime("20091115-0000"),
                makeDateTime("20091121-0000"), calendar, schedule, owner, visitor2);

        // visibleSchedule2 should show the Tue Nov 17 1:00 PM slot as FREE, but with 1 spot taken
        Assert.assertEquals(0, visibleSchedule2.getAttendingCount());
        List<AvailableBlock> visitor2FreeList = visibleSchedule2.getFreeList();
        for (AvailableBlock visitor2Block : visitor2FreeList) {
            if (visitor2Block.getStartTime().equals(targetMeetingStart)
                    && visitor2Block.getEndTime().equals(targetMeetingEnd)) {
                Assert.assertEquals(1, visitor2Block.getVisitorsAttending());
            } else {
                Assert.assertEquals(0, visitor2Block.getVisitorsAttending());
            }
            Assert.assertEquals(4, visitor2Block.getVisitorLimit());
            Assert.assertEquals(AvailableStatus.FREE, visibleSchedule2.getBlockMap().get(visitor2Block));
        }

    }

    /**
     * 
     * @throws Exception
     */
    @Test
    public void testOddAppointmentMultipleAttendees() throws Exception {
        Set<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("9:00 AM", "12:00 PM", "MTRF",
                makeDateTime("20090202-0830"), makeDateTime("20090206-1600"), 1, null);

        TreeSet<AvailableBlock> expanded = new TreeSet<AvailableBlock>(AvailableBlockBuilder.expand(blocks, 45));
        LOG.info("expanded set first: " + expanded.first() + ", last: " + expanded.last());
        AvailableSchedule schedule = new AvailableSchedule(expanded);

        DateTime eventStart = new net.fortuna.ical4j.model.DateTime(makeDateTime("20090203-1000"));
        eventStart.setTimeZone(americaChicago);
        DateTime eventEnd = new net.fortuna.ical4j.model.DateTime(makeDateTime("20090203-1030"));
        eventEnd.setTimeZone(americaChicago);
        VEvent someEvent = new VEvent(eventStart, eventEnd, "some conflicting appointment");

        Attendee attendee = new Attendee();
        attendee.setValue("mailto:person@domain.edu");
        attendee.getParameters().add(new XParameter("X-ORACLE-SHOWASFREE", "FREE"));
        attendee.getParameters().add(new Cn("GIVEN SURNAME"));

        Attendee attendee2 = new Attendee();
        attendee2.setValue("mailto:person2@domain.edu");
        attendee2.getParameters().add(new Cn("GIVEN2 SURNAME2"));

        someEvent.getProperties().add(attendee);
        someEvent.getProperties().add(attendee2);
        ComponentList components = new ComponentList();
        components.add(someEvent);

        MockCalendarAccount person1 = new MockCalendarAccount();
        person1.setEmailAddress("someowner@wisc.edu");
        person1.setDisplayName("Some Owner");
        MockScheduleOwner owner = new MockScheduleOwner(person1, 1);
        owner.setPreference(Preferences.DURATIONS, MeetingDurations.FORTYFIVE.getKey());
        VisibleSchedule visible = this.builder.calculateVisibleSchedule(makeDateTime("20090202-0830"),
                makeDateTime("20090206-1600"), new Calendar(components), schedule, owner);

        Assert.assertEquals(16, visible.getSize());
        List<AvailableBlock> free = visible.getFreeList();
        Assert.assertEquals(16, free.size());
        List<AvailableBlock> busy = visible.getBusyList();
        Assert.assertEquals(0, busy.size());
    }

    /**
     * Simulates the following scenario:
     * <ul>
     * <li>IScheduleOwner (owner1) has empty available schedule.</li>
     * <li>IScheduleOwner's (owner1) calendar agenda has an available appointment, but the owner is a visitor to someone else's agenda (person1 is visitor to owner2).</li>
     * </ul>
     * @throws Exception
     */
    @Test
    public void testVisibleScheduleCalendarHasAvailableAppointmentVisitor() throws Exception {
        AvailableSchedule emptyAvailableSchedule = new AvailableSchedule(new TreeSet<AvailableBlock>());

        MockCalendarAccount person1 = new MockCalendarAccount();
        person1.setEmailAddress("someowner@wisc.edu");
        person1.setDisplayName("Some Owner");
        MockScheduleOwner owner1 = new MockScheduleOwner(person1, 1);
        MockScheduleVisitor visitor1 = new MockScheduleVisitor(person1);

        MockCalendarAccount person2 = new MockCalendarAccount();
        person2.setEmailAddress("someotherowner@wisc.edu");
        person2.setDisplayName("Some Other Owner");
        MockScheduleOwner owner2 = new MockScheduleOwner(person2, 2);

        AvailableBlock block = AvailableBlockBuilder.createBlock(makeDateTime("20100911-0646"),
                makeDateTime("20100911-0701"), 3);
        VEvent event = this.eventUtils.constructAvailableAppointment(block, owner2, visitor1, "test appointment");

        ComponentList components = new ComponentList();
        components.add(event);
        Calendar owner1Calendar = new Calendar(components);

        MockCalendarAccount person3 = new MockCalendarAccount();
        person3.setEmailAddress("unsuspecting-visitor@wisc.edu");
        person3.setDisplayName("Unsuspecting Visitor");
        MockScheduleVisitor visitor3 = new MockScheduleVisitor(person3);

        VisibleSchedule visibleSchedule = this.builder.calculateVisibleSchedule(makeDateTime("20100910-1200"),
                makeDateTime("20100917-1200"), owner1Calendar, emptyAvailableSchedule, owner1, visitor3);

        Assert.assertEquals(0, visibleSchedule.getAttendingCount());
        Assert.assertEquals(0, visibleSchedule.getBusyCount());
        Assert.assertEquals(0, visibleSchedule.getFreeCount());

    }

    /**
     * 
     * @throws ParseException
     * @throws IOException
     * @throws ParserException
     * @throws InputFormatException
     */
    @Test
    public void testVisitorConflictsMisaligned()
            throws ParseException, IOException, ParserException, InputFormatException {
        InputStream calendarData = new ClassPathResource(
                "org/jasig/schedassist/model/misaligned-conflicts-test.ics").getInputStream();

        Assert.assertNotNull(calendarData);
        CalendarBuilder builder = new CalendarBuilder();
        Calendar calendar = builder.build(calendarData);

        Date startTime = makeDateTime("20101118-0000");
        Date endTime = makeDateTime("20101121-0000");

        SortedSet<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("10:00 AM", "3:00 PM", "F", startTime,
                endTime, 1, null);
        AvailableSchedule schedule = new AvailableSchedule(blocks);

        MockCalendarAccount person = new MockCalendarAccount();
        person.setEmailAddress("mesdjian@wisctest.wisc.edu");
        person.setDisplayName("ARA MESDJIAN");
        MockScheduleVisitor visitor = new MockScheduleVisitor(person);

        VisibleSchedule visibleSchedule = this.builder.calculateVisitorConflicts(startTime, endTime, calendar,
                schedule, MeetingDurations.fromKey("17"), visitor);

        Assert.assertEquals(3, visibleSchedule.getBusyCount());
        List<AvailableBlock> busyBlocks = visibleSchedule.getBusyList();
        Assert.assertTrue(busyBlocks.contains(
                AvailableBlockBuilder.createBlock(makeDateTime("20101119-1324"), makeDateTime("20101119-1341"))));
        Assert.assertTrue(busyBlocks.contains(
                AvailableBlockBuilder.createBlock(makeDateTime("20101119-1341"), makeDateTime("20101119-1358"))));
        Assert.assertTrue(busyBlocks.contains(
                AvailableBlockBuilder.createBlock(makeDateTime("20101119-1358"), makeDateTime("20101119-1415"))));
    }

    /**
     * helper method to create java.util.Date objects from a String
     * 
     * @param dateTimePhrase format is "yyyyMMdd-HHmm"
     * @return
     * @throws ParseException
     */
    private Date makeDateTime(String dateTimePhrase) throws ParseException {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmm");
        dateFormat.setTimeZone(TimeZone.getTimeZone("America/Chicago"));
        Date time = dateFormat.parse(dateTimePhrase);
        return time;
    }

    /**
     * helper method to create java.util.Date objects from a String
     * 
     * @param datePhrase format is "yyyyMMdd"
     * @return
     * @throws ParseException
     */
    private Date makeDate(String datePhrase) throws ParseException {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
        dateFormat.setTimeZone(TimeZone.getTimeZone("America/Chicago"));
        Date time = dateFormat.parse(datePhrase);
        return time;
    }

}