Skip to content

Commit

Permalink
WIP: first fluent checker tests
Browse files Browse the repository at this point in the history
  • Loading branch information
derTobsch committed Dec 13, 2024
1 parent 963c578 commit e570e89
Show file tree
Hide file tree
Showing 3 changed files with 323 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package de.focus_shift.jollyday.tests;

import de.focus_shift.jollyday.core.HolidayCalendar;
import de.focus_shift.jollyday.core.HolidayType;

import java.time.Month;
import java.time.Year;

public interface HolidayCheckerApi {

interface Holiday {
Between fixed(final String propertyKey, final Month month, final int day);
Between fixed(final String propertyKey, final Month month, final int day, final HolidayType type);

Between christian(final String propertyKey);
Between christian(final String propertyKey, final HolidayType type);
}

interface Between {
Between valid(Year from, Year to);

Holiday and();

void check();
}

static HolidayCheckerFluent assertHolidays(final HolidayCalendar calendar) {
return new HolidayCheckerFluent(calendar);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
package de.focus_shift.jollyday.tests;

import de.focus_shift.jollyday.core.Holiday;
import de.focus_shift.jollyday.core.HolidayCalendar;
import de.focus_shift.jollyday.core.HolidayManager;
import de.focus_shift.jollyday.core.HolidayType;
import net.jqwik.api.Arbitraries;
import net.jqwik.time.api.arbitraries.YearArbitrary;
import org.junit.jupiter.api.Assertions;

import java.time.LocalDate;
import java.time.Month;
import java.time.Year;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;

import static de.focus_shift.jollyday.core.ManagerParameters.create;
import static de.focus_shift.jollyday.tests.HolidayCheckerFluent.Category.BY_DAY;
import static de.focus_shift.jollyday.tests.HolidayCheckerFluent.Category.BY_KEY;
import static org.assertj.core.api.Assertions.assertThat;

public class HolidayCheckerFluent implements HolidayCheckerApi.Holiday, HolidayCheckerApi.Between {

enum Category {
BY_DAY,
BY_KEY
}

private final HolidayCalendar calendar;
private String propertyKey;
private Month month;
private int day;
private HolidayType type;
private Category category;
private List<YearRange> validRanges = new ArrayList<>();

private final List<HolidayCheck> checks = new ArrayList<>();

public HolidayCheckerFluent(HolidayCalendar calendar) {
this.calendar = calendar;
}


@Override
public HolidayCheckerApi.Between christian(final String propertyKey) {
return christian(propertyKey, HolidayType.PUBLIC_HOLIDAY);
}

@Override
public HolidayCheckerApi.Between christian(final String propertyKey, final HolidayType type) {
Objects.requireNonNull(propertyKey, "propertyKey is required");
Objects.requireNonNull(type, "holiday type is required");

this.category = BY_KEY;
this.propertyKey = "christian." + propertyKey;
this.type = type;

return this;
}

@Override
public HolidayCheckerApi.Between fixed(final String propertyKey, final Month month, final int day) {
return fixed(propertyKey, month, day, HolidayType.PUBLIC_HOLIDAY);
}

@Override
public HolidayCheckerApi.Between fixed(final String propertyKey, final Month month, final int day, final HolidayType type) {

Objects.requireNonNull(propertyKey, "propertyKey is required");
Objects.requireNonNull(month, "month is required");
if (day >= 32 || day <= 0) {
throw new IllegalArgumentException("day must be between 1 and 31");
}
Objects.requireNonNull(type, "holiday type is required");

this.category = BY_DAY;
this.propertyKey = propertyKey;
this.month = month;
this.day = day;
this.type = type;

return this;
}

@Override
public HolidayCheckerApi.Between valid(final Year from, Year to) {
validRanges.add(new YearRange(from, to));
return this;
}

@Override
public HolidayCheckerApi.Holiday and() {
checks.add(new HolidayCheck(calendar, propertyKey, month, day, type, validRanges, category));

this.propertyKey = null;
this.month = null;
this.day = 0;
this.type = null;
this.category = null;
this.validRanges = new ArrayList<>();

return this;
}

@Override
public void check() {
checks.add(new HolidayCheck(calendar, propertyKey, month, day, type, validRanges, category));

this.propertyKey = null;
this.month = null;
this.day = 0;
this.type = null;
this.category = null;
this.validRanges = new ArrayList<>();

for (HolidayCheck check : checks) {
switch (check.category) {
case BY_DAY:
checkByDate(check);
break;
case BY_KEY:
checkByKey(check);
break;
default:
throw new IllegalStateException("Unexpected value: " + check.category);
}
}

this.checks.clear();
}

private void checkByDate(HolidayCheck check) {
for (final YearRange validRange : check.getValidRanges()) {
((YearArbitrary) Arbitraries.defaultFor(Year.class))
.between(validRange.getFrom().getValue(), validRange.getTo().getValue())
.forEachValue(year -> {
final Set<Holiday> holidays = HolidayManager.getInstance(create(check.calendar)).getHolidays(year);
assertThat(holidays)
.isNotEmpty()
.contains(new Holiday(LocalDate.of(year.getValue(), check.getMonth(), check.getDay()), check.getPropertiesKey(), check.getHolidayType()));
}
);
}
}

private void checkByKey(HolidayCheck check) {
for (final YearRange validRange : check.getValidRanges()) {
((YearArbitrary) Arbitraries.defaultFor(Year.class))
.between(validRange.getFrom().getValue(), validRange.getTo().getValue())
.forEachValue(year -> {
final Set<Holiday> holidays = HolidayManager.getInstance(create(check.calendar)).getHolidays(year);
assertThat(holidays)
.isNotEmpty()
.filteredOn(holiday -> holiday.getPropertiesKey().equals(check.getPropertiesKey()))
.extracting(Holiday::getType)
.contains(check.getHolidayType());
}
);
}
}

private static final class HolidayCheck {

private final HolidayCalendar calendar;
private final List<YearRange> validRanges;
private final Month month;
private final int day;
private final String propertiesKey;
private final HolidayType holidayType;
private final Category category;

HolidayCheck(HolidayCalendar calendar,
String propertiesKey, Month month, int day, HolidayType holidayType,
List<YearRange> validRanges, Category category
) {
this.calendar = calendar;
this.propertiesKey = propertiesKey;
this.month = month;
this.day = day;
this.holidayType = holidayType;
this.validRanges = validRanges.isEmpty() ? List.of(new YearRange(Year.of(1900), Year.of(2500))) : Collections.unmodifiableList(validRanges);
this.category = category;
}

public HolidayCalendar getCalendar() {
return calendar;
}

public List<YearRange> getValidRanges() {
return validRanges;
}

public Month getMonth() {
return month;
}

public int getDay() {
return day;
}

public String getPropertiesKey() {
return propertiesKey;
}

public HolidayType getHolidayType() {
return holidayType;
}

public Category getCategory() {
return category;
}
}

private static class YearRange implements Iterable<Year> {

private final Year from;
private final Year to;

YearRange(final Year from, final Year to) {
if (from != null && to != null) {
Assertions.assertFalse(from.isAfter(to), "To must be greater than or equal to the from year.");
}
this.from = from;
this.to = to;
}

public Year getFrom() {
return from;
}

public Year getTo() {
return to;
}

@Override
public Iterator<Year> iterator() {
return new YearRangeIterator(from, to);
}

private static final class YearRangeIterator implements Iterator<Year> {

private final Year endYear;
private Year cursor;

YearRangeIterator(final Year startYear, final Year endYear) {
this.cursor = startYear;
this.endYear = endYear;
}

@Override
public boolean hasNext() {
return cursor.isBefore(endYear) || cursor.equals(endYear);
}

@Override
public Year next() {

if (cursor.isAfter(endYear)) {
throw new NoSuchElementException("next year is after endYear which is not in range anymore.");
}

final Year current = cursor;
cursor = cursor.plusYears(1);
return current;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

import static de.focus_shift.jollyday.core.HolidayCalendar.AUSTRIA;
import static de.focus_shift.jollyday.core.HolidayType.OBSERVANCE;
import static de.focus_shift.jollyday.tests.HolidayChecker.assertChristian;
import static de.focus_shift.jollyday.tests.HolidayChecker.assertFixed;
import static de.focus_shift.jollyday.tests.HolidayCheckerApi.assertHolidays;
import static java.time.Month.AUGUST;
import static java.time.Month.DECEMBER;
import static java.time.Month.JANUARY;
Expand All @@ -19,22 +19,25 @@ class HolidayATTest {

@Test
void ensuresHolidays() {
assertFixed(AUSTRIA, JANUARY, 1, "NEW_YEAR");
assertFixed(AUSTRIA, JANUARY, 6, "EPIPHANY");
assertFixed(AUSTRIA, MAY, 1, "LABOUR_DAY");
assertFixed(AUSTRIA, AUGUST, 15, "ASSUMPTION_DAY");
assertFixed(AUSTRIA, OCTOBER, 26, "NATIONAL_DAY");
assertFixed(AUSTRIA, NOVEMBER, 1, "ALL_SAINTS");
assertFixed(AUSTRIA, DECEMBER, 8, "IMMACULATE_CONCEPTION");
assertFixed(AUSTRIA, DECEMBER, 24, "CHRISTMAS_EVE", OBSERVANCE);
assertFixed(AUSTRIA, DECEMBER, 25, "CHRISTMAS");
assertFixed(AUSTRIA, DECEMBER, 26, "STEPHENS");
assertFixed(AUSTRIA, DECEMBER, 31, "NEW_YEARS_EVE", OBSERVANCE);
assertChristian(AUSTRIA, "EASTER");
assertChristian(AUSTRIA, "EASTER_MONDAY");
assertChristian(AUSTRIA, "ASCENSION_DAY");
assertChristian(AUSTRIA, "WHIT_MONDAY");
assertChristian(AUSTRIA, "CORPUS_CHRISTI");

assertHolidays(AUSTRIA)
.fixed("NEW_YEAR", JANUARY, 1).and()
.fixed("EPIPHANY", JANUARY, 6).and()
.fixed("LABOUR_DAY", MAY, 1).and()
.fixed("ASSUMPTION_DAY", AUGUST, 15).and()
.fixed("NATIONAL_DAY", OCTOBER, 26).and()
.fixed("ALL_SAINTS", NOVEMBER, 1).and()
.fixed("IMMACULATE_CONCEPTION", DECEMBER, 8).and()
.fixed("CHRISTMAS_EVE", DECEMBER, 24, OBSERVANCE).and()
.fixed("CHRISTMAS", DECEMBER, 25).and()
.fixed("STEPHENS", DECEMBER, 26).and()
.fixed("NEW_YEARS_EVE", DECEMBER, 31, OBSERVANCE).and()
.christian("EASTER").and()
.christian("EASTER_MONDAY").and()
.christian("ASCENSION_DAY").and()
.christian("WHIT_MONDAY").and()
.christian("CORPUS_CHRISTI")
.check();

assertFixed(AUSTRIA, "1", NOVEMBER, 11, "MARTINS_DAY");
assertFixed(AUSTRIA, "2", MARCH, 19, "JOSEFS_DAY");
Expand Down

0 comments on commit e570e89

Please sign in to comment.