import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

import org.junit.Test;
import org.quartz.CronExpression;
import org.quartz.CronScheduleBuilder;

/**
 * Quartz CronExpression 정규식 처리를 이해하기 위한 테스트 코드
 * 
 * @author ihoneymon
 * @see http://quartz-scheduler.org/api/2.2.0/org/quartz/CronExpression.html
 */
public class QuartzCronExpressionTest {

    /**
     * Null은 예외발생
     */
    @Test(expected = IllegalArgumentException.class)
    public void testNullCronExpressionException() {
        assertThat(CronExpression.isValidExpression(null), is(false));
        fail();
    }

    /**
     * 빈값이나 cron 정규식 표현이 아니라면 실패
     */
    @Test
    public void testEmptyCronExpressionException() {
        assertThat(CronExpression.isValidExpression(""), is(false));
        assertThat(CronExpression.isValidExpression("1 1+2 1*2"), is(false));
        assertThat(CronExpression.isValidExpression("testWrongChronExpression"), is(false));
    }

    /**
     * Quartz CronExpression 중에 DayOfMonth와 DayOfWeek에 대한 확인 DayOfMonth와
     * DayOfWeek의 표현식에 대해서는 신경을 써야한다.
     * 
     * Second Minute Hour DayOfMonth Month DayOfWeek (optional)Year
     */
    @Test
    public void testDayOfMonthAndDayOfWeekCronExpression() {
        /**
         * same DayOfMonth & DayOfMonth is valid fail DayOfMonth & DayOfMonth is
         * all: false DayOfMonth & DayOfMonth is none: valid fail
         */
        assertThat(CronExpression.isValidExpression("* * * * * *"), is(false));
        assertThat(CronExpression.isValidExpression("* * * ? * ?"), is(false));

        /**
         * DayOfMonth is all and DayOfMonth is none: valid success DayOfMonth is
         * all and DayOfMonth is define: valid fail
         */
        assertThat(CronExpression.isValidExpression("* * * * * ?"), is(true));
        assertThat(CronExpression.isValidExpression("* * * * * MON"), is(false));
        assertThat(CronExpression.isValidExpression("* * * * * 2"), is(false));
        assertThat(CronExpression.isValidExpression("* * * * * 2,5"), is(false));

        /**
         * DayOfMonth is none and DayOfMonth is all: valid success DayOfMonth
         * required none DayOfMonth DayOfMonth가 none(?)인 경우에는 DayOfMonth에 허용되는
         * 정규식은 대부분 통과
         */
        assertThat(CronExpression.isValidExpression("* * * ? * *"), is(true));
        assertThat(CronExpression.isValidExpression("* * * ? * MON"), is(true));
        assertThat(CronExpression.isValidExpression("* * * ? * MON-FRI"), is(true));
        assertThat(CronExpression.isValidExpression("* * * ? * MON-FRI,SUN"), is(true));
        assertThat(CronExpression.isValidExpression("* * * ? * MON#2"), is(true));
        assertThat(CronExpression.isValidExpression("* * * ? * 2#2"), is(true));
        assertThat(CronExpression.isValidExpression("* * * ? * 2#2,1#2"), is(false));

        /**
         * DayOfMonth에
         */
        assertThat(CronExpression.isValidExpression("* * * 1 * ?"), is(true));
        assertThat(CronExpression.isValidExpression("* * * 1-3 * ?"), is(true));
        assertThat(CronExpression.isValidExpression("* * * 1,6,9 * ?"), is(true));
        assertThat(CronExpression.isValidExpression("* * * 1 * *"), is(false));
        assertThat(CronExpression.isValidExpression("* * * 1-3 * *"), is(false));
        assertThat(CronExpression.isValidExpression("* * * 1,6,9 * FRI"), is(false));
        /**
         * DayOfMonth use L(last) special character
         */
        assertThat(CronExpression.isValidExpression("* * * L * ?"), is(true));
        assertThat(CronExpression.isValidExpression("* * * L * *"), is(false));
        assertThat(CronExpression.isValidExpression("* * * L * MON"), is(false));
        /**
         * DayOfMonth에 L은 단독으로 사용되거나 '-nth day'만 허용
         */
        assertThat(CronExpression.isValidExpression("* * * 15L * *"), is(false));
        assertThat(CronExpression.isValidExpression("* * * L15 * *"), is(false));
        assertThat(CronExpression.isValidExpression("* * * 15L * MON"), is(false));
        assertThat(CronExpression.isValidExpression("* * * 15L * ?"), is(false));
        assertThat(CronExpression.isValidExpression("* * * L-3 * ?"), is(true));
        assertThat(CronExpression.isValidExpression("* * * L-3 * *"), is(false));
        assertThat(CronExpression.isValidExpression("* * * L-3 * MON"), is(false));

        /**
         * DayOfMonth use W(week: mon-fri) special character
         */
        assertThat(CronExpression.isValidExpression("* * * W * *"), is(false));
        assertThat(CronExpression.isValidExpression("* * * 15W * *"), is(false));
        assertThat(CronExpression.isValidExpression("* * * 15W * ?"), is(true));
        assertThat(CronExpression.isValidExpression("* * * 15W,16W * ?"), is(true));
        assertThat(CronExpression.isValidExpression("* * * 15W-17W * ?"), is(true));
        assertThat(CronExpression.isValidExpression("* * * 15W/2W * ?"), is(true));

        /**
         * DayOfMonth use LW(last-week: mon-fri) special character
         */
        assertThat(CronExpression.isValidExpression("* * * LW * *"), is(false));
        assertThat(CronExpression.isValidExpression("* * * LW * ?"), is(true));
        assertThat(CronExpression.isValidExpression("* * * 1LW * ?"), is(false));
        assertThat(CronExpression.isValidExpression("* * * LW2 * ?"), is(false));
        assertThat(CronExpression.isValidExpression("* * * LW * MON"), is(false));
        assertThat(CronExpression.isValidExpression("* * * LW * 2"), is(false));
    }

    @Test(expected = RuntimeException.class)
    public void testCronBuilder() {
        assertThat(CronExpression.isValidExpression("* * * LW * *"), is(false));
        CronScheduleBuilder.cronSchedule("* * * LW * *").build();
    }
}

CronExpression.isValidExpression() 에서 false가 발생한 정규식을 CronScheduleBuilder.cronSchedule()를 사용하여 스케줄을 생성하려하면 RuntimeException이 발생한다.

+ Recent posts