filters = new ArrayList<>(); // start with a blank document
+
+ if (ctx.queryParamMap().containsKey(AGE_KEY)) {
+ int targetAge = ctx.queryParam(AGE_KEY, Integer.class).get();
+ filters.add(eq(AGE_KEY, targetAge));
+ }
+
+ if (ctx.queryParamMap().containsKey(COMPANY_KEY)) {
+ filters.add(regex(COMPANY_KEY, Pattern.quote(ctx.queryParam(COMPANY_KEY)), "i"));
+ }
+
+ if (ctx.queryParamMap().containsKey(ROLE_KEY)) {
+ filters.add(eq(ROLE_KEY, ctx.queryParam(ROLE_KEY)));
+ }
+
+ String sortBy = ctx.queryParam("sortby", "name"); //Sort by sort query param, default is name
+ String sortOrder = ctx.queryParam("sortorder", "asc");
+
+ ctx.json(userCollection.find(filters.isEmpty() ? new Document() : and(filters))
+ .sort(sortOrder.equals("desc") ? Sorts.descending(sortBy) : Sorts.ascending(sortBy))
+ .into(new ArrayList<>()));
+ }
+
+ /**
+ * Get a JSON response with a list of all the users.
+ *
+ * @param ctx a Javalin HTTP context
+ */
+ public void addNewUser(Context ctx) {
+ User newUser = ctx.bodyValidator(User.class)
+ .check(usr -> usr.name != null && usr.name.length() > 0) //Verify that the user has a name that is not blank
+ .check(usr -> usr.email.matches(emailRegex)) // Verify that the provided email is a valid email
+ .check(usr -> usr.age > 0) // Verify that the provided age is > 0
+ .check(usr -> usr.role.matches("^(admin|editor|viewer)$")) // Verify that the role is one of the valid roles
+ .check(usr -> usr.company != null && usr.company.length() > 0) // Verify that the user has a company that is not blank
+ .get();
+
+ // Generate user avatar (you won't need this part for todos)
+ try {
+ newUser.avatar = "https://gravatar.com/avatar/" + md5(newUser.email) + "?d=identicon"; // generate unique md5 code for identicon
+ } catch (NoSuchAlgorithmException ignored) {
+ newUser.avatar = "https://gravatar.com/avatar/?d=mp"; // set to mystery person
+ }
+
+ userCollection.insertOne(newUser);
+ ctx.status(201);
+ ctx.json(ImmutableMap.of("id", newUser._id));
+ }
+
+ /**
+ * Utility function to generate the md5 hash for a given string
+ *
+ * @param str the string to generate a md5 for
+ */
+ @SuppressWarnings("lgtm[java/weak-cryptographic-algorithm]")
+ public String md5(String str) throws NoSuchAlgorithmException {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ byte[] hashInBytes = md.digest(str.toLowerCase().getBytes(StandardCharsets.UTF_8));
+
+ StringBuilder result = new StringBuilder();
+ for (byte b : hashInBytes) {
+ result.append(String.format("%02x", b));
+ }
+ return result.toString();
+ }
+}
diff --git a/server/src/test/java/umm3601/mongotest/MongoSpec.java b/server/src/test/java/umm3601/mongotest/MongoSpec.java
new file mode 100644
index 0000000..110c752
--- /dev/null
+++ b/server/src/test/java/umm3601/mongotest/MongoSpec.java
@@ -0,0 +1,230 @@
+package umm3601.mongotest;
+
+import com.mongodb.MongoClientSettings;
+import com.mongodb.ServerAddress;
+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 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 org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * 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 UserController
+ * do the "right" thing.
+ *
+ * Created by mcphee on 20/2/17.
+ */
+public class MongoSpec {
+
+ private MongoCollection userDocuments;
+
+ static MongoClient mongoClient;
+ static MongoDatabase db;
+
+ @BeforeAll
+ public static void setupDB() {
+ String mongoAddr = System.getenv().getOrDefault("MONGO_ADDR", "localhost");
+
+ mongoClient = MongoClients.create(
+ MongoClientSettings.builder()
+ .applyToClusterSettings(builder ->
+ builder.hosts(Arrays.asList(new ServerAddress(mongoAddr))))
+ .build());
+
+ db = mongoClient.getDatabase("test");
+ }
+
+ @AfterAll
+ public static void teardown() {
+ db.drop();
+ mongoClient.close();
+ }
+
+ @BeforeEach
+ public void clearAndPopulateDB() {
+ userDocuments = db.getCollection("users");
+ userDocuments.drop();
+ List testUsers = new ArrayList<>();
+ testUsers.add(
+ new Document()
+ .append("name", "Chris")
+ .append("age", 25)
+ .append("company", "UMM")
+ .append("email", "chris@this.that"));
+ testUsers.add(
+ new Document()
+ .append("name", "Pat")
+ .append("age", 37)
+ .append("company", "IBM")
+ .append("email", "pat@something.com"));
+ testUsers.add(
+ new Document()
+ .append("name", "Jamie")
+ .append("age", 37)
+ .append("company", "Frogs, Inc.")
+ .append("email", "jamie@frogs.com"));
+
+ userDocuments.insertMany(testUsers);
+ }
+
+ private List intoList(MongoIterable documents) {
+ List users = new ArrayList<>();
+ documents.into(users);
+ return users;
+ }
+
+ private int countUsers(FindIterable documents) {
+ List users = intoList(documents);
+ return users.size();
+ }
+
+ @Test
+ public void shouldBeThreeUsers() {
+ FindIterable documents = userDocuments.find();
+ int numberOfUsers = countUsers(documents);
+ assertEquals(3, numberOfUsers, "Should be 3 total users");
+ }
+
+ @Test
+ public void shouldBeOneChris() {
+ FindIterable documents = userDocuments.find(eq("name", "Chris"));
+ int numberOfUsers = countUsers(documents);
+ assertEquals(1, numberOfUsers, "Should be 1 Chris");
+ }
+
+ @Test
+ public void shouldBeTwoOver25() {
+ FindIterable documents = userDocuments.find(gt("age", 25));
+ int numberOfUsers = countUsers(documents);
+ assertEquals(2, numberOfUsers, "Should be 2 over 25");
+ }
+
+ @Test
+ public void over25SortedByName() {
+ FindIterable documents
+ = userDocuments.find(gt("age", 25))
+ .sort(Sorts.ascending("name"));
+ List docs = intoList(documents);
+ assertEquals(2, docs.size(), "Should be 2");
+ assertEquals("Jamie", docs.get(0).get("name"), "First should be Jamie");
+ assertEquals("Pat", docs.get(1).get("name"), "Second should be Pat");
+ }
+
+ @Test
+ public void over25AndIbmers() {
+ FindIterable documents
+ = userDocuments.find(and(gt("age", 25),
+ eq("company", "IBM")));
+ List docs = intoList(documents);
+ assertEquals(1, docs.size(), "Should be 1");
+ assertEquals("Pat", docs.get(0).get("name"), "First should be Pat");
+ }
+
+ @Test
+ public void justNameAndEmail() {
+ FindIterable documents
+ = userDocuments.find().projection(fields(include("name", "email")));
+ List docs = intoList(documents);
+ assertEquals(3, docs.size(), "Should be 3");
+ assertEquals("Chris", docs.get(0).get("name"), "First should be Chris");
+ assertNotNull(docs.get(0).get("email"), "First should have email");
+ assertNull(docs.get(0).get("company"), "First shouldn't have 'company'");
+ assertNotNull(docs.get(0).get("_id"), "First should have '_id'");
+ }
+
+ @Test
+ public void justNameAndEmailNoId() {
+ FindIterable documents
+ = userDocuments.find()
+ .projection(fields(include("name", "email"), excludeId()));
+ List docs = intoList(documents);
+ assertEquals(3, docs.size(), "Should be 3");
+ assertEquals("Chris", docs.get(0).get("name"), "First should be Chris");
+ assertNotNull(docs.get(0).get("email"), "First should have email");
+ assertNull(docs.get(0).get("company"), "First shouldn't have 'company'");
+ assertNull(docs.get(0).get("_id"), "First should not have '_id'");
+ }
+
+ @Test
+ public void justNameAndEmailNoIdSortedByCompany() {
+ FindIterable documents
+ = userDocuments.find()
+ .sort(Sorts.ascending("company"))
+ .projection(fields(include("name", "email"), excludeId()));
+ List docs = intoList(documents);
+ assertEquals(3, docs.size(), "Should be 3");
+ assertEquals("Jamie", docs.get(0).get("name"), "First should be Jamie");
+ assertNotNull(docs.get(0).get("email"), "First should have email");
+ assertNull(docs.get(0).get("company"), "First shouldn't have 'company'");
+ assertNull(docs.get(0).get("_id"), "First should not have '_id'");
+ }
+
+ @Test
+ public void ageCounts() {
+ AggregateIterable documents
+ = userDocuments.aggregate(
+ Arrays.asList(
+ /*
+ * Groups data by the "age" field, and then counts
+ * the number of documents with each given age.
+ * This creates a new "constructed document" that
+ * has "age" 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(2, docs.size(), "Should be two distinct ages");
+ 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
+ = userDocuments.aggregate(
+ Arrays.asList(
+ Aggregates.group("$company",
+ Accumulators.avg("averageAge", "$age")),
+ Aggregates.sort(Sorts.ascending("_id"))
+ ));
+ List docs = intoList(documents);
+ assertEquals(3, docs.size(), "Should be three companies");
+
+ 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/user/UserControllerSpec.java b/server/src/test/java/umm3601/user/UserControllerSpec.java
new file mode 100644
index 0000000..97e5e1e
--- /dev/null
+++ b/server/src/test/java/umm3601/user/UserControllerSpec.java
@@ -0,0 +1,470 @@
+package umm3601.user;
+
+import static com.mongodb.client.model.Filters.eq;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableMap;
+import com.mockrunner.mock.web.MockHttpServletRequest;
+import com.mockrunner.mock.web.MockHttpServletResponse;
+import com.mongodb.MongoClientSettings;
+import com.mongodb.ServerAddress;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+import org.bson.Document;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.javalin.http.BadRequestResponse;
+import io.javalin.http.Context;
+import io.javalin.http.NotFoundResponse;
+import io.javalin.http.util.ContextUtil;
+import io.javalin.plugin.json.JavalinJson;
+
+
+/**
+* Tests the logic of the UserController
+*
+* @throws IOException
+*/
+public class UserControllerSpec {
+
+ MockHttpServletRequest mockReq = new MockHttpServletRequest();
+ MockHttpServletResponse mockRes = new MockHttpServletResponse();
+
+ private UserController userController;
+
+ private ObjectId samsId;
+
+ static MongoClient mongoClient;
+ static MongoDatabase db;
+
+ static ObjectMapper jsonMapper = new ObjectMapper();
+
+ @BeforeAll
+ public static void setupAll() {
+ String mongoAddr = System.getenv().getOrDefault("MONGO_ADDR", "localhost");
+
+ mongoClient = MongoClients.create(
+ MongoClientSettings.builder()
+ .applyToClusterSettings(builder ->
+ builder.hosts(Arrays.asList(new ServerAddress(mongoAddr))))
+ .build());
+
+ db = mongoClient.getDatabase("test");
+ }
+
+
+ @BeforeEach
+ public void setupEach() throws IOException {
+
+ // Reset our mock request and response objects
+ mockReq.resetAll();
+ mockRes.resetAll();
+
+ // Setup database
+ MongoCollection userDocuments = db.getCollection("users");
+ userDocuments.drop();
+ List testUsers = new ArrayList<>();
+ testUsers.add(
+ new Document()
+ .append("name", "Chris")
+ .append("age", 25)
+ .append("company", "UMM")
+ .append("email", "chris@this.that")
+ .append("role", "admin")
+ .append("avatar", "https://gravatar.com/avatar/8c9616d6cc5de638ea6920fb5d65fc6c?d=identicon"));
+ testUsers.add(
+ new Document()
+ .append("name", "Pat")
+ .append("age", 37)
+ .append("company", "IBM")
+ .append("email", "pat@something.com")
+ .append("role", "editor")
+ .append("avatar", "https://gravatar.com/avatar/b42a11826c3bde672bce7e06ad729d44?d=identicon"));
+ testUsers.add(
+ new Document()
+ .append("name", "Jamie")
+ .append("age", 37)
+ .append("company", "OHMNET")
+ .append("email", "jamie@frogs.com")
+ .append("role", "viewer")
+ .append("avatar", "https://gravatar.com/avatar/d4a6c71dd9470ad4cf58f78c100258bf?d=identicon"));
+
+ samsId = new ObjectId();
+ Document sam =
+ new Document()
+ .append("_id", samsId)
+ .append("name", "Sam")
+ .append("age", 45)
+ .append("company", "OHMNET")
+ .append("email", "sam@frogs.com")
+ .append("role", "viewer")
+ .append("avatar", "https://gravatar.com/avatar/08b7610b558a4cbbd20ae99072801f4d?d=identicon");
+
+
+ userDocuments.insertMany(testUsers);
+ userDocuments.insertOne(sam);
+
+ userController = new UserController(db);
+ }
+
+ @AfterAll
+ public static void teardown() {
+ db.drop();
+ mongoClient.close();
+ }
+
+ @Test
+ public void GetAllUsers() throws IOException {
+
+ // Create our fake Javalin context
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users");
+ userController.getUsers(ctx);
+
+
+ assertEquals(200, mockRes.getStatus());
+
+ String result = ctx.resultString();
+ assertEquals(db.getCollection("users").countDocuments(), JavalinJson.fromJson(result, User[].class).length);
+ }
+
+ @Test
+ public void GetUsersByAge() throws IOException {
+
+ // Set the query string to test with
+ mockReq.setQueryString("age=37");
+
+ // Create our fake Javalin context
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users");
+
+ userController.getUsers(ctx);
+
+ assertEquals(200, mockRes.getStatus()); // The response status should be 200
+
+ String result = ctx.resultString();
+ User[] resultUsers = JavalinJson.fromJson(result, User[].class);
+
+ assertEquals(2, resultUsers.length); // There should be two users returned
+ for (User user : resultUsers) {
+ assertEquals(37, user.age); // Every user should be age 37
+ }
+ }
+
+ /**
+ * Test that if the user sends a request with an illegal value in
+ * the age field (i.e., something that can't be parsed to a number)
+ * we get a reasonable error code back.
+ */
+ @Test
+ public void GetUsersWithIllegalAge() {
+
+ mockReq.setQueryString("age=abc");
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users");
+
+ // This should now throw a `BadRequestResponse` exception because
+ // our request has an age that can't be parsed to a number.
+ assertThrows(BadRequestResponse.class, () -> {
+ userController.getUsers(ctx);
+ });
+ }
+
+ @Test
+ public void GetUsersByCompany() throws IOException {
+
+ mockReq.setQueryString("company=OHMNET");
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users");
+ userController.getUsers(ctx);
+
+ assertEquals(200, mockRes.getStatus());
+ String result = ctx.resultString();
+
+ User[] resultUsers = JavalinJson.fromJson(result, User[].class);
+
+ assertEquals(2, resultUsers.length); // There should be two users returned
+ for (User user : resultUsers) {
+ assertEquals("OHMNET", user.company);
+ }
+ }
+
+ @Test
+ public void GetUsersByRole() throws IOException {
+
+ mockReq.setQueryString("role=viewer");
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users");
+ userController.getUsers(ctx);
+
+ assertEquals(200, mockRes.getStatus());
+ String result = ctx.resultString();
+ for (User user : JavalinJson.fromJson(result, User[].class)) {
+ assertEquals("viewer", user.role);
+ }
+ }
+
+ @Test
+ public void GetUsersByCompanyAndAge() throws IOException {
+
+ mockReq.setQueryString("company=OHMNET&age=37");
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users");
+ userController.getUsers(ctx);
+
+ assertEquals(200, mockRes.getStatus());
+ String result = ctx.resultString();
+ User[] resultUsers = JavalinJson.fromJson(result, User[].class);
+
+ assertEquals(1, resultUsers.length); // There should be one user returned
+ for (User user : resultUsers) {
+ assertEquals("OHMNET", user.company);
+ assertEquals(37, user.age);
+ }
+ }
+
+ @Test
+ public void GetUserWithExistentId() throws IOException {
+
+ String testID = samsId.toHexString();
+
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users/:id", ImmutableMap.of("id", testID));
+ userController.getUser(ctx);
+
+ assertEquals(200, mockRes.getStatus());
+
+ String result = ctx.resultString();
+ User resultUser = JavalinJson.fromJson(result, User.class);
+
+ assertEquals(resultUser._id, samsId.toHexString());
+ assertEquals(resultUser.name, "Sam");
+ }
+
+ @Test
+ public void GetUserWithBadId() throws IOException {
+
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users/:id", ImmutableMap.of("id", "bad"));
+
+ assertThrows(BadRequestResponse.class, () -> {
+ userController.getUser(ctx);
+ });
+ }
+
+ @Test
+ public void GetUserWithNonexistentId() throws IOException {
+
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users/:id", ImmutableMap.of("id", "58af3a600343927e48e87335"));
+
+ assertThrows(NotFoundResponse.class, () -> {
+ userController.getUser(ctx);
+ });
+ }
+
+ @Test
+ public void AddUser() throws IOException {
+
+ String testNewUser = "{"
+ + "\"name\": \"Test User\","
+ + "\"age\": 25,"
+ + "\"company\": \"testers\","
+ + "\"email\": \"test@example.com\","
+ + "\"role\": \"viewer\""
+ + "}";
+
+ mockReq.setBodyContent(testNewUser);
+ mockReq.setMethod("POST");
+
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users");
+
+ userController.addNewUser(ctx);
+
+ assertEquals(201, mockRes.getStatus());
+
+ String result = ctx.resultString();
+ String id = jsonMapper.readValue(result, ObjectNode.class).get("id").asText();
+ assertNotEquals("", id);
+ System.out.println(id);
+
+ assertEquals(1, db.getCollection("users").countDocuments(eq("_id", new ObjectId(id))));
+
+ //verify user was added to the database and the correct ID
+ Document addedUser = db.getCollection("users").find(eq("_id", new ObjectId(id))).first();
+ assertNotNull(addedUser);
+ assertEquals("Test User", addedUser.getString("name"));
+ assertEquals(25, addedUser.getInteger("age"));
+ assertEquals("testers", addedUser.getString("company"));
+ assertEquals("test@example.com", addedUser.getString("email"));
+ assertEquals("viewer", addedUser.getString("role"));
+ assertTrue(addedUser.containsKey("avatar"));
+ }
+
+ @Test
+ public void AddInvalidEmailUser() throws IOException {
+ String testNewUser = "{"
+ + "\"name\": \"Test User\","
+ + "\"age\": 25,"
+ + "\"company\": \"testers\","
+ + "\"email\": \"invalidemail\","
+ + "\"role\": \"viewer\""
+ + "}";
+ mockReq.setBodyContent(testNewUser);
+ mockReq.setMethod("POST");
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users");
+
+ assertThrows(BadRequestResponse.class, () -> {
+ userController.addNewUser(ctx);
+ });
+ }
+
+ @Test
+ public void AddInvalidAgeUser() throws IOException {
+ String testNewUser = "{"
+ + "\"name\": \"Test User\","
+ + "\"age\": \"notanumber\","
+ + "\"company\": \"testers\","
+ + "\"email\": \"test@example.com\","
+ + "\"role\": \"viewer\""
+ + "}";
+ mockReq.setBodyContent(testNewUser);
+ mockReq.setMethod("POST");
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users");
+
+ assertThrows(BadRequestResponse.class, () -> {
+ userController.addNewUser(ctx);
+ });
+ }
+
+ public void Add0AgeUser() throws IOException {
+ String testNewUser = "{"
+ + "\"name\": \"Test User\","
+ + "\"age\": 0,"
+ + "\"company\": \"testers\","
+ + "\"email\": \"test@example.com\","
+ + "\"role\": \"viewer\""
+ + "}";
+ mockReq.setBodyContent(testNewUser);
+ mockReq.setMethod("POST");
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users");
+
+ assertThrows(BadRequestResponse.class, () -> {
+ userController.addNewUser(ctx);
+ });
+ }
+
+ @Test
+ public void AddNullNameUser() throws IOException {
+ String testNewUser = "{"
+ + "\"age\": 25,"
+ + "\"company\": \"testers\","
+ + "\"email\": \"test@example.com\","
+ + "\"role\": \"viewer\""
+ + "}";
+ mockReq.setBodyContent(testNewUser);
+ mockReq.setMethod("POST");
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users");
+
+ assertThrows(BadRequestResponse.class, () -> {
+ userController.addNewUser(ctx);
+ });
+ }
+
+ @Test
+ public void AddInvalidNameUser() throws IOException {
+ String testNewUser = "{"
+ + "\"name\": \"\","
+ + "\"age\": 25,"
+ + "\"company\": \"testers\","
+ + "\"email\": \"test@example.com\","
+ + "\"role\": \"viewer\""
+ + "}";
+ mockReq.setBodyContent(testNewUser);
+ mockReq.setMethod("POST");
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users");
+
+ assertThrows(BadRequestResponse.class, () -> {
+ userController.addNewUser(ctx);
+ });
+ }
+
+ @Test
+ public void AddInvalidRoleUser() throws IOException {
+ String testNewUser = "{"
+ + "\"name\": \"Test User\","
+ + "\"age\": 25,"
+ + "\"company\": \"testers\","
+ + "\"email\": \"test@example.com\","
+ + "\"role\": \"invalidrole\""
+ + "}";
+ mockReq.setBodyContent(testNewUser);
+ mockReq.setMethod("POST");
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users");
+
+ assertThrows(BadRequestResponse.class, () -> {
+ userController.addNewUser(ctx);
+ });
+ }
+
+ public void AddNullCompanyUser() throws IOException {
+ String testNewUser = "{"
+ + "\"name\": \"Test User\","
+ + "\"age\": 25,"
+ + "\"email\": \"test@example.com\","
+ + "\"role\": \"viewer\""
+ + "}";
+ mockReq.setBodyContent(testNewUser);
+ mockReq.setMethod("POST");
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users");
+
+ assertThrows(BadRequestResponse.class, () -> {
+ userController.addNewUser(ctx);
+ });
+ }
+
+ @Test
+ public void AddInvalidCompanyUser() throws IOException {
+ String testNewUser = "{"
+ + "\"name\": \"\","
+ + "\"age\": 25,"
+ + "\"company\": \"\","
+ + "\"email\": \"test@example.com\","
+ + "\"role\": \"viewer\""
+ + "}";
+ mockReq.setBodyContent(testNewUser);
+ mockReq.setMethod("POST");
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users");
+
+ assertThrows(BadRequestResponse.class, () -> {
+ userController.addNewUser(ctx);
+ });
+ }
+
+ @Test
+ public void DeleteUser() throws IOException {
+
+ String testID = samsId.toHexString();
+
+ // User exists before deletion
+ assertEquals(1, db.getCollection("users").countDocuments(eq("_id", new ObjectId(testID))));
+
+ Context ctx = ContextUtil.init(mockReq, mockRes, "api/users/:id", ImmutableMap.of("id", testID));
+ userController.deleteUser(ctx);
+
+ assertEquals(200, mockRes.getStatus());
+
+ // User is no longer in the database
+ assertEquals(0, db.getCollection("users").countDocuments(eq("_id", new ObjectId(testID))));
+ }
+
+}
diff --git a/server/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/server/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d
--- /dev/null
+++ b/server/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/setupdroplet.sh b/setupdroplet.sh
new file mode 100755
index 0000000..ab48476
--- /dev/null
+++ b/setupdroplet.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+if [[ ! -e /swapfile ]]; then
+ echo "/swapfile does not exist, setting up swap"
+ # Set up swap space
+ fallocate -l 3G /swapfile
+ chmod 600 /swapfile
+ mkswap /swapfile
+ swapon /swapfile
+ echo '/swapfile none swap sw 0 0' | tee -a /etc/fstab
+ echo 'vm.swappiness=10' >> /etc/sysctl.conf
+else
+ echo "/swapfile already exists, skipping swap setup"
+fi
+
+ip="$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address)"
+domain="${ip}.nip.io"
+
+echo
+echo "Setting APP_HOST to ${domain}"
+echo "APP_HOST=${domain}" > .env
+echo
+echo "Your site will be served over HTTPS automatically using Let's Encrypt or ZeroSSL."
+echo "By continuing, you agree to the Let's Encrypt Subscriber Agreement at:"
+echo "https://letsencrypt.org/documents/2017.11.15-LE-SA-v1.2.pdf"
+echo "as well as the ZeroSSL Terms of Service at:"
+echo "https://zerossl.com/terms/"
+echo
+echo "Please enter your email address to signify agreement and to be notified"
+echo "in case of issues."
+read -p "Email address: " email
+echo
+if [ -z "$email" ]; then
+ echo "No email entered; not setting APP_CADDY_GLOBAL_OPTIONS"
+else
+ echo "Setting APP_CADDY_GLOBAL_OPTIONS to \"email ${email}\""
+ echo "APP_CADDY_GLOBAL_OPTIONS=\"email ${email}\"" >> .env
+fi
+echo
+echo "Your server is set up."
+echo "Once you start it with 'docker-compose up -d' it will be available at:"
+echo "https://${domain}"
+echo "You should copy this down somewhere."