Compare commits

...

9 commits

6 changed files with 944 additions and 16 deletions

View file

@ -2,6 +2,7 @@ module calendarwebapp.calendarwebapp;
import calendarwebapp.authenticator; import calendarwebapp.authenticator;
import calendarwebapp.event; import calendarwebapp.event;
import calendarwebapp.jsonexport;
import calendarwebapp.passhash : PasswordHasher; import calendarwebapp.passhash : PasswordHasher;
import core.time : days; import core.time : days;
@ -22,7 +23,7 @@ import vibe.web.web : errorDisplay, noRoute, redirect, render, SessionVar,
@requiresAuth class CalendarWebapp @requiresAuth class CalendarWebapp
{ {
@noRoute AuthInfo authenticate(scope HTTPServerRequest req, scope HTTPServerResponse) @safe @noRoute AuthInfo authenticate(scope HTTPServerRequest, scope HTTPServerResponse) @safe
{ {
if (authInfo.value.isNone) if (authInfo.value.isNone)
redirect("/login"); redirect("/login");
@ -76,13 +77,14 @@ public:
auto event = Event("", begin, end, name, description.replace("\r", ""), type, shout); auto event = Event("", begin, end, name, description.replace("\r", ""), type, shout);
eventStore.addEvent(event); eventStore.addEvent(event);
exporter.exportJSON;
redirect("/"); redirect("/");
} }
@auth(Role.user | Role.admin) void postRemoveevent(string id) @auth(Role.user | Role.admin) void postRemoveevent(string id)
{ {
eventStore.removeEvent(id); eventStore.removeEvent(id);
exporter.exportJSON;
redirect("/"); redirect("/");
} }
@ -128,4 +130,5 @@ private:
@Autowire EventStore eventStore; @Autowire EventStore eventStore;
@Autowire Authenticator authenticator; @Autowire Authenticator authenticator;
@Autowire PasswordHasher passwordHasher; @Autowire PasswordHasher passwordHasher;
@Autowire JSONExporter exporter;
} }

View file

@ -3,6 +3,7 @@ module calendarwebapp.configuration;
import calendarwebapp.authenticator : Authenticator; import calendarwebapp.authenticator : Authenticator;
import calendarwebapp.calendarwebapp : CalendarWebapp; import calendarwebapp.calendarwebapp : CalendarWebapp;
import calendarwebapp.event : EventStore; import calendarwebapp.event : EventStore;
import calendarwebapp.jsonexport : JSONExporter;
import calendarwebapp.passhash : PasswordHasher, SHA256PasswordHasher; import calendarwebapp.passhash : PasswordHasher, SHA256PasswordHasher;
import poodinis; import poodinis;
@ -20,10 +21,10 @@ public:
final switch (arguments.database) with (DatabaseArgument) final switch (arguments.database) with (DatabaseArgument)
{ {
case mongodb: case mongodb:
import vibe.db.mongo.client : MongoClient;
import vibe.db.mongo.mongo : connectMongoDB;
import calendarwebapp.authenticator : MongoDBAuthenticator; import calendarwebapp.authenticator : MongoDBAuthenticator;
import calendarwebapp.event : MongoDBEventStore; import calendarwebapp.event : MongoDBEventStore;
import vibe.db.mongo.client : MongoClient;
import vibe.db.mongo.mongo : connectMongoDB;
auto mongoClient = connectMongoDB(arguments.mongodb.host); auto mongoClient = connectMongoDB(arguments.mongodb.host);
container.register!MongoClient.existingInstance(mongoClient); container.register!MongoClient.existingInstance(mongoClient);
@ -33,9 +34,9 @@ public:
logInfo("Using MongoDB as database system"); logInfo("Using MongoDB as database system");
break; break;
case mysql: case mysql:
import mysql : MySQLPool;
import calendarwebapp.authenticator : MySQLAuthenticator; import calendarwebapp.authenticator : MySQLAuthenticator;
import calendarwebapp.event : MySQLEventStore; import calendarwebapp.event : MySQLEventStore;
import mysql : MySQLPool;
auto pool = new MySQLPool(arguments.mysql.host, arguments.mysql.username, auto pool = new MySQLPool(arguments.mysql.host, arguments.mysql.username,
arguments.mysql.password, arguments.mysql.database); arguments.mysql.password, arguments.mysql.database);
@ -45,6 +46,7 @@ public:
logInfo("Using MySQL as database system"); logInfo("Using MySQL as database system");
break; break;
} }
container.register!JSONExporter;
container.register!(PasswordHasher, SHA256PasswordHasher); container.register!(PasswordHasher, SHA256PasswordHasher);
container.register!CalendarWebapp; container.register!CalendarWebapp;
container.register!(ValueInjector!string, StringInjector); container.register!(ValueInjector!string, StringInjector);
@ -91,8 +93,8 @@ class AppArgumentsInjector : ValueInjector!Arguments
{ {
private: private:
Arguments arguments; Arguments arguments;
public:
public:
this() this()
{ {
import vibe.core.args : readOption; import vibe.core.args : readOption;
@ -109,6 +111,7 @@ public:
"The password to use for logging into the MySQL instance."); "The password to use for logging into the MySQL instance.");
readOption("mysql.database", &arguments.mysql.database, readOption("mysql.database", &arguments.mysql.database,
"The name of the MySQL database to use."); "The name of the MySQL database to use.");
readOption("output", &arguments.output, "The file to write JSON output to.");
} }
override Arguments get(string key) @safe override Arguments get(string key) @safe
@ -120,6 +123,18 @@ public:
} }
} }
class StubAppArgumentsInjector : ValueInjector!Arguments
{
public:
override Arguments get(string key) @safe
{
import std.exception : enforce;
enforce(key == "", "There is only one instance of Arguments, to inject it use @Value().");
return Arguments.init;
}
}
enum DatabaseArgument enum DatabaseArgument
{ {
mongodb, mongodb,
@ -145,4 +160,5 @@ struct Arguments
DatabaseArgument database = DatabaseArgument.mongodb; DatabaseArgument database = DatabaseArgument.mongodb;
MySQLArguments mysql; MySQLArguments mysql;
MongoDBArguments mongodb; MongoDBArguments mongodb;
string output = "cal.json";
} }

