diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index f60bb7b..a45927a 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html @@ -3,6 +3,7 @@ homeHome account_boxUsers + account_boxTodos diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 030aab7..b11297f 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -10,12 +10,19 @@ import {HomeComponent} from './home/home.component'; import {UserComponent} from './users/user.component'; import {UserListComponent} from './users/user-list.component'; import {UserListService} from './users/user-list.service'; + +import {TodoComponent} from './todos/todo.component'; +import {TodoListComponent} from './todos/todo-list.component'; +import {TodoListService} from './todos/todo-list.service'; + import {Routing} from './app.routes'; import {APP_BASE_HREF} from '@angular/common'; import {CustomModule} from './custom.module'; import {AddUserComponent} from './users/add-user.component'; +import {AddTodoComponent} from './todos/add-todo.component'; + @NgModule({ imports: [ @@ -29,15 +36,24 @@ import {AddUserComponent} from './users/add-user.component'; HomeComponent, UserListComponent, UserComponent, - AddUserComponent + AddUserComponent, + + TodoListComponent, + TodoComponent, + AddTodoComponent ], providers: [ UserListService, {provide: APP_BASE_HREF, useValue: '/'}, + {provide: MATERIAL_COMPATIBILITY_MODE, useValue: true}, + + TodoListService, + {provide: APP_BASE_HREF, useValue: '/'}, {provide: MATERIAL_COMPATIBILITY_MODE, useValue: true} ], entryComponents: [ AddUserComponent, + AddTodoComponent ], bootstrap: [AppComponent] }) diff --git a/client/src/app/app.routes.ts b/client/src/app/app.routes.ts index 75fc97a..d101b86 100644 --- a/client/src/app/app.routes.ts +++ b/client/src/app/app.routes.ts @@ -3,11 +3,13 @@ import {ModuleWithProviders} from '@angular/core'; import {Routes, RouterModule} from '@angular/router'; import {HomeComponent} from './home/home.component'; import {UserListComponent} from './users/user-list.component'; +import{TodoListComponent} from './todos/todo-list.component'; // Route Configuration export const routes: Routes = [ {path: '', component: HomeComponent}, - {path: 'users', component: UserListComponent} + {path: 'users', component: UserListComponent}, + {path: 'todos', component: TodoListComponent} ]; export const Routing: ModuleWithProviders = RouterModule.forRoot(routes); diff --git a/client/src/app/todos/add-todo.component.html b/client/src/app/todos/add-todo.component.html index 9de061d..958276c 100644 --- a/client/src/app/todos/add-todo.component.html +++ b/client/src/app/todos/add-todo.component.html @@ -1,24 +1,24 @@
- 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); + + } + + + +}