1 package com.atlassian.scheduler.core.tests;
2
3 import com.atlassian.scheduler.cron.CronSyntaxException;
4 import com.atlassian.scheduler.cron.ErrorCode;
5 import org.junit.Test;
6
7 import static com.atlassian.scheduler.cron.ErrorCode.INVALID_NAME;
8 import static org.junit.Assert.assertThat;
9 import static org.junit.Assert.fail;
10
11 /**
12 * Quartz has so many stupid bugs in it (both in 1.x and 2.x) that we have little choice but to hack up
13 * the tests with a bunch of special cases. This tries to encapsulate all of that ugliness.
14 *
15 * @since v1.4
16 */
17 public abstract class QuartzCronExpressionValidatorTest extends CronExpressionValidatorTest {
18 /**
19 * Exempts the Quartz validator from reporting error offsets correctly as long as it
20 * reports {@code -1}.
21 * <p>
22 * See {@link com.atlassian.scheduler.core.util.QuartzParseExceptionMapper} for the sad explanation.
23 * </p>
24 */
25 @Override
26 protected boolean verifyErrorOffset(int expected, CronSyntaxException cse) {
27 return cse.getErrorOffset() == -1 || super.verifyErrorOffset(expected, cse);
28 }
29
30 /**
31 * Allow Quartz validator to fail to detect this error.
32 * <p>
33 * There are several cases where the Quartz cron expression parser forgives errors in the syntax that it
34 * really should not permit.
35 * </p>
36 */
37 @Override
38 protected void assertErrorQuartzExempt(String cronExpression, ErrorCode errorCode, String message, String value,
39 int errorOffset, String whyQuartzIsWrong) {
40 try {
41 validator.validate(cronExpression);
42 System.err.println("Quartz thinks '" + cronExpression + "' is valid: " + whyQuartzIsWrong);
43 } catch (CronSyntaxException cse) {
44 assertThat(cse, exception(cronExpression, errorCode, message, value, errorOffset));
45 fail("Quartz used to think '" + cronExpression + "' was valid, but now correctly identifies the problem!");
46 }
47 }
48
49 /**
50 * Quartz's assumption that names are always 3 chars long without checking means that this causes
51 * more characters to get gobbled up as part of the name than should be.
52 */
53 @Override
54 @Test
55 public void testNameRangesWrongLength() {
56 assertInvalidNameDayOfWeek("0 0 0 ? 1 M-FRI", "M-F", 10);
57 assertInvalidNameDayOfWeek("0 0 0 ? 1 MO-FRI", "MO-", 10);
58 assertInvalidNameRange("0 0 0 ? 1 MON-F", 14);
59 assertInvalidNameRange("0 0 0 ? 1 MON-FR", 14);
60 assertInvalidName("0 0 0 WL 1 ?", "WL", 6);
61 }
62
63 /**
64 * Quartz's assumption that names are always 3 chars long without checking means that this causes
65 * a {@code StringIndexOutOfBoundsException} and gets reported as a general failure.
66 * <p>
67 * We work backwards to try to tell what went wrong, but the logic for this is not perfect and
68 * we cannot tell what field was being checked at the time. We can't assume it is always the
69 * day-of-week at the end of the expression, because Quartz tokenizes the expression into fields
70 * beforehand, so it could just as easily happen to the day-of-month. All we can do is report
71 * {@code INVALID_NAME} to be as friendly/helpful as possible.
72 * </p><p>
73 * That's better than nothing, I guess, but the main test does the correct assertion.
74 * </p>
75 */
76 @Override
77 @Test
78 public void testTruncatedExpression() {
79 assertError("0 0 0 ? 1 NO", INVALID_NAME, "Invalid name: 'NO'", "NO", 10);
80 }
81 }