View file

@ -2,7 +2,7 @@ module calendarwebapp.event;
import poodinis; import poodinis;
import std.algorithm : map; import std.algorithm : filter, map;
import std.conv : to; import std.conv : to;
import std.datetime : Date; import std.datetime : Date;
import std.range.interfaces : InputRange, inputRangeObject; import std.range.interfaces : InputRange, inputRangeObject;
@ -17,7 +17,7 @@ interface EventStore
Event getEvent(string id); Event getEvent(string id);
InputRange!Event getAllEvents(); InputRange!Event getAllEvents();
void addEvent(Event); void addEvent(Event);
/* InputRange!Event getEventsBeginningBetween(Date begin, Date end) @safe; */ InputRange!Event getEventsBeginningBetween(Date begin, Date end);
void removeEvent(string id); void removeEvent(string id);
} }
@ -54,7 +54,7 @@ public:
InputRange!Event getEventsBeginningBetween(Date begin, Date end) @safe InputRange!Event getEventsBeginningBetween(Date begin, Date end) @safe
{ {
return events.find(["$and" : [["date" : ["$gte" : begin.serializeToBson]], return events.find(["$and" : [["date" : ["$gte" : begin.serializeToBson]],
["date" : ["$lte" : end.serializeToBson]]]]).map!(deserializeBson!Event) ["date" : ["$lt" : end.serializeToBson]]]]).map!(deserializeBson!Event)
.inputRangeObject; .inputRangeObject;
} }
@ -82,7 +82,8 @@ public:
scope (exit) scope (exit)
cn.close; cn.close;
auto prepared = cn.prepare( auto prepared = cn.prepare(
"SELECT id begin end name description type shout FROM " ~ eventsTableName ~ " WHERE id = ?"); "SELECT id begin end name description type shout FROM "
~ eventsTableName ~ " WHERE id = ?");
prepared.setArg(0, id.to!uint); prepared.setArg(0, id.to!uint);
return toEvent(prepared.query.front); return toEvent(prepared.query.front);
} }
@ -103,18 +104,23 @@ public:
scope (exit) scope (exit)
cn.close; cn.close;
auto prepared = cn.prepare( auto prepared = cn.prepare(
"INSERT INTO " ~ eventsTableName ~ " (begin, end, name, description, type, shout) VALUES(?, ?, ?, ?, ?, ?)"); "INSERT INTO " ~ eventsTableName
~ " (begin, end, name, description, type, shout) VALUES(?, ?, ?, ?, ?, ?)");
prepared.setArgs(event.begin, event.end, event.name, event.description, prepared.setArgs(event.begin, event.end, event.name, event.description,
event.type.to!uint, event.shout); event.type.to!uint, event.shout);
prepared.exec(); prepared.exec();
} }
/* InputRange!Event getEventsBeginningBetween(Date begin, Date end) @safe InputRange!Event getEventsBeginningBetween(Date begin, Date end)
{ {
return events.find(["$and" : [["date" : ["$gte" : begin.serializeToBson]], auto cn = pool.lockConnection();
["date" : ["$lte" : end.serializeToBson]]]]).map!(deserializeBson!Event) scope (exit)
.inputRangeObject; cn.close;
} */ auto prepared = cn.prepare(
"SELECT id, begin, end, name, description, type, shout FROM events WHERE begin >= ? and end < ?");
prepared.setArgs(begin, end);
return prepared.querySet.map!(r => toEvent(r)).inputRangeObject;
}
void removeEvent(string id) void removeEvent(string id)
{ {
@ -144,6 +150,42 @@ private:
} }
} }
class StubEventStore : EventStore
{
private:
Event[string] events;
public:
Event getEvent(string id) @safe
{
import std.exception : enforce;
enforce((id in events) != null, "No event with id \"" ~ id ~ "\".");
return events[id];
}
InputRange!Event getAllEvents() nothrow
{
return events.byValue.inputRangeObject;
}
void addEvent(Event event) @safe nothrow
{
events[event.id] = event;
}
InputRange!Event getEventsBeginningBetween(Date begin, Date end) nothrow
{
return events.byValue.filter!(event => event.begin >= begin
&& event.begin < end).inputRangeObject;
}
void removeEvent(string id) @safe nothrow
{
events.remove(id);
}
}
enum EventType enum EventType
{ {
Holiday, Holiday,

View file

@ -0,0 +1,231 @@
module calendarwebapp.jsonexport;
import calendarwebapp.event : Event, EventStore;
import calendarwebapp.configuration : Arguments;
import core.time;
import std.algorithm.iteration : each;
import std.datetime.date;
import std.datetime.interval;
import std.datetime.systime;
import std.format : format;
import poodinis : Autowire, Value;
import vibe.data.serialization : serializationName = name;
struct DayDataManager
{
private:
Date begin, end;
Event[][Date] events;
public:
this(in Date begin, in Date end)
in
{
assert(begin < end,
"DayDataManager: begin (%s) needs to be earlier than end (%s)".format(begin, end));
}
do
{
this.begin = begin;
this.end = end;
Interval!Date(this.begin, this.end).fwdRange(date => date + 1.dur!"days")
.each!(date => events[date] = []);
}
void addEvent(Event event)
{
if (Interval!Date(begin, end).contains(event.begin))
{
events[event.begin] ~= event;
}
}
auto getDayData(Date date)
{
import std.exception : enforce;
enforce(Interval!Date(begin, end).contains(date));
return DayData(date.year, date.month, date.month.toGerString, date.day,
date.dayOfWeek.dayType, events[date], date.dayOfWeek.toShortGerString, []);
}
}
class JSONExporter
{
private:
@Autowire EventStore eventStore;
@Value() Arguments arguments;
public:
auto write(in Date today = cast(Date) Clock.currTime) @system
{
import std.algorithm : each, map;
import std.range : array;
import std.format : format;
immutable todayName = dateFormatString.format(today.dayOfWeek.toGerString,
today.day, today.month.toGerString, today.year);
immutable todays = Today(today.year, today.month, today.day, today.dayOfWeek, todayName);
auto startDate = Date(today.year, today.month, 1);
auto endDate = startDate;
endDate.add!"months"(3);
auto dayDataManager = new DayDataManager(startDate, endDate);
foreach (event; eventStore.getEventsBeginningBetween(startDate, endDate))
{
dayDataManager.addEvent(event);
}
return Interval!Date(startDate, endDate).fwdRange(date => date + 1.dur!"days")
.map!(day => dayDataManager.getDayData(day)).array;
}
void exportJSON() @system
{
import vibe.core.file : writeFile;
import vibe.core.path : Path;
import vibe.data.json : serializeToPrettyJson;
import std.datetime.systime : Clock;
import std.datetime.date : Date;
struct OutputFormat
{
private:
alias TrackedDays = typeof(write());
public:
Today today;
@serializationName("tracked_days") TrackedDays trackedDays;
}
immutable today = cast(Date) Clock.currTime;
auto output = OutputFormat(Today(today.year, today.month, today.day,
today.dayOfWeek, dateFormatString.format(today.dayOfWeek.toGerString,
today.day, today.month.toGerString, today.year)), this.write());
Path(arguments.output).writeFile(cast(ubyte[]) output.serializeToPrettyJson);
}
}
struct DayData
{
short year;
Month month;
string monthName;
ubyte day;
@serializationName("daytype") DayType dayType;
Event[] events;
@serializationName("wday") string weekDayName;
Line[] lines;
}
enum DayType
{
Workday,
Holiday,
Weekend
}
private:
enum dateFormatString = "%s, %s. %s, %s";
string toGerString(Month m)
{
final switch (m) with (Month)
{
case jan:
return "Januar";
case feb:
return "Februar";
case mar:
return "März";
case apr:
return "April";
case may:
return "Mai";
case jun:
return "Juni";
case jul:
return "Juli";
case aug:
return "August";
case sep:
return "September";
case oct:
return "Oktober";
case nov:
return "November";
case dec:
return "Dezember";
}
}
string toGerString(DayOfWeek d)
{
final switch (d) with (DayOfWeek)
{
case mon:
return "Montag";
case tue:
return "Dienstag";
case wed:
return "Mittwoch";
case thu:
return "Donnerstag";
case fri:
return "Freitag";
case sat:
return "Samstag";
case sun:
return "Sonntag";
}
}
string toShortGerString(DayOfWeek d)
{
final switch (d) with (DayOfWeek)
{
case mon:
return "Mo";
case tue:
return "Di";
case wed:
return "Mi";
case thu:
return "Do";
case fri:
return "Fr";
case sat:
return "Sa";
case sun:
return "So";
}
}
DayType dayType(DayOfWeek dayOfWeek)
{
switch (dayOfWeek) with (DayOfWeek)
{
case sat:
return DayType.Weekend;
case sun:
return DayType.Holiday;
default:
return DayType.Workday;
}
}
struct Line
{
}
struct Today
{
short year;
Month month;
ubyte day;
@serializationName("weekday") DayOfWeek weekDay;
string name;
}

View file

@ -165,3 +165,366 @@ public:
collection.verify; collection.verify;
} }
@("StubEventStore.getEvent no event")
@safe unittest
{
auto store = new StubEventStore;
store.getEvent("").shouldThrow;
store.getEvent("599090de97355141140fc698").shouldThrow;
}
@("StubEventStore.getEvent 1 event")
@safe unittest
{
auto store = new StubEventStore;
store.addEvent(Event("599090de97355141140fc698"));
store.getEvent("599090de97355141140fc698").shouldEqual(Event("599090de97355141140fc698"));
store.getEvent("59cb9ad8fc0ba5751c0df02b").shouldThrow;
store.getEvent("").shouldThrow;
}
@("StubEventStore.getEvent 2 events")
@safe unittest
{
auto store = new StubEventStore;
store.addEvent(Event("599090de97355141140fc698"));
store.addEvent(Event("59cb9ad8fc0ba5751c0df02b"));
store.getEvent("599090de97355141140fc698").shouldEqual(Event("599090de97355141140fc698"));
store.getEvent("59cb9ad8fc0ba5751c0df02b").shouldEqual(Event("59cb9ad8fc0ba5751c0df02b"));
store.getEvent("").shouldThrow;
}
@("StubEventStore.getAllEvents no event")
@system unittest
{
auto store = new StubEventStore;
store.getAllEvents.empty.shouldBeTrue;
}
@("StubEventStore.getAllEvents 1 event")
@system unittest
{
auto store = new StubEventStore;
store.addEvent(Event("599090de97355141140fc698"));
store.getAllEvents.array.shouldEqual([Event("599090de97355141140fc698")]);
}
@("StubEventStore.getAllEvents 2 events")
@system unittest
{
auto store = new StubEventStore;
store.addEvent(Event("599090de97355141140fc698"));
store.addEvent(Event("59cb9ad8fc0ba5751c0df02b"));
store.getAllEvents.array.shouldEqual([Event("599090de97355141140fc698"),
Event("59cb9ad8fc0ba5751c0df02b")]);
}
@("StubEventStore.getEventsBeginningBetween no event")
@system unittest
{
import std.datetime.date : Date;
auto store = new StubEventStore;
store.getEventsBeginningBetween(Date(2017, 12, 10), Date(2018, 1, 1)).empty.shouldBeTrue;
}
@("StubEventStore.getEventsBeginningBetween 1 event on begin")
@system unittest
{
import std.datetime.date : Date;
auto store = new StubEventStore;
auto event = Event("599090de97355141140fc698", Date(2017, 12, 10));
store.addEvent(event);
store.getEventsBeginningBetween(Date(2017, 12, 10), Date(2018, 1, 1)).front.shouldEqual(event);
}
@("StubEventStore.getEventsBeginningBetween 1 event on end excluded")
@system unittest
{
import std.datetime.date : Date;
auto store = new StubEventStore;
auto event = Event("599090de97355141140fc698", Date(2018, 1, 1));
store.addEvent(event);
store.getEventsBeginningBetween(Date(2017, 12, 10), Date(2018, 1, 1)).empty.shouldBeTrue;
}
@("StubEventStore.getEventsBeginningBetween 1 event on end included")
@system unittest
{
import std.datetime.date : Date;
auto store = new StubEventStore;
auto event = Event("599090de97355141140fc698", Date(2017, 12, 31));
store.addEvent(event);
store.getEventsBeginningBetween(Date(2017, 12, 10), Date(2018, 1, 1)).front.shouldEqual(event);
}
@("StubEventStore.getEventsBeginningBetween 1 event somwhere inbetween")
@system unittest
{
import std.datetime.date : Date;
auto store = new StubEventStore;
auto event = Event("599090de97355141140fc698", Date(2017, 12, 17));
store.addEvent(event);
store.getEventsBeginningBetween(Date(2017, 12, 10), Date(2018, 1, 1)).front.shouldEqual(event);
}
@("StubEventStore.getEventsBeginningBetween 1 event somwhere before")
@system unittest
{
import std.datetime.date : Date;
auto store = new StubEventStore;
auto event = Event("599090de97355141140fc698", Date(2016, 12, 17));
store.addEvent(event);
store.getEventsBeginningBetween(Date(2017, 12, 10), Date(2018, 1, 1)).empty.shouldBeTrue;
}
@("StubEventStore.getEventsBeginningBetween 1 event somwhere after")
@system unittest
{
import std.datetime.date : Date;
auto store = new StubEventStore;
auto event = Event("599090de97355141140fc698", Date(2018, 12, 17));
store.addEvent(event);
store.getEventsBeginningBetween(Date(2017, 12, 10), Date(2018, 1, 1)).empty.shouldBeTrue;
}
@("StubEventStore.getEventsBeginningBetween 1 event somewhere before, 1 event somewhere after")
@system unittest
{
import std.datetime.date : Date;
auto store = new StubEventStore;
auto event1 = Event("599090de97355141140fc698", Date(2016, 12, 17));
auto event2 = Event("59cb9ad8fc0ba5751c0df02b", Date(2018, 12, 17));
store.addEvent(event1);
store.addEvent(event2);
store.getEventsBeginningBetween(Date(2017, 12, 10), Date(2018, 1, 1)).empty.shouldBeTrue;
}
@("StubEventStore.getEventsBeginningBetween 2 events before")
@system unittest
{
import std.datetime.date : Date;
auto store = new StubEventStore;
auto event1 = Event("599090de97355141140fc698", Date(2016, 12, 17));
auto event2 = Event("59cb9ad8fc0ba5751c0df02b", Date(2016, 12, 16));
store.addEvent(event1);
store.addEvent(event2);
store.getEventsBeginningBetween(Date(2017, 12, 10), Date(2018, 1, 1)).empty.shouldBeTrue;
}
@("StubEventStore.getEventsBeginningBetween 2 events after")
@system unittest
{
import std.datetime.date : Date;
auto store = new StubEventStore;
auto event1 = Event("599090de97355141140fc698", Date(2018, 12, 17));
auto event2 = Event("59cb9ad8fc0ba5751c0df02b", Date(2018, 12, 16));
store.addEvent(event1);
store.addEvent(event2);
store.getEventsBeginningBetween(Date(2017, 12, 10), Date(2018, 1, 1)).empty.shouldBeTrue;
}
@("StubEventStore.getEventsBeginningBetween 1 event on begin, 1 event somwhere outside")
@system unittest
{
import std.datetime.date : Date;
auto store = new StubEventStore;
auto event1 = Event("599090de97355141140fc698", Date(2017, 12, 10));
auto event2 = Event("59cb9ad8fc0ba5751c0df02b", Date(2018, 12, 16));
store.addEvent(event1);
store.addEvent(event2);
store.getEventsBeginningBetween(Date(2017, 12, 10), Date(2018, 1, 1))
.array.shouldEqual([event1]);
}
@("StubEventStore.getEventsBeginningBetween 1 event on end excluded, 1 event somwhere outside")
@system unittest
{
import std.datetime.date : Date;
auto store = new StubEventStore;
auto event1 = Event("599090de97355141140fc698", Date(2018, 1, 1));
auto event2 = Event("59cb9ad8fc0ba5751c0df02b", Date(2018, 12, 16));
store.addEvent(event1);
store.addEvent(event2);
store.getEventsBeginningBetween(Date(2017, 12, 10), Date(2018, 1, 1)).empty.shouldBeTrue;
}
@("StubEventStore.getEventsBeginningBetween 1 event on end included, 1 event somwhere outside")
@system unittest
{
import std.datetime.date : Date;
auto store = new StubEventStore;
auto event1 = Event("599090de97355141140fc698", Date(2017, 12, 31));
auto event2 = Event("59cb9ad8fc0ba5751c0df02b", Date(2018, 12, 16));
store.addEvent(event1);
store.addEvent(event2);
store.getEventsBeginningBetween(Date(2017, 12, 10), Date(2018, 1, 1))
.array.shouldEqual([event1]);
}
@("StubEventStore.getEventsBeginningBetween 1 event somewhere inside, 1 event somwhere outside")
@system unittest
{
import std.datetime.date : Date;
auto store = new StubEventStore;
auto event1 = Event("599090de97355141140fc698", Date(2017, 12, 17));
auto event2 = Event("59cb9ad8fc0ba5751c0df02b", Date(2018, 12, 16));
store.addEvent(event1);
store.addEvent(event2);
store.getEventsBeginningBetween(Date(2017, 12, 10), Date(2018, 1, 1))
.array.shouldEqual([event1]);
}
@("StubEventStore.getEventsBeginningBetween 1 event somewhere inside, 1 event on begin")
@system unittest
{
import std.datetime.date : Date;
import std.exception : assumeUnique;
auto store = new StubEventStore;
immutable event1 = Event("599090de97355141140fc698", Date(2017, 12, 17));
immutable event2 = Event("59cb9ad8fc0ba5751c0df02b", Date(2017, 12, 10));
store.addEvent(event1);
store.addEvent(event2);
immutable events = assumeUnique(store.getEventsBeginningBetween(Date(2017,
12, 10), Date(2018, 1, 1)).array);
event1.shouldBeIn(events);
event2.shouldBeIn(events);
events.length.shouldEqual(2);
}
@("StubEventStore.getEventsBeginningBetween 1 event somewhere inside, 1 event on end excluded")
@system unittest
{
import std.datetime.date : Date;
import std.exception : assumeUnique;
auto store = new StubEventStore;
immutable event1 = Event("599090de97355141140fc698", Date(2017, 12, 17));
immutable event2 = Event("59cb9ad8fc0ba5751c0df02b", Date(2018, 1, 1));
store.addEvent(event1);
store.addEvent(event2);
immutable events = assumeUnique(store.getEventsBeginningBetween(Date(2017,
12, 10), Date(2018, 1, 1)).array);
event1.shouldBeIn(events);
event2.shouldNotBeIn(events);
events.length.shouldEqual(1);
}
@("StubEventStore.getEventsBeginningBetween 1 event somewhere inside, 1 event on end included")
@system unittest
{
import std.datetime.date : Date;
import std.exception : assumeUnique;
auto store = new StubEventStore;
immutable event1 = Event("599090de97355141140fc698", Date(2017, 12, 17));
immutable event2 = Event("59cb9ad8fc0ba5751c0df02b", Date(2017, 12, 31));
store.addEvent(event1);
store.addEvent(event2);
immutable events = assumeUnique(store.getEventsBeginningBetween(Date(2017,
12, 10), Date(2018, 1, 1)).array);
event1.shouldBeIn(events);
event2.shouldBeIn(events);
events.length.shouldEqual(2);
}
@("StubEventStore.getEventsBeginningBetween 2 events somewhere inside")
@system unittest
{
import std.datetime.date : Date;
import std.exception : assumeUnique;
auto store = new StubEventStore;
immutable event1 = Event("599090de97355141140fc698", Date(2017, 12, 17));
immutable event2 = Event("59cb9ad8fc0ba5751c0df02b", Date(2017, 12, 24));
store.addEvent(event1);
store.addEvent(event2);
immutable events = assumeUnique(store.getEventsBeginningBetween(Date(2017,
12, 10), Date(2018, 1, 1)).array);
event1.shouldBeIn(events);
event2.shouldBeIn(events);
events.length.shouldEqual(2);
}
@("StubEventStore.removeEvent 1 event")
@safe unittest
{
auto store = new StubEventStore;
immutable event = Event("599090de97355141140fc698");
store.addEvent(event);
store.getEvent("599090de97355141140fc698").shouldEqual(event);
store.removeEvent("");
store.getEvent("599090de97355141140fc698").shouldEqual(event);
store.removeEvent("599090de97355141140fc698");
store.getEvent("599090de97355141140fc698").shouldThrow;
}
@("StubEventStore.removeEvent 2 events")
@safe unittest
{
auto store = new StubEventStore;
immutable event1 = Event("599090de97355141140fc698");
immutable event2 = Event("59cb9ad8fc0ba5751c0df02b");
store.addEvent(event1);
store.addEvent(event2);
store.getEvent("599090de97355141140fc698").shouldEqual(event1);
store.getEvent("59cb9ad8fc0ba5751c0df02b").shouldEqual(event2);
store.removeEvent("");
store.getEvent("599090de97355141140fc698").shouldEqual(event1);
store.getEvent("59cb9ad8fc0ba5751c0df02b").shouldEqual(event2);
store.removeEvent("599090de97355141140fc698");
store.getEvent("599090de97355141140fc698").shouldThrow;
store.getEvent("59cb9ad8fc0ba5751c0df02b").shouldEqual(event2);
store.removeEvent("59cb9ad8fc0ba5751c0df02b");
store.getEvent("599090de97355141140fc698").shouldThrow;
store.getEvent("59cb9ad8fc0ba5751c0df02b").shouldThrow;
}

