- New User
+ New Todo
-
+
-
+
-
+
-
+
+ (click)="this.addNewTodo(newTodoOwner, newTodoStatus, newTodoBody, newTodoCategory)">Add Todo
diff --git a/client/src/app/todos/add-todo.component.ts b/client/src/app/todos/add-todo.component.ts
index 45899f6..a686c8d 100644
--- a/client/src/app/todos/add-todo.component.ts
+++ b/client/src/app/todos/add-todo.component.ts
@@ -10,7 +10,7 @@ import {Todo} from "./todo";
})
export class AddTodoComponent {
public newTodoOwner:string;
- public newTodoStatus: number;
+ public newTodoStatus: string;
public newTodoBody: string;
public newTodoCategory: string;
private todoAddSuccess : Boolean = false;
@@ -25,7 +25,7 @@ export class AddTodoComponent {
this.dialogRef.close();
}
- addNewTodo(owner: string, status: number, body : string, category : string) : void{
+ addNewTodo(owner: string, status: boolean, body : string, category : string) : void{
//Here we clear all the fields, there's probably a better way
//of doing this could be with forms or something else
diff --git a/client/src/app/todos/todo-list.component.html b/client/src/app/todos/todo-list.component.html
index d9a295c..ede077b 100644
--- a/client/src/app/todos/todo-list.component.html
+++ b/client/src/app/todos/todo-list.component.html
@@ -31,8 +31,8 @@
{{todo.owner}}
- Works for {{todo.body}}
- Status: {{todo.status}} years old
+ Owner {{todo.owner}}
+ Status: {{todo.status}}
diff --git a/client/src/app/todos/todo-list.component.ts b/client/src/app/todos/todo-list.component.ts
index 7fb11a2..18e5650 100644
--- a/client/src/app/todos/todo-list.component.ts
+++ b/client/src/app/todos/todo-list.component.ts
@@ -16,9 +16,11 @@ export class TodoListComponent implements OnInit {
public todos: Todo[];
public filteredTodos: Todo[];
- public todoName : string;
- public todoAge : number;
- public todoCompany : string;
+ public todoOwner : string;
+ public todoID : string;
+ public todoStatus : boolean;
+ public todoBody : string;
+ public todoCategory : string;
public loadReady: boolean = false;
@@ -42,23 +44,23 @@ export class TodoListComponent implements OnInit {
}
- public filterTodos(searchName: string, searchAge: number): Todo[] {
+ public filterTodos(searchOwner: string, searchStatus: boolean): Todo[] {
this.filteredTodos = this.todos;
//Filter by owner
- if (searchName != null) {
- searchName = searchName.toLocaleLowerCase();
+ if (searchOwner != null) {
+ searchOwner = searchOwner.toLocaleLowerCase();
this.filteredTodos = this.filteredTodos.filter(todo => {
- return !searchName || todo.owner.toLowerCase().indexOf(searchName) !== -1;
+ return !searchOwner || todo.owner.toLowerCase().indexOf(searchOwner) !== -1;
});
}
//Filter by status
- if (searchAge != null) {
+ if (searchStatus != null) {
this.filteredTodos = this.filteredTodos.filter(todo => {
- return !searchAge || todo.status == searchAge;
+ return !searchStatus || todo.status == searchStatus;
});
}
@@ -75,12 +77,13 @@ export class TodoListComponent implements OnInit {
//
//Subscribe waits until the data is fully downloaded, then
//performs an action on it (the first lambda)
-
+ console.log("in refreshTodos");
let todos : Observable = this.todoListService.getTodos();
todos.subscribe(
todos => {
+ console.log("First todo in refresh is " + JSON.stringify(todos[0]));
this.todos = todos;
- this.filterTodos(this.todoName, this.todoAge);
+ this.filterTodos(this.todoOwner, this.todoStatus);
},
err => {
console.log(err);
@@ -90,9 +93,11 @@ export class TodoListComponent implements OnInit {
loadService(): void {
+ console.log('in loadService');
this.loadReady = true;
- this.todoListService.getTodos(this.todoCompany).subscribe(
+ this.todoListService.getTodos(this.todoBody).subscribe(
todos => {
+ console.log("First todo in loadService is " + JSON.stringify(todos[0]));
this.todos = todos;
this.filteredTodos = this.todos;
},
diff --git a/client/src/app/todos/todo-list.service.spec.ts b/client/src/app/todos/todo-list.service.spec.ts
index 5610c64..ca98369 100644
--- a/client/src/app/todos/todo-list.service.spec.ts
+++ b/client/src/app/todos/todo-list.service.spec.ts
@@ -9,23 +9,23 @@ describe('Todo list service: ', () => {
// A small collection of test todos
const testTodos: Todo[] = [
{
- _id: 'chris_id',
- owner: 'Chris',
- status: 25,
- body: 'UMM',
- category: 'chris@this.that'
+ _id: '58af3a600343927e48e8720f',
+ owner: 'Blanche',
+ status: false,
+ body: 'In sunt ex non tempor cillum commodo amet incididunt anim qui commodo quis. Cillum non labore ex sint esse.',
+ category: 'software design'
},
{
_id: 'pat_id',
owner: 'Pat',
- status: 37,
+ status: true,
body: 'IBM',
category: 'pat@something.com'
},
{
_id: 'jamie_id',
owner: 'Jamie',
- status: 37,
+ status: true,
body: 'Frogs, Inc.',
category: 'jamie@frogs.com'
}
diff --git a/client/src/app/todos/todo-list.service.ts b/client/src/app/todos/todo-list.service.ts
index f1307b6..1c734a6 100644
--- a/client/src/app/todos/todo-list.service.ts
+++ b/client/src/app/todos/todo-list.service.ts
@@ -15,8 +15,8 @@ export class TodoListService {
constructor(private http: HttpClient) {
}
- getTodos(todoCompany?: string): Observable {
- this.filterByCompany(todoCompany);
+ getTodos(todoOwner?: string): Observable {
+ this.filterByOwner(todoOwner);
return this.http.get(this.todoUrl);
}
@@ -34,27 +34,27 @@ export class TodoListService {
}
*/
- filterByCompany(todoCompany?: string): void {
- if(!(todoCompany == null || todoCompany == "")){
- if (this.todoUrl.indexOf('body=') !== -1){
+ filterByOwner(todoOwner?: string): void {
+ if(!(todoOwner == null || todoOwner == "")){
+ if (this.todoUrl.indexOf('owner=') !== -1){
//there was a previous search by body that we need to clear
- let start = this.todoUrl.indexOf('body=');
+ let start = this.todoUrl.indexOf('owner=');
let end = this.todoUrl.indexOf('&', start);
this.todoUrl = this.todoUrl.substring(0, start-1) + this.todoUrl.substring(end+1);
}
if (this.todoUrl.indexOf('&') !== -1) {
//there was already some information passed in this url
- this.todoUrl += 'body=' + todoCompany + '&';
+ this.todoUrl += 'owner=' + todoOwner + '&';
}
else {
//this was the first bit of information to pass in the url
- this.todoUrl += "?body=" + todoCompany + "&";
+ this.todoUrl += "?owner=" + todoOwner + "&";
}
}
else {
//there was nothing in the box to put onto the URL... reset
- if (this.todoUrl.indexOf('body=') !== -1){
- let start = this.todoUrl.indexOf('body=');
+ if (this.todoUrl.indexOf('owner=') !== -1){
+ let start = this.todoUrl.indexOf('owner=');
let end = this.todoUrl.indexOf('&', start);
if (this.todoUrl.substring(start-1, start) === '?'){
start = start-1
@@ -65,7 +65,7 @@ export class TodoListService {
}
addNewTodo(owner: string, status: boolean, body : string, category : string): Observable {
- const body = {owner:owner, status:status, body:body, category:category};
+ const todoBody = {owner:owner, status:status, body:body, category:category};
console.log(body);
//Send post request to add a new todo with the todo data as the body with specified headers.
diff --git a/client/src/app/todos/todo.component.spec.ts b/client/src/app/todos/todo.component.spec.ts
index 2c0da16..3ad9a7d 100644
--- a/client/src/app/todos/todo.component.spec.ts
+++ b/client/src/app/todos/todo.component.spec.ts
@@ -21,21 +21,21 @@ describe("Todo component", () => {
{
_id: "chris_id",
owner: "Chris",
- status: 25,
+ status: true,
body: "UMM",
category: "chris@this.that"
},
{
_id: "pat_id",
owner: "Pat",
- status: 37,
+ status: true,
body: "IBM",
category: "pat@something.com"
},
{
_id: "jamie_id",
owner: "Jamie",
- status: 37,
+ status: true,
body: "Frogs, Inc.",
category: "jamie@frogs.com"
}
diff --git a/server/src/main/java/umm3601/Server.java b/server/src/main/java/umm3601/Server.java
index 5d2ea0d..22bfb0d 100644
--- a/server/src/main/java/umm3601/Server.java
+++ b/server/src/main/java/umm3601/Server.java
@@ -6,6 +6,9 @@
import spark.Response;
import umm3601.user.UserController;
import umm3601.user.UserRequestHandler;
+import umm3601.todo.TodoController;
+import umm3601.todo.TodoRequestHandler;
+
import java.io.IOException;
@@ -14,17 +17,21 @@
import static spark.debug.DebugScreen.enableDebugScreen;
public class Server {
- private static final String userDatabaseName = "dev";
+ private static final String databaseName = "dev";
private static final int serverPort = 4567;
public static void main(String[] args) throws IOException {
MongoClient mongoClient = new MongoClient();
- MongoDatabase userDatabase = mongoClient.getDatabase(userDatabaseName);
+ MongoDatabase userDatabase = mongoClient.getDatabase(databaseName);
+ MongoDatabase todoDatabase = mongoClient.getDatabase(databaseName);
UserController userController = new UserController(userDatabase);
UserRequestHandler userRequestHandler = new UserRequestHandler(userController);
+ TodoController todoController = new TodoController(todoDatabase);
+ TodoRequestHandler todoRequestHandler = new TodoRequestHandler(todoController);
+
//Configure Spark
port(serverPort);
enableDebugScreen();
@@ -67,6 +74,10 @@ public static void main(String[] args) throws IOException {
get("api/users/:id", userRequestHandler::getUserJSON);
post("api/users/new", userRequestHandler::addNewUser);
+ get("api/todos", todoRequestHandler::getTodos);
+ get("api/todos/:id", todoRequestHandler::getTodoJSON);
+ post("api/todos/new", todoRequestHandler::addNewTodo);
+
// An example of throwing an unhandled exception so you can see how the
// Java Spark debugger displays errors like this.
get("api/error", (req, res) -> {
diff --git a/server/src/main/java/umm3601/todo/TodoController.java b/server/src/main/java/umm3601/todo/TodoController.java
new file mode 100644
index 0000000..b79d575
--- /dev/null
+++ b/server/src/main/java/umm3601/todo/TodoController.java
@@ -0,0 +1,129 @@
+package umm3601.todo;
+
+import com.google.gson.Gson;
+import com.mongodb.*;
+import com.mongodb.client.FindIterable;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.util.JSON;
+import org.bson.Document;
+import org.bson.types.ObjectId;
+import java.util.Iterator;
+import java.util.Map;
+
+import static com.mongodb.client.model.Filters.eq;
+
+/**
+ * Controller that manages requests for info about todos.
+ */
+public class TodoController {
+
+ private final Gson gson;
+ private MongoDatabase database;
+ private final MongoCollection todoCollection;
+
+ /**
+ * Construct a controller for todos.
+ *
+ * @param database the database containing todo data
+ */
+ public TodoController(MongoDatabase database) {
+ gson = new Gson();
+ this.database = database;
+ todoCollection = database.getCollection("todos");
+ }
+
+
+
+
+ /**
+ * Helper method that gets a single todo specified by the `id`
+ * parameter in the request.
+ *
+ * @param id the Mongo ID of the desired todo
+ * @return the desired todo as a JSON object if the todo with that ID is found,
+ * and `null` if no todo with that ID is found
+ */
+
+ public String getTodo(String id) {
+ FindIterable jsonTodos
+ = todoCollection
+ .find(eq("_id", new ObjectId(id)));
+
+ Iterator iterator = jsonTodos.iterator();
+ if (iterator.hasNext()) {
+ Document todo = iterator.next();
+ return todo.toJson();
+ } else {
+ // We didn't find the desired todo
+ return null;
+ }
+ }
+
+
+ /** Helper method which iterates through the collection, receiving all
+ * documents if no query parameter is specified. If the age query parameter
+ * is specified, then the collection is filtered so only documents of that
+ * specified age are found.
+ *
+ /**
+ * @param queryParams
+ * @return an array of Todos in a JSON formatted string
+ */
+ public String getTodos(Map queryParams) {
+
+ Document filterDoc = new Document();
+
+ if (queryParams.containsKey("status")) {
+ boolean targetStatus = Boolean.parseBoolean(queryParams.get("status")[0]);
+ filterDoc = filterDoc.append("status", targetStatus);
+ }
+
+ if (queryParams.containsKey("body")) {
+ String targetContent = (queryParams.get("body")[0]);
+ Document contentRegQuery = new Document();
+ contentRegQuery.append("$regex", targetContent);
+ contentRegQuery.append("$options", "i");
+ filterDoc = filterDoc.append("body", contentRegQuery);
+ }
+
+ //FindIterable comes from mongo, Document comes from Gson
+ FindIterable matchingTodos = todoCollection.find(filterDoc);
+
+ return JSON.serialize(matchingTodos);
+ }
+
+
+ /**Helper method which appends received todo information to the to-be added document
+ /**
+ *
+ * @param owner
+ * @param status
+ * @param body
+ * @param category
+ * @return boolean after successfully or unsuccessfully adding a todo
+ */
+ public boolean addNewTodo(String owner, boolean status, String body, String category) {
+
+ Document newTodo = new Document();
+ newTodo.append("owner", owner);
+ newTodo.append("status", status);
+ newTodo.append("body", body);
+ newTodo.append("category", category);
+
+ try {
+ todoCollection.insertOne(newTodo);
+ }
+ catch(MongoException me)
+ {
+ me.printStackTrace();
+ return false;
+ }
+
+ return true;
+ }
+
+
+
+
+}
diff --git a/server/src/main/java/umm3601/todo/TodoRequestHandler.java b/server/src/main/java/umm3601/todo/TodoRequestHandler.java
new file mode 100644
index 0000000..9649857
--- /dev/null
+++ b/server/src/main/java/umm3601/todo/TodoRequestHandler.java
@@ -0,0 +1,112 @@
+package umm3601.todo;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.util.JSON;
+import spark.Request;
+import spark.Response;
+
+/**
+ * Created by Brian on 11/29/2017.
+ */
+public class TodoRequestHandler {
+
+ private final TodoController todoController;
+ public TodoRequestHandler(TodoController todoController){
+ this.todoController = todoController;
+ }
+ /**Method called from Server when the 'api/todos/:id' endpoint is received.
+ * Get a JSON response with a list of all the todos in the database.
+ *
+ * @param req the HTTP request
+ * @param res the HTTP response
+ * @return one todo in JSON formatted string and if it fails it will return text with a different HTTP status code
+ */
+ public String getTodoJSON(Request req, Response res){
+ res.type("application/json");
+ String id = req.params("id");
+ String todo;
+ try {
+ todo = todoController.getTodo(id);
+ } catch (IllegalArgumentException e) {
+ // This is thrown if the ID doesn't have the appropriate
+ // form for a Mongo Object ID.
+ // https://docs.mongodb.com/manual/reference/method/ObjectId/
+ res.status(400);
+ res.body("The requested todo id " + id + " wasn't a legal Mongo Object ID.\n" +
+ "See 'https://docs.mongodb.com/manual/reference/method/ObjectId/' for more info.");
+ return "";
+ }
+ if (todo != null) {
+ return todo;
+ } else {
+ res.status(404);
+ res.body("The requested todo with id " + id + " was not found");
+ return "";
+ }
+ }
+
+
+
+ /**Method called from Server when the 'api/todos' endpoint is received.
+ * This handles the request received and the response
+ * that will be sent back.
+ *@param req the HTTP request
+ * @param res the HTTP response
+ * @return an array of todos in JSON formatted String
+ */
+ public String getTodos(Request req, Response res)
+ {
+ res.type("application/json");
+ return todoController.getTodos(req.queryMap().toMap());
+ }
+
+
+ /**Method called from Server when the 'api/todos/new'endpoint is recieved.
+ * Gets specified todo info from request and calls addNewTodo helper method
+ * to append that info to a document
+ *
+ * @param req the HTTP request
+ * @param res the HTTP response
+ * @return a boolean as whether the todo was added successfully or not
+ */
+ public boolean addNewTodo(Request req, Response res)
+ {
+
+ res.type("application/json");
+ Object o = JSON.parse(req.body());
+ try {
+ if(o.getClass().equals(BasicDBObject.class))
+ {
+ try {
+ BasicDBObject dbO = (BasicDBObject) o;
+
+ String owner = dbO.getString("owner");
+ //For some reason status is a string right now, caused by angular.
+ //This is a problem and should not be this way but here ya go
+ boolean status = dbO.getBoolean("status");
+ String body = dbO.getString("body");
+ String category = dbO.getString("category");
+
+ System.err.println("Adding new todo [owner=" + owner + ", status=" + status + " body=" + body + " category=" + category + ']');
+ return todoController.addNewTodo(owner, status, body, category);
+ }
+ catch(NullPointerException e)
+ {
+ System.err.println("A value was malformed or omitted, new todo request failed.");
+ return false;
+ }
+
+ }
+ else
+ {
+ System.err.println("Expected BasicDBObject, received " + o.getClass());
+ return false;
+ }
+ }
+ catch(RuntimeException ree)
+ {
+ ree.printStackTrace();
+ return false;
+ }
+ }
+}
diff --git a/server/src/test/java/umm3601/mongotest/MongoSpec.java b/server/src/test/java/umm3601/mongotest/MongoSpec.java
index 4849029..8f8f3a3 100644
--- a/server/src/test/java/umm3601/mongotest/MongoSpec.java
+++ b/server/src/test/java/umm3601/mongotest/MongoSpec.java
@@ -37,6 +37,7 @@ public class MongoSpec {
private MongoCollection userDocuments;
+
@Before
public void clearAndPopulateDB() {
MongoClient mongoClient = new MongoClient();
diff --git a/server/src/test/java/umm3601/mongotest/TodoMongoSpec.java b/server/src/test/java/umm3601/mongotest/TodoMongoSpec.java
new file mode 100644
index 0000000..6bc9789
--- /dev/null
+++ b/server/src/test/java/umm3601/mongotest/TodoMongoSpec.java
@@ -0,0 +1,216 @@
+package umm3601.mongotest;
+
+import com.mongodb.MongoClient;
+import com.mongodb.client.*;
+import com.mongodb.client.model.Accumulators;
+import com.mongodb.client.model.Aggregates;
+import com.mongodb.client.model.Sorts;
+import org.bson.Document;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static com.mongodb.client.model.Filters.*;
+import static com.mongodb.client.model.Projections.*;
+import static org.junit.Assert.*;
+
+/**
+ * Some simple "tests" that demonstrate our ability to
+ * connect to a Mongo database and run some basic queries
+ * against it.
+ *
+ * Note that none of these are actually tests of any of our
+ * code; they are mostly demonstrations of the behavior of
+ * the MongoDB Java libraries. Thus if they test anything,
+ * they test that code, and perhaps our understanding of it.
+ *
+ * To test "our" code we'd want the tests to confirm that
+ * the behavior of methods in things like the TodoController
+ * do the "right" thing.
+ *
+ * Created by mcphee on 20/2/17.
+ */
+public class TodoMongoSpec {
+
+ private MongoCollection todoDocuments;
+
+
+ @Before
+ public void clearAndPopulateDB() {
+ MongoClient mongoClient = new MongoClient();
+ MongoDatabase db = mongoClient.getDatabase("test");
+ todoDocuments = db.getCollection("todos");
+ todoDocuments.drop();
+ List testTodos = new ArrayList<>();
+ testTodos.add(Document.parse("{\n" +
+ " owner: \"Chris\",\n" +
+ " status: 25,\n" +
+ " body: \"UMM\",\n" +
+ " body: \"UMM\",\n" +
+ " category: \"chris@this.that\"\n" +
+ " }"));
+ testTodos.add(Document.parse("{\n" +
+ " owner: \"Pat\",\n" +
+ " status: 37,\n" +
+ " body: \"IBM\",\n" +
+ " body: \"IBM\",\n" +
+ " category: \"pat@something.com\"\n" +
+ " }"));
+ testTodos.add(Document.parse("{\n" +
+ " owner: \"Jamie\",\n" +
+ " status: 37,\n" +
+ " body: \"Frogs, Inc.\",\n" +
+ " body: \"Frogs, Inc.\",\n" +
+ " category: \"jamie@frogs.com\"\n" +
+ " }"));
+ todoDocuments.insertMany(testTodos);
+ }
+
+ private List intoList(MongoIterable documents) {
+ List todos = new ArrayList<>();
+ documents.into(todos);
+ return todos;
+ }
+
+ private int countTodos(FindIterable documents) {
+ List todos = intoList(documents);
+ return todos.size();
+ }
+
+ @Test
+ public void shouldBeThreeTodos() {
+ FindIterable documents = todoDocuments.find();
+ int numberOfTodos = countTodos(documents);
+ assertEquals("Should be 3 total todos", 3, numberOfTodos);
+ }
+
+ @Test
+ public void shouldBeOneChris() {
+ FindIterable documents = todoDocuments.find(eq("owner", "Chris"));
+ int numberOfTodos = countTodos(documents);
+ assertEquals("Should be 1 Chris", 1, numberOfTodos);
+ }
+
+ @Test
+ public void shouldBeTwoOver25() {
+ FindIterable documents = todoDocuments.find(gt("status", 25));
+ int numberOfTodos = countTodos(documents);
+ assertEquals("Should be 2 over 25", 2, numberOfTodos);
+ }
+
+ @Test
+ public void over25SortedByName() {
+ FindIterable documents
+ = todoDocuments.find(gt("status", 25))
+ .sort(Sorts.ascending("owner"));
+ List docs = intoList(documents);
+ assertEquals("Should be 2", 2, docs.size());
+ assertEquals("First should be Jamie", "Jamie", docs.get(0).get("owner"));
+ assertEquals("Second should be Pat", "Pat", docs.get(1).get("owner"));
+ }
+
+ @Test
+ public void over25AndIbmers() {
+ FindIterable documents
+ = todoDocuments.find(and(gt("status", 25),
+ eq("body", "IBM")));
+ List docs = intoList(documents);
+ assertEquals("Should be 1", 1, docs.size());
+ assertEquals("First should be Pat", "Pat", docs.get(0).get("owner"));
+ }
+
+ @Test
+ public void justNameAndEmail() {
+ FindIterable documents
+ = todoDocuments.find().projection(fields(include("owner", "category")));
+ List docs = intoList(documents);
+ assertEquals("Should be 3", 3, docs.size());
+ assertEquals("First should be Chris", "Chris", docs.get(0).get("owner"));
+ assertNotNull("First should have category", docs.get(0).get("category"));
+ assertNull("First shouldn't have 'body'", docs.get(0).get("body"));
+ assertNull("First shouldn't have 'body'", docs.get(0).get("body"));
+ assertNull("First shouldn't have 'body'", docs.get(0).get("body"));
+ assertNotNull("First should have '_id'", docs.get(0).get("_id"));
+ }
+
+ @Test
+ public void justNameAndEmailNoId() {
+ FindIterable documents
+ = todoDocuments.find()
+ .projection(fields(include("owner", "category"), excludeId()));
+ List docs = intoList(documents);
+ assertEquals("Should be 3", 3, docs.size());
+ assertEquals("First should be Chris", "Chris", docs.get(0).get("owner"));
+ assertNotNull("First should have category", docs.get(0).get("category"));
+ assertNull("First shouldn't have 'body'", docs.get(0).get("body"));
+ assertNull("First shouldn't have 'body'", docs.get(0).get("body"));
+ assertNull("First shouldn't have 'body'", docs.get(0).get("body"));
+ assertNull("First should not have '_id'", docs.get(0).get("_id"));
+ }
+
+ @Test
+ public void justNameAndEmailNoIdSortedByCompany() {
+ FindIterable documents
+ = todoDocuments.find()
+ .sort(Sorts.ascending("body"))
+ .sort(Sorts.ascending("body"))
+ .projection(fields(include("owner", "category"), excludeId()));
+ List docs = intoList(documents);
+ assertEquals("Should be 3", 3, docs.size());
+ assertEquals("First should be Jamie", "Jamie", docs.get(0).get("owner"));
+ assertNotNull("First should have category", docs.get(0).get("category"));
+ assertNull("First shouldn't have 'body'", docs.get(0).get("body"));
+ assertNull("First shouldn't have 'body'", docs.get(0).get("body"));
+ assertNull("First shouldn't have 'body'", docs.get(0).get("body"));
+ assertNull("First should not have '_id'", docs.get(0).get("_id"));
+ }
+
+ @Test
+ public void ageCounts() {
+ AggregateIterable documents
+ = todoDocuments.aggregate(
+ Arrays.asList(
+ /*
+ * Groups data by the "status" field, and then counts
+ * the number of documents with each given status.
+ * This creates a new "constructed document" that
+ * has "status" as it's "_id", and the count as the
+ * "ageCount" field.
+ */
+ Aggregates.group("$age",
+ Accumulators.sum("ageCount", 1)),
+ Aggregates.sort(Sorts.ascending("_id"))
+ )
+ );
+ List docs = intoList(documents);
+ assertEquals("Should be two distinct ages", 2, docs.size());
+ assertEquals(docs.get(0).get("_id"), 25);
+ assertEquals(docs.get(0).get("ageCount"), 1);
+ assertEquals(docs.get(1).get("_id"), 37);
+ assertEquals(docs.get(1).get("ageCount"), 2);
+ }
+
+ @Test
+ public void averageAge() {
+ AggregateIterable documents
+ = todoDocuments.aggregate(
+ Arrays.asList(
+ Aggregates.group("$company",
+ Accumulators.avg("averageAge", "$age")),
+ Aggregates.sort(Sorts.ascending("_id"))
+ ));
+ List docs = intoList(documents);
+ assertEquals("Should be three companies", 3, docs.size());
+
+ assertEquals("Frogs, Inc.", docs.get(0).get("_id"));
+ assertEquals(37.0, docs.get(0).get("averageAge"));
+ assertEquals("IBM", docs.get(1).get("_id"));
+ assertEquals(37.0, docs.get(1).get("averageAge"));
+ assertEquals("UMM", docs.get(2).get("_id"));
+ assertEquals(25.0, docs.get(2).get("averageAge"));
+ }
+
+}
diff --git a/server/src/test/java/umm3601/todo/TodoControllerSpec.java b/server/src/test/java/umm3601/todo/TodoControllerSpec.java
new file mode 100644
index 0000000..af5dd52
--- /dev/null
+++ b/server/src/test/java/umm3601/todo/TodoControllerSpec.java
@@ -0,0 +1,176 @@
+package umm3601.todo;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import org.bson.*;
+import org.bson.codecs.*;
+import org.bson.codecs.configuration.CodecRegistries;
+import org.bson.codecs.configuration.CodecRegistry;
+import org.bson.json.JsonReader;
+import org.bson.types.ObjectId;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.*;
+
+/**
+ * JUnit tests for the TodoController.
+ *
+ * Created by mcphee on 22/2/17.
+ */
+public class TodoControllerSpec
+{
+ private TodoController todoController;
+ private ObjectId samsId;
+ @Before
+ public void clearAndPopulateDB() throws IOException {
+ MongoClient mongoClient = new MongoClient();
+ MongoDatabase db = mongoClient.getDatabase("test");
+ MongoCollection todoDocuments = db.getCollection("todos");
+ todoDocuments.drop();
+ List testTodos = new ArrayList<>();
+ testTodos.add(Document.parse("{\n" +
+ " owner: \"Chris\",\n" +
+ " status: true,\n" +
+ " body: \"UMM\",\n" +
+ " category: \"chris@this.that\"\n" +
+ " }"));
+ testTodos.add(Document.parse("{\n" +
+ " owner: \"Pat\",\n" +
+ " status: true,\n" +
+ " body: \"IBM\",\n" +
+ " category: \"pat@something.com\"\n" +
+ " }"));
+ testTodos.add(Document.parse("{\n" +
+ " owner: \"Jamie\",\n" +
+ " status: true,\n" +
+ " body: \"Frogs, Inc.\",\n" +
+ " category: \"jamie@frogs.com\"\n" +
+ " }"));
+
+ samsId = new ObjectId();
+ BasicDBObject sam = new BasicDBObject("_id", samsId);
+ sam = sam.append("owner", "Sam")
+ .append("status", true)
+ .append("body", "Frogs, Inc.")
+ .append("category", "sam@frogs.com");
+
+
+
+ todoDocuments.insertMany(testTodos);
+ todoDocuments.insertOne(Document.parse(sam.toJson()));
+
+ // It might be important to construct this _after_ the DB is set up
+ // in case there are bits in the constructor that care about the state
+ // of the database.
+ todoController = new TodoController(db);
+ }
+
+ // http://stackoverflow.com/questions/34436952/json-parse-equivalent-in-mongo-driver-3-x-for-java
+ private BsonArray parseJsonArray(String json) {
+ final CodecRegistry codecRegistry
+ = CodecRegistries.fromProviders(Arrays.asList(
+ new ValueCodecProvider(),
+ new BsonValueCodecProvider(),
+ new DocumentCodecProvider()));
+
+ JsonReader reader = new JsonReader(json);
+ BsonArrayCodec arrayReader = new BsonArrayCodec(codecRegistry);
+
+ return arrayReader.decode(reader, DecoderContext.builder().build());
+ }
+
+ private static String getName(BsonValue val) {
+ BsonDocument doc = val.asDocument();
+ return ((BsonString) doc.get("owner")).getValue();
+ }
+
+ @Test
+ public void getAllTodos() {
+ Map emptyMap = new HashMap<>();
+ String jsonResult = todoController.getTodos(emptyMap);
+ BsonArray docs = parseJsonArray(jsonResult);
+
+ assertEquals("Should be 4 todos", 4, docs.size());
+ List owners = docs
+ .stream()
+ .map(TodoControllerSpec::getName)
+ .sorted()
+ .collect(Collectors.toList());
+ List expectedNames = Arrays.asList("Chris", "Jamie", "Pat", "Sam");
+ assertEquals("Names should match", expectedNames, owners);
+ }
+
+ @Test
+ public void getTodosWhoAre37() {
+ Map argMap = new HashMap<>();
+ argMap.put("status", new String[] { "true" });
+ String jsonResult = todoController.getTodos(argMap);
+ BsonArray docs = parseJsonArray(jsonResult);
+
+ assertEquals("Should be 2 todos", 2, docs.size());
+ List owners = docs
+ .stream()
+ .map(TodoControllerSpec::getName)
+ .sorted()
+ .collect(Collectors.toList());
+ List expectedNames = Arrays.asList("Jamie", "Pat");
+ assertEquals("Names should match", expectedNames, owners);
+ }
+
+ @Test
+ public void getSamById() {
+ String jsonResult = todoController.getTodo(samsId.toHexString());
+ Document sam = Document.parse(jsonResult);
+ assertEquals("Name should match", "Sam", sam.get("owner"));
+ String noJsonResult = todoController.getTodo(new ObjectId().toString());
+ assertNull("No owner should match",noJsonResult);
+
+ }
+
+ @Test
+ public void addTodoTest(){
+ //String newId = todoController.addNewTodo("Brian",true,"umm", "brian@yahoo.com");
+
+ //assertNotNull("Add new todo should return true when todo is added,", newId);
+ Map argMap = new HashMap<>();
+ argMap.put("status", new String[] { "true" });
+ String jsonResult = todoController.getTodos(argMap);
+ BsonArray docs = parseJsonArray(jsonResult);
+
+ List owner = docs
+ .stream()
+ .map(TodoControllerSpec::getName)
+ .sorted()
+ .collect(Collectors.toList());
+ assertEquals("Should return owner of new todo", "Brian", owner.get(0));
+ }
+
+ @Test
+ public void getTodoByCompany(){
+ Map argMap = new HashMap<>();
+ //Mongo in TodoController is doing a regex search so can just take a Java Reg. Expression
+ //This will search the body starting with an I or an F
+ argMap.put("body", new String[] { "[I,F]" });
+ String jsonResult = todoController.getTodos(argMap);
+ BsonArray docs = parseJsonArray(jsonResult);
+ assertEquals("Should be 3 todos", 3, docs.size());
+ List owner = docs
+ .stream()
+ .map(TodoControllerSpec::getName)
+ .sorted()
+ .collect(Collectors.toList());
+ List expectedName = Arrays.asList("Jamie","Pat","Sam");
+ assertEquals("Names should match", expectedName, owner);
+
+ }
+
+
+
+}