initial addition of unittests
This commit is contained in:
parent
64cc7e5b49
commit
40f438852f
8 changed files with 483 additions and 3 deletions
25
dub.json
25
dub.json
|
@ -4,14 +4,33 @@
|
||||||
"Johannes Loher"
|
"Johannes Loher"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vibe-d": "0.8.1-rc.2",
|
"vibe-d": "0.8.1",
|
||||||
"poodinis": "~>8.0.1"
|
"poodinis": "8.0.1"
|
||||||
},
|
},
|
||||||
"description": "A simple webapplication to edit and view calendar entries",
|
"description": "A simple webapplication to edit and view calendar entries",
|
||||||
"copyright": "Copyright © 2017, Johannes Loher",
|
"copyright": "Copyright © 2017, Johannes Loher",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"targetType": "executable",
|
||||||
|
"targetPath": "generated",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "executable",
|
||||||
|
"versions": [
|
||||||
|
"VibeDefaultMain"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "unittest",
|
||||||
|
"targetType": "executable",
|
||||||
|
"preBuildCommands": ["dub run unit-threaded -c gen_ut_main -- -f generated/ut.d test"],
|
||||||
|
"mainSourceFile": "generated/ut.d",
|
||||||
|
"sourcePaths": ["test"],
|
||||||
|
"dependencies": {
|
||||||
|
"unit-threaded": "0.7.30"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"versions": [
|
"versions": [
|
||||||
"VibeDefaultMain",
|
|
||||||
"VibeUseOpenSSL11"
|
"VibeUseOpenSSL11"
|
||||||
]
|
]
|
||||||
}
|
}
|
31
source/calendarwebapp/app.d
Normal file
31
source/calendarwebapp/app.d
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
module calendarwebapp.app;
|
||||||
|
|
||||||
|
import calendarwebapp.calendarwebapp : CalendarWebapp;
|
||||||
|
import calendarwebapp.configuration : Context;
|
||||||
|
|
||||||
|
import poodinis;
|
||||||
|
|
||||||
|
import vibe.core.log : logInfo;
|
||||||
|
|
||||||
|
import vibe.http.fileserver : serveStaticFiles;
|
||||||
|
import vibe.http.router : URLRouter;
|
||||||
|
import vibe.http.server : HTTPServerSettings, listenHTTP, MemorySessionStore;
|
||||||
|
import vibe.web.web : registerWebInterface;
|
||||||
|
|
||||||
|
shared static this()
|
||||||
|
{
|
||||||
|
auto container = new shared DependencyContainer();
|
||||||
|
container.registerContext!Context;
|
||||||
|
|
||||||
|
auto router = new URLRouter;
|
||||||
|
router.registerWebInterface(container.resolve!CalendarWebapp);
|
||||||
|
router.get("*", serveStaticFiles("public"));
|
||||||
|
|
||||||
|
auto settings = new HTTPServerSettings;
|
||||||
|
settings.port = 8080;
|
||||||
|
settings.bindAddresses = ["::1", "127.0.0.1"];
|
||||||
|
settings.sessionStore = new MemorySessionStore;
|
||||||
|
listenHTTP(settings, router);
|
||||||
|
|
||||||
|
logInfo("Please open http://127.0.0.1:8080/ in your browser.");
|
||||||
|
}
|
30
source/calendarwebapp/authenticator.d
Normal file
30
source/calendarwebapp/authenticator.d
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
module calendarwebapp.authenticator;
|
||||||
|
|
||||||
|
import poodinis;
|
||||||
|
|
||||||
|
import vibe.data.bson : Bson;
|
||||||
|
import vibe.db.mongo.collection : MongoCollection;
|
||||||
|
|
||||||
|
interface Authenticator
|
||||||
|
{
|
||||||
|
bool checkUser(string username, string password) @safe;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MongoDBAuthenticator(Collection = MongoCollection) : Authenticator
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
@Value("users")
|
||||||
|
Collection users;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool checkUser(string username, string password) @safe
|
||||||
|
{
|
||||||
|
auto result = users.findOne(["username" : username, "password" : password]);
|
||||||
|
return result != Bson(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AuthInfo
|
||||||
|
{
|
||||||
|
string userName;
|
||||||
|
}
|
98
source/calendarwebapp/calendarwebapp.d
Normal file
98
source/calendarwebapp/calendarwebapp.d
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
module calendarwebapp.calendarwebapp;
|
||||||
|
|
||||||
|
import calendarwebapp.authenticator : Authenticator, AuthInfo;
|
||||||
|
import calendarwebapp.event;
|
||||||
|
|
||||||
|
import core.time : days;
|
||||||
|
|
||||||
|
import poodinis;
|
||||||
|
|
||||||
|
import std.datetime : Date;
|
||||||
|
import std.exception : enforce;
|
||||||
|
import std.typecons : Nullable;
|
||||||
|
|
||||||
|
import vibe.data.bson : BsonObjectID;
|
||||||
|
import vibe.http.common : HTTPStatusException;
|
||||||
|
import vibe.http.server : HTTPServerRequest, HTTPServerResponse;
|
||||||
|
import vibe.http.status : HTTPStatus;
|
||||||
|
import vibe.web.auth;
|
||||||
|
import vibe.web.web : errorDisplay, noRoute, redirect, render, SessionVar,
|
||||||
|
terminateSession;
|
||||||
|
|
||||||
|
@requiresAuth class CalendarWebapp
|
||||||
|
{
|
||||||
|
@noRoute AuthInfo authenticate(scope HTTPServerRequest req, scope HTTPServerResponse) @safe
|
||||||
|
{
|
||||||
|
if (!req.session || !req.session.isKeySet("auth"))
|
||||||
|
{
|
||||||
|
redirect("/login");
|
||||||
|
return AuthInfo.init;
|
||||||
|
}
|
||||||
|
return req.session.get!AuthInfo("auth");
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
@anyAuth void index()
|
||||||
|
{
|
||||||
|
auto events = eventStore.getAllEvents();
|
||||||
|
render!("showevents.dt", events);
|
||||||
|
}
|
||||||
|
|
||||||
|
@noAuth void getLogin(string _error = null)
|
||||||
|
{
|
||||||
|
render!("login.dt", _error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@noAuth @errorDisplay!getLogin void postLogin(string username, string password) @safe
|
||||||
|
{
|
||||||
|
enforce(authenticator.checkUser(username, password), "Benutzername oder Passwort ungültig");
|
||||||
|
immutable AuthInfo authInfo = {username};
|
||||||
|
auth = authInfo;
|
||||||
|
redirect("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@anyAuth void getLogout() @safe
|
||||||
|
{
|
||||||
|
terminateSession();
|
||||||
|
redirect("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@anyAuth void getCreate(ValidationErrorData _error = ValidationErrorData.init)
|
||||||
|
{
|
||||||
|
render!("create.dt", _error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@anyAuth @errorDisplay!getCreate void postCreate(Date begin,
|
||||||
|
Nullable!Date end, string description, string name, EventType type, bool shout) @safe
|
||||||
|
{
|
||||||
|
import std.array : replace, split;
|
||||||
|
|
||||||
|
if (!end.isNull)
|
||||||
|
enforce(end - begin >= 1.days,
|
||||||
|
"Mehrtägige Ereignisse müssen mindestens einen Tag dauern");
|
||||||
|
auto event = Event(BsonObjectID.generate, begin, end, name,
|
||||||
|
description.replace("\r", ""), type, shout);
|
||||||
|
|
||||||
|
eventStore.addEvent(event);
|
||||||
|
|
||||||
|
redirect("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@anyAuth void postRemove(BsonObjectID id) @safe
|
||||||
|
{
|
||||||
|
eventStore.removeEvent(id);
|
||||||
|
redirect("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ValidationErrorData
|
||||||
|
{
|
||||||
|
string msg;
|
||||||
|
string field;
|
||||||
|
}
|
||||||
|
|
||||||
|
SessionVar!(AuthInfo, "auth") auth;
|
||||||
|
|
||||||
|
@Autowire EventStore eventStore;
|
||||||
|
@Autowire Authenticator authenticator;
|
||||||
|
}
|
61
source/calendarwebapp/configuration.d
Normal file
61
source/calendarwebapp/configuration.d
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
module calendarwebapp.configuration;
|
||||||
|
|
||||||
|
import calendarwebapp.authenticator : Authenticator, MongoDBAuthenticator;
|
||||||
|
import calendarwebapp.calendarwebapp : CalendarWebapp;
|
||||||
|
import calendarwebapp.event : EventStore, MongoDBEventStore;
|
||||||
|
|
||||||
|
import poodinis;
|
||||||
|
|
||||||
|
import vibe.db.mongo.client : MongoClient;
|
||||||
|
import vibe.db.mongo.collection : MongoCollection;
|
||||||
|
import vibe.db.mongo.mongo : connectMongoDB;
|
||||||
|
|
||||||
|
class Context : ApplicationContext
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
override void registerDependencies(shared(DependencyContainer) container)
|
||||||
|
{
|
||||||
|
auto mongoClient = connectMongoDB("localhost");
|
||||||
|
container.register!MongoClient.existingInstance(mongoClient);
|
||||||
|
container.register!(EventStore, MongoDBEventStore!());
|
||||||
|
container.register!(Authenticator, MongoDBAuthenticator!());
|
||||||
|
container.register!CalendarWebapp;
|
||||||
|
container.register!(ValueInjector!string, StringInjector);
|
||||||
|
container.register!(ValueInjector!MongoCollection, MongoCollectionInjector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StringInjector : ValueInjector!string
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
string[string] config;
|
||||||
|
|
||||||
|
public:
|
||||||
|
this() const @safe pure nothrow
|
||||||
|
{
|
||||||
|
// dfmt off
|
||||||
|
config = ["Database name" : "CalendarWebapp",
|
||||||
|
"Users collection name": "users",
|
||||||
|
"Events collection name" : "events"];
|
||||||
|
// dfmt on
|
||||||
|
}
|
||||||
|
|
||||||
|
override string get(string key) const @safe pure nothrow
|
||||||
|
{
|
||||||
|
return config[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MongoCollectionInjector : ValueInjector!MongoCollection
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
@Autowire MongoClient mongoClient;
|
||||||
|
@Value("Database name")
|
||||||
|
string databaseName;
|
||||||
|
|
||||||
|
public:
|
||||||
|
override MongoCollection get(string key) @safe
|
||||||
|
{
|
||||||
|
return mongoClient.getCollection(databaseName ~ "." ~ key);
|
||||||
|
}
|
||||||
|
}
|
79
source/calendarwebapp/event.d
Normal file
79
source/calendarwebapp/event.d
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
module calendarwebapp.event;
|
||||||
|
|
||||||
|
import poodinis;
|
||||||
|
|
||||||
|
import std.algorithm : map;
|
||||||
|
import std.datetime : Date;
|
||||||
|
import std.range.interfaces : InputRange, inputRangeObject;
|
||||||
|
import std.typecons : Nullable;
|
||||||
|
|
||||||
|
import vibe.data.bson : Bson, BsonObjectID, deserializeBson, serializeToBson;
|
||||||
|
import vibe.data.serialization : serializationName = name;
|
||||||
|
import vibe.db.mongo.collection : MongoCollection;
|
||||||
|
|
||||||
|
interface EventStore
|
||||||
|
{
|
||||||
|
Event getEvent(BsonObjectID id) @safe;
|
||||||
|
InputRange!Event getAllEvents() @safe;
|
||||||
|
void addEvent(Event) @safe;
|
||||||
|
InputRange!Event getEventsBeginningBetween(Date begin, Date end) @safe;
|
||||||
|
void removeEvent(BsonObjectID id) @safe;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MongoDBEventStore(Collection = MongoCollection) : EventStore
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Event getEvent(BsonObjectID id) @safe
|
||||||
|
{
|
||||||
|
return events.findOne(["_id" : id]).deserializeBson!Event;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputRange!Event getAllEvents() @safe
|
||||||
|
{
|
||||||
|
return events.find().map!(deserializeBson!Event).inputRangeObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addEvent(Event event) @safe
|
||||||
|
{
|
||||||
|
if (!event.id.valid)
|
||||||
|
event.id = BsonObjectID.generate;
|
||||||
|
|
||||||
|
events.insert(event.serializeToBson);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputRange!Event getEventsBeginningBetween(Date begin, Date end) @safe
|
||||||
|
{
|
||||||
|
return events.find(["$and" : [["date" : ["$gte" : begin.serializeToBson]], ["date"
|
||||||
|
: ["$lte" : end.serializeToBson]]]]).map!(deserializeBson!Event)
|
||||||
|
.inputRangeObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeEvent(BsonObjectID id) @safe
|
||||||
|
{
|
||||||
|
events.remove(["_id" : id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
@Value("events")
|
||||||
|
Collection events;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EventType
|
||||||
|
{
|
||||||
|
Holiday,
|
||||||
|
Birthday,
|
||||||
|
FSI_Event,
|
||||||
|
General_University_Event,
|
||||||
|
Any
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Event
|
||||||
|
{
|
||||||
|
@serializationName("_id") BsonObjectID id;
|
||||||
|
@serializationName("date") Date begin;
|
||||||
|
@serializationName("end_date") Nullable!Date end;
|
||||||
|
string name;
|
||||||
|
@serializationName("desc") string description;
|
||||||
|
@serializationName("etype") EventType type;
|
||||||
|
bool shout;
|
||||||
|
}
|
53
test/calendarwebapp/testauthenticator.d
Normal file
53
test/calendarwebapp/testauthenticator.d
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
module test.calendarwebapp.testauthenticator;
|
||||||
|
|
||||||
|
import calendarwebapp.authenticator;
|
||||||
|
|
||||||
|
import poodinis;
|
||||||
|
|
||||||
|
import unit_threaded.mock;
|
||||||
|
import unit_threaded.should;
|
||||||
|
|
||||||
|
import vibe.data.bson : Bson;
|
||||||
|
|
||||||
|
interface Collection
|
||||||
|
{
|
||||||
|
Bson findOne(string[string] query) @safe;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CollectionInjector : ValueInjector!Collection
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
Collection[string] collections;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void add(string key, Collection collection)
|
||||||
|
{
|
||||||
|
collections[key] = collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
override Collection get(string key) @safe
|
||||||
|
{
|
||||||
|
return collections[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@("Test MongoDBAuthenticator")
|
||||||
|
@system unittest
|
||||||
|
{
|
||||||
|
auto collection = mock!Collection;
|
||||||
|
auto container = new shared DependencyContainer;
|
||||||
|
container.register!(ValueInjector!Collection, CollectionInjector);
|
||||||
|
container.resolve!CollectionInjector.add("users", collection);
|
||||||
|
container.register!(Authenticator, MongoDBAuthenticator!(Collection))(
|
||||||
|
RegistrationOption.doNotAddConcreteTypeRegistration);
|
||||||
|
|
||||||
|
collection.returnValue!"findOne"(Bson(true), Bson(null));
|
||||||
|
collection.expect!"findOne"(["username" : "", "password" : ""]);
|
||||||
|
collection.expect!"findOne"(["username" : "foo", "password" : "bar"]);
|
||||||
|
|
||||||
|
auto authenticator = container.resolve!(Authenticator);
|
||||||
|
authenticator.checkUser("", "").shouldBeTrue;
|
||||||
|
authenticator.checkUser("foo", "bar").shouldBeFalse;
|
||||||
|
|
||||||
|
collection.verify;
|
||||||
|
}
|
109
test/calendarwebapp/testevent.d
Normal file
109
test/calendarwebapp/testevent.d
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
module test.calendarwebapp.testevent;
|
||||||
|
|
||||||
|
import calendarwebapp.event;
|
||||||
|
|
||||||
|
import poodinis;
|
||||||
|
|
||||||
|
import unit_threaded.mock;
|
||||||
|
import unit_threaded.should;
|
||||||
|
|
||||||
|
import vibe.data.bson : Bson, BsonObjectID, serializeToBson;
|
||||||
|
|
||||||
|
interface Collection
|
||||||
|
{
|
||||||
|
Bson findOne(BsonObjectID[string] query) @safe;
|
||||||
|
Bson[] find() @safe;
|
||||||
|
Bson[] find(Bson[string][string][][string] query) @safe;
|
||||||
|
void insert(Bson document) @safe;
|
||||||
|
void remove(BsonObjectID[string] selector) @safe;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CollectionInjector : ValueInjector!Collection
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
Collection[string] collections;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void add(string key, Collection collection)
|
||||||
|
{
|
||||||
|
collections[key] = collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
override Collection get(string key) @safe
|
||||||
|
{
|
||||||
|
return collections[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@("Test failing getEventMongoDBEventStore.getEvent")
|
||||||
|
@system unittest
|
||||||
|
{
|
||||||
|
auto collection = mock!Collection;
|
||||||
|
auto container = new shared DependencyContainer;
|
||||||
|
container.register!(ValueInjector!Collection, CollectionInjector);
|
||||||
|
container.resolve!CollectionInjector.add("events", collection);
|
||||||
|
container.register!(EventStore, MongoDBEventStore!(Collection))(
|
||||||
|
RegistrationOption.doNotAddConcreteTypeRegistration);
|
||||||
|
|
||||||
|
collection.returnValue!"findOne"(Bson(null));
|
||||||
|
|
||||||
|
auto id = BsonObjectID.fromString("599090de97355141140fc698");
|
||||||
|
collection.expect!"findOne"(["_id" : id]);
|
||||||
|
|
||||||
|
auto eventStore = container.resolve!(EventStore);
|
||||||
|
eventStore.getEvent(id).shouldThrowWithMessage!Exception("Expected object instead of null_");
|
||||||
|
collection.verify;
|
||||||
|
}
|
||||||
|
|
||||||
|
@("Test successful MongoDBEventStore.getEvent")
|
||||||
|
@system unittest
|
||||||
|
{
|
||||||
|
auto collection = mock!Collection;
|
||||||
|
auto container = new shared DependencyContainer;
|
||||||
|
container.register!(ValueInjector!Collection, CollectionInjector);
|
||||||
|
container.resolve!CollectionInjector.add("events", collection);
|
||||||
|
container.register!(EventStore, MongoDBEventStore!(Collection))(
|
||||||
|
RegistrationOption.doNotAddConcreteTypeRegistration);
|
||||||
|
|
||||||
|
auto id = BsonObjectID.fromString("599090de97355141140fc698");
|
||||||
|
Event event;
|
||||||
|
event.id = id;
|
||||||
|
|
||||||
|
collection.returnValue!"findOne"(event.serializeToBson);
|
||||||
|
|
||||||
|
collection.expect!"findOne"(["_id" : id]);
|
||||||
|
|
||||||
|
auto eventStore = container.resolve!(EventStore);
|
||||||
|
eventStore.getEvent(id).shouldEqual(event);
|
||||||
|
collection.verify;
|
||||||
|
}
|
||||||
|
|
||||||
|
@("Test MongoDBEventStore.addEvent")
|
||||||
|
@system unittest
|
||||||
|
{
|
||||||
|
auto collection = mock!Collection;
|
||||||
|
auto container = new shared DependencyContainer;
|
||||||
|
container.register!(ValueInjector!Collection, CollectionInjector);
|
||||||
|
container.resolve!CollectionInjector.add("events", collection);
|
||||||
|
container.register!(EventStore, MongoDBEventStore!(Collection))(
|
||||||
|
RegistrationOption.doNotAddConcreteTypeRegistration);
|
||||||
|
|
||||||
|
auto id = BsonObjectID.fromString("599090de97355141140fc698");
|
||||||
|
Event event;
|
||||||
|
event.id = id;
|
||||||
|
auto serializedEvent = event.serializeToBson;
|
||||||
|
|
||||||
|
collection.returnValue!"findOne"(Bson(null), event.serializeToBson);
|
||||||
|
|
||||||
|
collection.expect!"findOne"(["_id" : id]);
|
||||||
|
collection.expect!"insert"(serializedEvent);
|
||||||
|
collection.expect!"findOne"(["_id" : id]);
|
||||||
|
|
||||||
|
auto eventStore = container.resolve!(EventStore);
|
||||||
|
|
||||||
|
eventStore.getEvent(id).shouldThrowWithMessage!Exception("Expected object instead of null_");
|
||||||
|
eventStore.addEvent(event);
|
||||||
|
eventStore.getEvent(id).shouldEqual(event);
|
||||||
|
|
||||||
|
collection.verify;
|
||||||
|
}
|
Loading…
Reference in a new issue