View file

@ -0,0 +1,273 @@
module test.calendarwebapp.testjsonexport;
import calendarwebapp.configuration: Arguments, StubAppArgumentsInjector;
import calendarwebapp.event;
import calendarwebapp.jsonexport;
import core.exception : AssertError;
import poodinis;
import std.algorithm.iteration : each;
import std.conv : to;
import std.datetime.date : Date, Month;
import std.exception : enforce;
import std.range.interfaces : InputRange, inputRangeObject;
import std.range.primitives : empty;
import unit_threaded;
@("JSONExporter.write with 0 events")
@system unittest
{
auto container = new shared DependencyContainer();
container.register!(EventStore, StubEventStore);
container.register!JSONExporter;
container.register!(ValueInjector!Arguments, StubAppArgumentsInjector);
auto exporter = container.resolve!JSONExporter;
exporter.write.each!(dayData => dayData.events.empty.shouldBeTrue);
}
@("JSONExporter.write with 1 event")
@system unittest
{
auto container = new shared DependencyContainer();
container.register!(EventStore, StubEventStore);
container.register!JSONExporter;
container.register!(ValueInjector!Arguments, StubAppArgumentsInjector);
auto exporter = container.resolve!JSONExporter;
auto eventStore = container.resolve!EventStore;
immutable event = Event("599090de97355141140fc698", Date(2018, 1, 14));
eventStore.addEvent(event);
exporter.write.each!(dayData => (dayData.year == 2018
&& dayData.month == Month.jan && dayData.day == 14) ? dayData.events.shouldEqual([event])
: dayData.events.empty.shouldBeTrue);
}
@("JSONExporter.write with 2 events at the same date")
@system unittest
{
auto container = new shared DependencyContainer();
container.register!(EventStore, StubEventStore);
container.register!JSONExporter;
container.register!(ValueInjector!Arguments, StubAppArgumentsInjector);
auto exporter = container.resolve!JSONExporter;
auto eventStore = container.resolve!EventStore;
immutable event1 = Event("599090de97355141140fc698", Date(2018, 1, 14));
immutable event2 = Event("59cb9ad8fc0ba5751c0df02b", Date(2018, 1, 14));
eventStore.addEvent(event1);
eventStore.addEvent(event2);
exporter.write(Date(2018, 1, 14)).each!(dayData => (dayData.year == 2018
&& dayData.month == Month.jan && dayData.day == 14) ? dayData.events.shouldEqual([event1,
event2]) : dayData.events.empty.shouldBeTrue);
}
@("JSONExporter.write with 2 events at different dates")
@system unittest
{
auto container = new shared DependencyContainer();
container.register!(EventStore, StubEventStore);
container.register!JSONExporter;
container.register!(ValueInjector!Arguments, StubAppArgumentsInjector);
auto exporter = container.resolve!JSONExporter;
auto eventStore = container.resolve!EventStore;
immutable event1 = Event("599090de97355141140fc698", Date(2018, 1, 14));
immutable event2 = Event("59cb9ad8fc0ba5751c0df02b", Date(2018, 1, 15));
eventStore.addEvent(event1);
eventStore.addEvent(event2);
exporter.write(Date(2018, 1, 14)).each!((dayData) {
immutable date = Date(dayData.year, dayData.month.to!int, dayData.day);
if (date == Date(2018, 1, 14))
{
dayData.events.shouldEqual([event1]);
}
else if (date == Date(2018, 1, 15))
{
dayData.events.shouldEqual([event2]);
}
else
{
dayData.events.empty.shouldBeTrue;
}
});
}
@("JSONExporter.write check date inbetween begin and end")
@system unittest
{
auto container = new shared DependencyContainer();
container.register!(EventStore, StubEventStore);
container.register!JSONExporter;
container.register!(ValueInjector!Arguments, StubAppArgumentsInjector);
auto exporter = container.resolve!JSONExporter;
DayData(2018, Month.jan, "Januar", 14, DayType.Holiday, [], "So", []).shouldBeIn(
exporter.write(Date(2018, 1, 14)));
}
@("JSONExporter.write check date at begin")
@system unittest
{
auto container = new shared DependencyContainer();
container.register!(EventStore, StubEventStore);
container.register!JSONExporter;
container.register!(ValueInjector!Arguments, StubAppArgumentsInjector);
auto exporter = container.resolve!JSONExporter;
DayData(2018, Month.jan, "Januar", 1, DayType.Workday, [], "Mo", []).shouldBeIn(
exporter.write(Date(2018, 1, 14)));
}
@("JSONExporter.write check date just after begin")
@system unittest
{
auto container = new shared DependencyContainer();
container.register!(EventStore, StubEventStore);
container.register!JSONExporter;
container.register!(ValueInjector!Arguments, StubAppArgumentsInjector);
auto exporter = container.resolve!JSONExporter;
DayData(2018, Month.jan, "Januar", 2, DayType.Workday, [], "Di", []).shouldBeIn(
exporter.write(Date(2018, 1, 14)));
}
@("JSONExporter.write check date before begin")
@system unittest
{
auto container = new shared DependencyContainer();
container.register!(EventStore, StubEventStore);
container.register!JSONExporter;
container.register!(ValueInjector!Arguments, StubAppArgumentsInjector);
auto exporter = container.resolve!JSONExporter;
DayData(2017, Month.dec, "Dezember", 1, DayType.Holiday, [], "So", []).shouldNotBeIn(
exporter.write(Date(2018, 1, 14)));
}
@("JSONExporter.write check date at end")
@system unittest
{
auto container = new shared DependencyContainer();
container.register!(EventStore, StubEventStore);
container.register!JSONExporter;
container.register!(ValueInjector!Arguments, StubAppArgumentsInjector);
auto exporter = container.resolve!JSONExporter;
DayData(2018, Month.mar, "März", 31, DayType.Weekend, [], "Sa", []).shouldBeIn(
exporter.write(Date(2018, 1, 14)));
}
@("JSONExporter.write check date just before end")
@system unittest
{
auto container = new shared DependencyContainer();
container.register!(EventStore, StubEventStore);
container.register!JSONExporter;
container.register!(ValueInjector!Arguments, StubAppArgumentsInjector);
auto exporter = container.resolve!JSONExporter;
DayData(2018, Month.mar, "März", 30, DayType.Workday, [], "Fr", []).shouldBeIn(
exporter.write(Date(2018, 1, 14)));
}
@("JSONExporter.write check date after end")
@system unittest
{
auto container = new shared DependencyContainer();
container.register!(EventStore, StubEventStore);
container.register!JSONExporter;
container.register!(ValueInjector!Arguments, StubAppArgumentsInjector);
auto exporter = container.resolve!JSONExporter;
DayData(2018, Month.apr, "April", 1, DayType.Holiday, [], "So", []).shouldNotBeIn(
exporter.write(Date(2018, 1, 14)));
}
@("DayDataManager with begin > end")
@system unittest
{
DayDataManager(Date(2018, 1, 14), Date(2018, 1, 13)).shouldThrow!AssertError;
}
@("DayDataManager with begin = end")
@system unittest
{
DayDataManager(Date(2018, 1, 14), Date(2018, 1, 14)).shouldThrow!AssertError;
}
@("DayDataManager.getDayData with date < begin and 0 events")
@system unittest
{
auto dayDataManager = DayDataManager(Date(2018, 1, 14), Date(2018, 1, 16));
dayDataManager.getDayData(Date(2018, 1, 13)).shouldThrow;
}
@("DayDataManager.getDayData with date > end and 0 events")
@system unittest
{
auto dayDataManager = DayDataManager(Date(2018, 1, 14), Date(2018, 1, 16));
dayDataManager.getDayData(Date(2018, 1, 17)).shouldThrow;
}
@("DayDataManager.getDayData with date = end and 0 events")
@system unittest
{
auto dayDataManager = DayDataManager(Date(2018, 1, 14), Date(2018, 1, 16));
dayDataManager.getDayData(Date(2018, 1, 16)).shouldThrow;
}
@("DayDataManager.getDayData with date = begin and 0 events")
@system unittest
{
auto dayDataManager = DayDataManager(Date(2018, 1, 14), Date(2018, 1, 16));
dayDataManager.getDayData(Date(2018, 1, 14)).shouldEqual(DayData(2018,
Month.jan, "Januar", 14, DayType.Holiday, [], "So", []));
}
@("DayDataManager.getDayData with begin < date < end and 0 events")
@system unittest
{
auto dayDataManager = DayDataManager(Date(2018, 1, 14), Date(2018, 1, 16));
dayDataManager.getDayData(Date(2018, 1, 15)).shouldEqual(DayData(2018,
Month.jan, "Januar", 15, DayType.Workday, [], "Mo", []));
}
@("DayDataManager.getDayData with date < begin and 1 event")
@system unittest
{
auto dayDataManager = DayDataManager(Date(2018, 1, 14), Date(2018, 1, 16));
immutable event = Event("599090de97355141140fc698", Date(2018, 1, 14));
dayDataManager.addEvent(event);
dayDataManager.getDayData(Date(2018, 1, 13)).shouldThrow;
}
@("DayDataManager.getDayData with date > end and 1 event")
@system unittest
{
auto dayDataManager = DayDataManager(Date(2018, 1, 14), Date(2018, 1, 16));
immutable event = Event("599090de97355141140fc698", Date(2018, 1, 14));
dayDataManager.addEvent(event);
dayDataManager.getDayData(Date(2018, 1, 17)).shouldThrow;
}
@("DayDataManager.getDayData with date = end and 1 event")
@system unittest
{
auto dayDataManager = DayDataManager(Date(2018, 1, 14), Date(2018, 1, 16));
immutable event = Event("599090de97355141140fc698", Date(2018, 1, 14));
dayDataManager.addEvent(event);
dayDataManager.getDayData(Date(2018, 1, 16)).shouldThrow;
}
@("DayDataManager.getDayData with date = begin and 1 event")
@system unittest
{
auto dayDataManager = DayDataManager(Date(2018, 1, 14), Date(2018, 1, 15));
immutable event = Event("599090de97355141140fc698", Date(2018, 1, 14));
dayDataManager.addEvent(event);
dayDataManager.getDayData(Date(2018, 1, 14)).shouldEqual(DayData(2018,
Month.jan, "Januar", 14, DayType.Holiday, [event], "So", []));
}
@("DayDataManager.getDayData with begin < date < end and 1 event")
@system unittest
{
auto dayDataManager = DayDataManager(Date(2018, 1, 14), Date(2018, 1, 16));
immutable event = Event("599090de97355141140fc698", Date(2018, 1, 15));
dayDataManager.addEvent(event);
dayDataManager.getDayData(Date(2018, 1, 15)).shouldEqual(DayData(2018,
Month.jan, "Januar", 15, DayType.Workday, [event], "Mo", []));
}