From e78884737a92069823f29771ae564aa89621f7ab Mon Sep 17 00:00:00 2001 From: Jon Sundin Date: Thu, 25 Jan 2024 11:06:38 -0800 Subject: [PATCH] Initial commit --- .gitignore | 9 ++ pom.xml | 76 +++++++++ readme.md | 95 +++++++++++ .../Controller/SocialMediaController.java | 33 ++++ src/main/java/DAO/todo.md | 5 + src/main/java/Main.java | 14 ++ src/main/java/Model/Account.java | 118 ++++++++++++++ src/main/java/Model/Message.java | 142 ++++++++++++++++ src/main/java/Service/todo.md | 2 + src/main/java/Util/ConnectionUtil.java | 74 +++++++++ src/main/resources/SocialMedia.sql | 17 ++ src/test/java/CreateMessageTest.java | 152 ++++++++++++++++++ .../java/DeleteMessageByMessageIdTest.java | 91 +++++++++++ .../java/RetrieveAllMessagesForUserTest.java | 92 +++++++++++ src/test/java/RetrieveAllMessagesTest.java | 113 +++++++++++++ .../java/RetrieveMessageByMessageIdTest.java | 90 +++++++++++ src/test/java/UpdateMessageTextTest.java | 142 ++++++++++++++++ src/test/java/UserLoginTest.java | 122 ++++++++++++++ src/test/java/UserRegistrationTest.java | 144 +++++++++++++++++ 19 files changed, 1531 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 readme.md create mode 100644 src/main/java/Controller/SocialMediaController.java create mode 100644 src/main/java/DAO/todo.md create mode 100644 src/main/java/Main.java create mode 100644 src/main/java/Model/Account.java create mode 100644 src/main/java/Model/Message.java create mode 100644 src/main/java/Service/todo.md create mode 100644 src/main/java/Util/ConnectionUtil.java create mode 100644 src/main/resources/SocialMedia.sql create mode 100644 src/test/java/CreateMessageTest.java create mode 100644 src/test/java/DeleteMessageByMessageIdTest.java create mode 100644 src/test/java/RetrieveAllMessagesForUserTest.java create mode 100644 src/test/java/RetrieveAllMessagesTest.java create mode 100644 src/test/java/RetrieveMessageByMessageIdTest.java create mode 100644 src/test/java/UpdateMessageTextTest.java create mode 100644 src/test/java/UserLoginTest.java create mode 100644 src/test/java/UserRegistrationTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d0fcbc5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +target/ +h2/ +history_log.txt +testCases_log.txt +restructure_tmp/ +restructured_labs/ +.revature/tmp +.vscode +.idea/ \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a230087 --- /dev/null +++ b/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + org.revature + Challenges + 1.1 + + + 11 + 11 + + + + + + junit + junit + 4.13.2 + + + + io.javalin + javalin + 5.0.1 + + + org.slf4j + slf4j-simple + 1.7.36 + + + + com.fasterxml.jackson.core + jackson-databind + 2.14.0-rc1 + + + + com.h2database + h2 + 2.1.214 + + + + + org.mockito + mockito-core + 4.9.0 + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M7 + + + org.apache.maven.surefire + surefire-junit47 + 3.0.0-M7 + + + + + + + \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..832002e --- /dev/null +++ b/readme.md @@ -0,0 +1,95 @@ +# Project: Social media blog API + +## Background + +When building a full-stack application, we're typically concerned with both a front end, that displays information to the user and takes in input, and a backend, that manages persisted information. + +This project will be a backend for a hypothetical social media app, where we must manage our users’ accounts as well as any messages that they submit to the application. The application will function as a micro-blogging or messaging app. In our hypothetical application, any user should be able to see all of the messages posted to the site, or they can see the messages posted by a particular user. In either case, we require a backend which is able to deliver the data needed to display this information as well as process actions like logins, registrations, message creations, message updates, and message deletions. + +## Database Tables + +These will be provided in a sql script, and a ConnectionUtil class that will run the sql script is provided: + +### Account +``` +account_id integer primary key auto_increment, +username varchar(255) unique, +password varchar(255) +``` + +### Message +``` +message_id integer primary key auto_increment, +posted_by integer, +message_text varchar(255), +time_posted_epoch long, +foreign key (posted_by) references Account(account_id) +``` + +# Requirements + +## 1: Our API should be able to process new User registrations. + +As a user, I should be able to create a new Account on the endpoint POST localhost:8080/register. The body will contain a representation of a JSON Account, but will not contain an account_id. + +- The registration will be successful if and only if the username is not blank, the password is at least 4 characters long, and an Account with that username does not already exist. If all these conditions are met, the response body should contain a JSON of the Account, including its account_id. The response status should be 200 OK, which is the default. The new account should be persisted to the database. +- If the registration is not successful, the response status should be 400. (Client error) + +## 2: Our API should be able to process User logins. + +As a user, I should be able to verify my login on the endpoint POST localhost:8080/login. The request body will contain a JSON representation of an Account, not containing an account_id. In the future, this action may generate a Session token to allow the user to securely use the site. We will not worry about this for now. + +- The login will be successful if and only if the username and password provided in the request body JSON match a real account existing on the database. If successful, the response body should contain a JSON of the account in the response body, including its account_id. The response status should be 200 OK, which is the default. +- If the login is not successful, the response status should be 401. (Unauthorized) + + +## 3: Our API should be able to process the creation of new messages. + +As a user, I should be able to submit a new post on the endpoint POST localhost:8080/messages. The request body will contain a JSON representation of a message, which should be persisted to the database, but will not contain a message_id. + +- The creation of the message will be successful if and only if the message_text is not blank, is not over 255 characters, and posted_by refers to a real, existing user. If successful, the response body should contain a JSON of the message, including its message_id. The response status should be 200, which is the default. The new message should be persisted to the database. +- If the creation of the message is not successful, the response status should be 400. (Client error) + +## 4: Our API should be able to retrieve all messages. + +As a user, I should be able to submit a GET request on the endpoint GET localhost:8080/messages. + +- The response body should contain a JSON representation of a list containing all messages retrieved from the database. It is expected for the list to simply be empty if there are no messages. The response status should always be 200, which is the default. + +## 5: Our API should be able to retrieve a message by its ID. + +As a user, I should be able to submit a GET request on the endpoint GET localhost:8080/messages/{message_id}. + +- The response body should contain a JSON representation of the message identified by the message_id. It is expected for the response body to simply be empty if there is no such message. The response status should always be 200, which is the default. + +## 6: Our API should be able to delete a message identified by a message ID. + +As a User, I should be able to submit a DELETE request on the endpoint DELETE localhost:8080/messages/{message_id}. + +- The deletion of an existing message should remove an existing message from the database. If the message existed, the response body should contain the now-deleted message. The response status should be 200, which is the default. +- If the message did not exist, the response status should be 200, but the response body should be empty. This is because the DELETE verb is intended to be idempotent, ie, multiple calls to the DELETE endpoint should respond with the same type of response. + +## 7: Our API should be able to update a message text identified by a message ID. + +As a user, I should be able to submit a PATCH request on the endpoint PATCH localhost:8080/messages/{message_id}. The request body should contain a new message_text values to replace the message identified by message_id. The request body can not be guaranteed to contain any other information. + +- The update of a message should be successful if and only if the message id already exists and the new message_text is not blank and is not over 255 characters. If the update is successful, the response body should contain the full updated message (including message_id, posted_by, message_text, and time_posted_epoch), and the response status should be 200, which is the default. The message existing on the database should have the updated message_text. +- If the update of the message is not successful for any reason, the response status should be 400. (Client error) + +## 8: Our API should be able to retrieve all messages written by a particular user. + +As a user, I should be able to submit a GET request on the endpoint GET localhost:8080/accounts/{account_id}/messages. + +- The response body should contain a JSON representation of a list containing all messages posted by a particular user, which is retrieved from the database. It is expected for the list to simply be empty if there are no messages. The response status should always be 200, which is the default. + +# Further guidance + +Some classes are already complete and SHOULD NOT BE CHANGED - Integration tests, Model classes for Account and Message, a ConnectionUtil class. Changing any of these classes will likely result in the test cases being impossible to pass. + +The .sql script found in src/main/resources is already complete and SHOULD NOT BE CHANGED. Changing this file will likely result in the test cases being impossible to pass. + +You SHOULD be changing the SocialMediaController class to add endpoints to the StartAPI method. A main method in Main.java is also provided to allow you to run the entire application and manually play or test with the app. Changing that class will not affect the test cases at all. You could use it to perform any manual unit testing on your other classes. + +You SHOULD be creating and designing DAO and Service class to allow you to complete the project. In theory, you could design the project however you like, so long as the functionality works and you are somehow persisting data to the database - but a 3-layer architecture is a robust design pattern and following help you in the long run. You can refer to prior mini-projects and course material for help on designing your application in this way. + +# Good luck! diff --git a/src/main/java/Controller/SocialMediaController.java b/src/main/java/Controller/SocialMediaController.java new file mode 100644 index 0000000..5094705 --- /dev/null +++ b/src/main/java/Controller/SocialMediaController.java @@ -0,0 +1,33 @@ +package Controller; + +import io.javalin.Javalin; +import io.javalin.http.Context; + +/** + * TODO: You will need to write your own endpoints and handlers for your controller. The endpoints you will need can be + * found in readme.md as well as the test cases. You should + * refer to prior mini-project labs and lecture materials for guidance on how a controller may be built. + */ +public class SocialMediaController { + /** + * In order for the test cases to work, you will need to write the endpoints in the startAPI() method, as the test + * suite must receive a Javalin object from this method. + * @return a Javalin app object which defines the behavior of the Javalin controller. + */ + public Javalin startAPI() { + Javalin app = Javalin.create(); + app.get("example-endpoint", this::exampleHandler); + + return app; + } + + /** + * This is an example handler for an example endpoint. + * @param context The Javalin Context object manages information about both the HTTP request and response. + */ + private void exampleHandler(Context context) { + context.json("sample text"); + } + + +} \ No newline at end of file diff --git a/src/main/java/DAO/todo.md b/src/main/java/DAO/todo.md new file mode 100644 index 0000000..8427033 --- /dev/null +++ b/src/main/java/DAO/todo.md @@ -0,0 +1,5 @@ +You will need to design and create your own DAO classes from scratch. +You should refer to prior mini-project lab examples and course material for guidance. + +Please refrain from using a 'try-with-resources' block when connecting to your database. +The ConnectionUtil provided uses a singleton, and using a try-with-resources will cause issues in the tests. diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 0000000..de2c86b --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,14 @@ +import Controller.SocialMediaController; +import io.javalin.Javalin; + +/** + * This class is provided with a main method to allow you to manually run and test your application. This class will not + * affect your program in any way and you may write whatever code you like here. + */ +public class Main { + public static void main(String[] args) { + SocialMediaController controller = new SocialMediaController(); + Javalin app = controller.startAPI(); + app.start(8080); + } +} diff --git a/src/main/java/Model/Account.java b/src/main/java/Model/Account.java new file mode 100644 index 0000000..63d60bb --- /dev/null +++ b/src/main/java/Model/Account.java @@ -0,0 +1,118 @@ +package Model; + +/** + * This is a class that models an Account. + * + * DO NOT CHANGE ANYTHING IN THIS CLASS + * + */ +public class Account { + /** + * An id for this Account which will be automatically generated by the database. + */ + public int account_id; + /** + * A username for this Account (must be unique and not blank) + */ + public String username; + /** + * A password for this account (must be over 4 characters) + */ + public String password; + /** + * A default, no-args constructor, as well as correctly formatted getters and setters, are needed for + * Jackson Objectmapper to work. + */ + public Account(){ + + } + /** + * When posting a new Account, the id can be generated by the database. In that case, a constructor without + * account_id is needed. + * @param username + * @param password + */ + public Account(String username, String password){ + this.username = username; + this.password = password; + } + /** + * Whem retrieving an Account from the database, all fields will be needed. In that case, a constructor with all + * fields is needed. + * @param account_id + * @param username + * @param password + */ + public Account(int account_id, String username, String password) { + this.account_id = account_id; + this.username = username; + this.password = password; + } + /** + * Properly named getters and setters are necessary for Jackson ObjectMapper to work. You may use them as well. + * @return account_id + */ + public int getAccount_id() { + return account_id; + } + /** + * Properly named getters and setters are necessary for Jackson ObjectMapper to work. You may use them as well. + * @param account_id + */ + public void setAccount_id(int account_id) { + this.account_id = account_id; + } + /** + * Properly named getters and setters are necessary for Jackson ObjectMapper to work. You may use them as well. + * @return username + */ + public String getUsername() { + return username; + } + /** + * Properly named getters and setters are necessary for Jackson ObjectMapper to work. You may use them as well. + * @param username + */ + public void setUsername(String username) { + this.username = username; + } + /** + * Properly named getters and setters are necessary for Jackson ObjectMapper to work. You may use them as well. + * @return password + */ + public String getPassword() { + return password; + } + /** + * Properly named getters and setters are necessary for Jackson ObjectMapper to work. You may use them as well. + * @param password + */ + public void setPassword(String password) { + this.password = password; + } + /** + * Overriding the default equals() method adds functionality to tell when two objects are identical, allowing + * Assert.assertEquals and List.contains to function. + * @param o the other object. + * @return true if o is equal to this object. + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Account account = (Account) o; + return account_id == account.account_id && username.equals(account.username) && password.equals(account.password); + } + /** + * Overriding the default toString() method allows for easy debugging. + * @return a String representation of this class. + */ + @Override + public String toString() { + return "Account{" + + "account_id=" + account_id + + ", username='" + username + '\'' + + ", password='" + password + '\'' + + '}'; + } +} diff --git a/src/main/java/Model/Message.java b/src/main/java/Model/Message.java new file mode 100644 index 0000000..2c36663 --- /dev/null +++ b/src/main/java/Model/Message.java @@ -0,0 +1,142 @@ +package Model; +/** + * This is a class that models a Message. + * + * DO NOT CHANGE ANYTHING IN THIS CLASS! + * + */ +public class Message { + /** + * An id for this message which will be automatically generated by the database. + */ + public int message_id; + /** + * The id for the user who has posted this message. We will assume that this is provided by the front-end of this + * application. + */ + public int posted_by; + /** + * The text for this message- eg "this is my first post!". Must be not blank and under 255 characters + */ + public String message_text; + /** + * The epoch time when this tweet was posted (number of seconds since Jan 1, 1970). Longs are large enough + * to store this number. We will assume that this number is provided by the front-end of this application. + */ + public long time_posted_epoch; + /** + * A default, no-args constructor, as well as correctly formatted getters and setters, are needed for + * Jackson Objectmapper to work. + */ + public Message(){ + } + /** + * When posting a new message, the id can be generated by the database. In that case, a constructor without + * message_id is needed. + * @param posted_by + * @param message_text + * @param time_posted_epoch + */ + public Message(int posted_by, String message_text, long time_posted_epoch) { + this.posted_by = posted_by; + this.message_text = message_text; + this.time_posted_epoch = time_posted_epoch; + } + /** + * Whem retrieving a message from the database, all fields will be needed. In that case, a constructor with all + * fields is needed. + * @param message_id + * @param posted_by + * @param message_text + * @param time_posted_epoch + */ + public Message(int message_id, int posted_by, String message_text, long time_posted_epoch) { + this.message_id = message_id; + this.posted_by = posted_by; + this.message_text = message_text; + this.time_posted_epoch = time_posted_epoch; + } + /** + * Properly named getters and setters are necessary for Jackson ObjectMapper to work. You may use them as well. + * @return message_id + */ + public int getMessage_id() { + return message_id; + } + /** + * Properly named getters and setters are necessary for Jackson ObjectMapper to work. You may use them as well. + * @param message_id + */ + public void setMessage_id(int message_id) { + this.message_id = message_id; + } + /** + * Properly named getters and setters are necessary for Jackson ObjectMapper to work. You may use them as well. + * @return posted_by + */ + public int getPosted_by() { + return posted_by; + } + /** + * Properly named getters and setters are necessary for Jackson ObjectMapper to work. You may use them as well. + * @param posted_by + */ + public void setPosted_by(int posted_by) { + this.posted_by = posted_by; + } + /** + * Properly named getters and setters are necessary for Jackson ObjectMapper to work. You may use them as well. + * @return message_text + */ + public String getMessage_text() { + return message_text; + } + /** + * Properly named getters and setters are necessary for Jackson ObjectMapper to work. You may use them as well. + * @param message_text + */ + public void setMessage_text(String message_text) { + this.message_text = message_text; + } + /** + * Properly named getters and setters are necessary for Jackson ObjectMapper to work. You may use them as well. + * @return time_posted_epoch + */ + public long getTime_posted_epoch() { + return time_posted_epoch; + } + /** + * Properly named getters and setters are necessary for Jackson ObjectMapper to work. You may use them as well. + * @param time_posted_epoch + */ + public void setTime_posted_epoch(long time_posted_epoch) { + this.time_posted_epoch = time_posted_epoch; + } + /** + * Overriding the default equals() method adds functionality to tell when two objects are identical, allowing + * Assert.assertEquals and List.contains to function. + * @param o the other object. + * @return true if o is equal to this object. + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Message message = (Message) o; + return message_id == message.message_id && posted_by == message.posted_by + && time_posted_epoch == message.time_posted_epoch && message_text.equals(message.message_text); + } + /** + * Overriding the default toString() method allows for easy debugging. + * @return a String representation of this class. + */ + @Override + public String toString() { + return "Message{" + + "message_id=" + message_id + + ", posted_by=" + posted_by + + ", message_text='" + message_text + '\'' + + ", time_posted_epoch=" + time_posted_epoch + + '}'; + } +} diff --git a/src/main/java/Service/todo.md b/src/main/java/Service/todo.md new file mode 100644 index 0000000..e710989 --- /dev/null +++ b/src/main/java/Service/todo.md @@ -0,0 +1,2 @@ +You will need to design and create your own Service classes from scratch. +You should refer to prior mini-project lab examples and course material for guidance. \ No newline at end of file diff --git a/src/main/java/Util/ConnectionUtil.java b/src/main/java/Util/ConnectionUtil.java new file mode 100644 index 0000000..d6af6b4 --- /dev/null +++ b/src/main/java/Util/ConnectionUtil.java @@ -0,0 +1,74 @@ +package Util; + +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.sql.Connection; +import java.sql.SQLException; + +import org.h2.jdbcx.JdbcDataSource; +import org.h2.tools.RunScript; + +/** + * The ConnectionUtil class will be utilized to create an active connection to + * our database. This class utilizes the singleton design pattern. We will be + * utilizing an in-memory called h2database for the sql demos. + * + * DO NOT CHANGE ANYTHING IN THIS CLASS + */ +public class ConnectionUtil { + + /** + * url will represent our connection string. Since this is an in-memory db, we + * will represent a file location to store the data + */ + private static String url = "jdbc:h2:./h2/db;"; + /** + * Default username for connecting to h2 + */ + private static String username = "sa"; + /** + * Default password for connecting to h2 + */ + private static String password = "sa"; + + /** + * DataSource for pooling. Pooling enables the creation of multiple connections when connections are closed. + */ + private static JdbcDataSource pool = new JdbcDataSource(); + + /** + * static initialization block to establish credentials for DataSoure Pool + */ + static { + pool.setURL(url); + pool.setUser(username); + pool.setPassword(password); + } + + /** + * @return an active connection to the database + */ + public static Connection getConnection() { + try { + return pool.getConnection(); + } catch (SQLException e) { + e.printStackTrace(); + } + + return null; + } + + /** + * For the purpose of testing, we will need to drop and recreate our database + * tables to keep it consistent across all tests. The method will read the sql + * file in resources. This will be performed before every test. + */ + public static void resetTestDatabase() { + try { + FileReader sqlReader = new FileReader("src/main/resources/SocialMedia.sql"); + RunScript.execute(getConnection(), sqlReader); + } catch (SQLException | FileNotFoundException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/resources/SocialMedia.sql b/src/main/resources/SocialMedia.sql new file mode 100644 index 0000000..4eb1bc5 --- /dev/null +++ b/src/main/resources/SocialMedia.sql @@ -0,0 +1,17 @@ +drop table if exists message; +drop table if exists account; +create table account ( + account_id int primary key auto_increment, + username varchar(255) unique, + password varchar(255) +); +create table message ( + message_id int primary key auto_increment, + posted_by int, + message_text varchar(255), + time_posted_epoch bigint, + foreign key (posted_by) references account(account_id) +); + +insert into account (username, password) values ('testuser1', 'password'); +insert into message (posted_by, message_text, time_posted_epoch) values (1,'test message 1',1669947792); diff --git a/src/test/java/CreateMessageTest.java b/src/test/java/CreateMessageTest.java new file mode 100644 index 0000000..4352891 --- /dev/null +++ b/src/test/java/CreateMessageTest.java @@ -0,0 +1,152 @@ +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import Controller.SocialMediaController; +import Model.Message; +import Util.ConnectionUtil; +import io.javalin.Javalin; + +public class CreateMessageTest { + SocialMediaController socialMediaController; + HttpClient webClient; + ObjectMapper objectMapper; + Javalin app; + + /** + * Before every test, reset the database, restart the Javalin app, and create a new webClient and ObjectMapper + * for interacting locally on the web. + * @throws InterruptedException + */ + @Before + public void setUp() throws InterruptedException { + ConnectionUtil.resetTestDatabase(); + socialMediaController = new SocialMediaController(); + app = socialMediaController.startAPI(); + webClient = HttpClient.newHttpClient(); + objectMapper = new ObjectMapper(); + app.start(8080); + Thread.sleep(1000); + } + + @After + public void tearDown() { + app.stop(); + } + + + + + /** + * Sending an http request to POST localhost:8080/messages with valid message credentials + * + * Expected Response: + * Status Code: 200 + * Response Body: JSON representation of message object + */ + @Test + public void createMessageSuccessful() throws IOException, InterruptedException { + HttpRequest postMessageRequest = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/messages")) + .POST(HttpRequest.BodyPublishers.ofString("{"+ + "\"posted_by\":1, " + + "\"message_text\": \"hello message\", " + + "\"time_posted_epoch\": 1669947792}")) + .header("Content-Type", "application/json") + .build(); + HttpResponse response = webClient.send(postMessageRequest, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + Assert.assertEquals(200, status); + + ObjectMapper om = new ObjectMapper(); + Message expectedResult = new Message(2, 1, "hello message", 1669947792); + System.out.println(response.body().toString()); + Message actualResult = om.readValue(response.body().toString(), Message.class); + Assert.assertEquals(expectedResult, actualResult); + } + + /** + * Sending an http request to POST localhost:8080/messages with empty message + * + * Expected Response: + * Status Code: 400 + * Response Body: + */ + @Test + public void createMessageMessageTextBlank() throws IOException, InterruptedException { + HttpRequest postMessageRequest = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/messages")) + .POST(HttpRequest.BodyPublishers.ofString("{"+ + "\"posted_by\":1, " + + "\"message_text\": \"\", " + + "\"time_posted_epoch\": 1669947792}")) + .header("Content-Type", "application/json") + .build(); + HttpResponse response = webClient.send(postMessageRequest, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(400, status); + Assert.assertEquals("", response.body().toString()); + } + + + /** + * Sending an http request to POST localhost:8080/messages with message length greater than 255 + * + * Expected Response: + * Status Code: 400 + * Response Body: + */ + @Test + public void createMessageMessageGreaterThan255() throws IOException, InterruptedException { + HttpRequest postMessageRequest = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/messages")) + .POST(HttpRequest.BodyPublishers.ofString("{"+ + "\"posted_by\":1, " + + "\"message_text\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\", " + + "\"time_posted_epoch\": 1669947792}")) + .header("Content-Type", "application/json") + .build(); + HttpResponse response = webClient.send(postMessageRequest, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(400, status); + Assert.assertEquals("", response.body().toString()); + } + + + /** + * Sending an http request to POST localhost:8080/messages with a user id that doesnt exist in db + * + * Expected Response: + * Status Code: 400 + * Response Body: + */ + @Test + public void createMessageUserNotInDb() throws IOException, InterruptedException { + HttpRequest postMessageRequest = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/messages")) + .POST(HttpRequest.BodyPublishers.ofString("{"+ + "\"posted_by\":3, " + + "\"message_text\": \"message test\", " + + "\"time_posted_epoch\": 1669947792}")) + .header("Content-Type", "application/json") + .build(); + HttpResponse response = webClient.send(postMessageRequest, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(400, status); + Assert.assertEquals("", response.body().toString()); + } + + +} diff --git a/src/test/java/DeleteMessageByMessageIdTest.java b/src/test/java/DeleteMessageByMessageIdTest.java new file mode 100644 index 0000000..394d454 --- /dev/null +++ b/src/test/java/DeleteMessageByMessageIdTest.java @@ -0,0 +1,91 @@ +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import Controller.SocialMediaController; +import Model.Message; +import Util.ConnectionUtil; +import io.javalin.Javalin; + +public class DeleteMessageByMessageIdTest { + SocialMediaController socialMediaController; + HttpClient webClient; + ObjectMapper objectMapper; + Javalin app; + + /** + * Before every test, reset the database, restart the Javalin app, and create a new webClient and ObjectMapper + * for interacting locally on the web. + * @throws InterruptedException + */ + @Before + public void setUp() throws InterruptedException { + ConnectionUtil.resetTestDatabase(); + socialMediaController = new SocialMediaController(); + app = socialMediaController.startAPI(); + webClient = HttpClient.newHttpClient(); + objectMapper = new ObjectMapper(); + app.start(8080); + Thread.sleep(1000); + } + + @After + public void tearDown() { + app.stop(); + } + + + /** + * Sending an http request to DELETE localhost:8080/messages/1 (message exists) + * + * Expected Response: + * Status Code: 200 + * Response Body: JSON representation of the message that was deleted + */ + @Test + public void deleteMessageGivenMessageIdMessageFound() throws IOException, InterruptedException { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/messages/1")) + .DELETE() + .build(); + HttpResponse response = webClient.send(request, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(200, status); + + Message expectedResult = new Message(1, 1, "test message 1", 1669947792); + Message actualResult = objectMapper.readValue(response.body().toString(), Message.class); + Assert.assertEquals(expectedResult, actualResult); + } + + /** + * Sending an http request to DELETE localhost:8080/messages/100 (message does NOT exists) + * + * Expected Response: + * Status Code: 200 + * Response Body: + */ + @Test + public void deleteMessageGivenMessageIdMessageNotFound() throws IOException, InterruptedException { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/messages/100")) + .DELETE() + .build(); + HttpResponse response = webClient.send(request, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(200, status); + Assert.assertTrue(response.body().toString().isEmpty()); + } + +} diff --git a/src/test/java/RetrieveAllMessagesForUserTest.java b/src/test/java/RetrieveAllMessagesForUserTest.java new file mode 100644 index 0000000..a628822 --- /dev/null +++ b/src/test/java/RetrieveAllMessagesForUserTest.java @@ -0,0 +1,92 @@ +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.List; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import Controller.SocialMediaController; +import Model.Message; +import Util.ConnectionUtil; +import io.javalin.Javalin; + +public class RetrieveAllMessagesForUserTest { + SocialMediaController socialMediaController; + HttpClient webClient; + ObjectMapper objectMapper; + Javalin app; + + /** + * Before every test, reset the database, restart the Javalin app, and create a new webClient and ObjectMapper + * for interacting locally on the web. + * @throws InterruptedException + */ + @Before + public void setUp() throws InterruptedException { + ConnectionUtil.resetTestDatabase(); + socialMediaController = new SocialMediaController(); + app = socialMediaController.startAPI(); + webClient = HttpClient.newHttpClient(); + objectMapper = new ObjectMapper(); + app.start(8080); + Thread.sleep(1000); + } + + @After + public void tearDown() { + app.stop(); + } + + /** + * Sending an http request to GET localhost:8080/accounts/1/messages (messages exist for user) + * + * Expected Response: + * Status Code: 200 + * Response Body: JSON representation of a list of messages + */ + @Test + public void getAllMessagesFromUserMessageExists() throws IOException, InterruptedException { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/accounts/1/messages")) + .build(); + HttpResponse response = webClient.send(request, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(200, status); + + List expectedResult = new ArrayList<>(); + expectedResult.add(new Message(1, 1, "test message 1", 1669947792)); + List actualResult = objectMapper.readValue(response.body().toString(), new TypeReference>(){}); + Assert.assertEquals(expectedResult, actualResult); + } + + /** + * Sending an http request to GET localhost:8080/accounts/1/messages (messages does NOT exist for user) + * + * Expected Response: + * Status Code: 200 + * Response Body: + */ + @Test + public void getAllMessagesFromUserNoMessagesFound() throws IOException, InterruptedException { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/accounts/2/messages")) + .build(); + HttpResponse response = webClient.send(request, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(200, status); + + List actualResult = objectMapper.readValue(response.body().toString(), new TypeReference>(){}); + Assert.assertTrue(actualResult.isEmpty()); + } +} diff --git a/src/test/java/RetrieveAllMessagesTest.java b/src/test/java/RetrieveAllMessagesTest.java new file mode 100644 index 0000000..28483dd --- /dev/null +++ b/src/test/java/RetrieveAllMessagesTest.java @@ -0,0 +1,113 @@ +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import Controller.SocialMediaController; +import Model.Message; +import Util.ConnectionUtil; +import io.javalin.Javalin; + +public class RetrieveAllMessagesTest { + SocialMediaController socialMediaController; + HttpClient webClient; + ObjectMapper objectMapper; + Javalin app; + + /** + * Before every test, reset the database, restart the Javalin app, and create a new webClient and ObjectMapper + * for interacting locally on the web. + * @throws InterruptedException + */ + @Before + public void setUp() throws InterruptedException { + ConnectionUtil.resetTestDatabase(); + socialMediaController = new SocialMediaController(); + app = socialMediaController.startAPI(); + webClient = HttpClient.newHttpClient(); + objectMapper = new ObjectMapper(); + app.start(8080); + Thread.sleep(1000); + } + + @After + public void tearDown() { + app.stop(); + } + +/** + * Sending an http request to GET localhost:8080/messages + * + * Expected Response: + * Status Code: 200 + * Response Body: JSON represenation of a list of message objects + */ + @Test + public void getAllMessagesMessagesAvailable() throws IOException, InterruptedException { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/messages")) + .build(); + HttpResponse response = webClient.send(request, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(200, status); + + List expectedResult = new ArrayList<>(); + expectedResult.add(new Message(1, 1, "test message 1", 1669947792)); + List actualResult = objectMapper.readValue(response.body().toString(), new TypeReference>(){}); + Assert.assertEquals(expectedResult, actualResult); + } + + + /** + * Sending an http request to GET localhost:8080/messages with no mesages in db + * + * Expected Response: + * Status Code: 200 + * Response Body: JSON represenation of an empty list + */ + @Test + public void getAllMessagesNoMessages() throws IOException, InterruptedException { + + removeInitialMessage(); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/messages")) + .build(); + HttpResponse response = webClient.send(request, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(200, status); + + List messages = objectMapper.readValue(response.body().toString(), new TypeReference>(){}); + Assert.assertTrue(messages.isEmpty()); + } + + + + private void removeInitialMessage(){ + try { + Connection conn = ConnectionUtil.getConnection(); + PreparedStatement ps = conn.prepareStatement("delete from message where message_id = ?"); + ps.setInt(1, 1); + ps.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/test/java/RetrieveMessageByMessageIdTest.java b/src/test/java/RetrieveMessageByMessageIdTest.java new file mode 100644 index 0000000..3ba0c28 --- /dev/null +++ b/src/test/java/RetrieveMessageByMessageIdTest.java @@ -0,0 +1,90 @@ +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import Controller.SocialMediaController; +import Model.Message; +import Util.ConnectionUtil; +import io.javalin.Javalin; + +public class RetrieveMessageByMessageIdTest { + SocialMediaController socialMediaController; + HttpClient webClient; + ObjectMapper objectMapper; + Javalin app; + + /** + * Before every test, reset the database, restart the Javalin app, and create a new webClient and ObjectMapper + * for interacting locally on the web. + * @throws InterruptedException + */ + @Before + public void setUp() throws InterruptedException { + ConnectionUtil.resetTestDatabase(); + socialMediaController = new SocialMediaController(); + app = socialMediaController.startAPI(); + webClient = HttpClient.newHttpClient(); + objectMapper = new ObjectMapper(); + app.start(8080); + Thread.sleep(1000); + } + + @After + public void tearDown() { + app.stop(); + } + + + /** + * Sending an http request to GET localhost:8080/messages/1 + * + * Expected Response: + * Status Code: 200 + * Response Body: JSON represenation of a message object + */ + @Test + public void getMessageGivenMessageIdMessageFound() throws IOException, InterruptedException { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/messages/1")) + .build(); + HttpResponse response = webClient.send(request, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(200, status); + + Message expectedResult = new Message(1, 1, "test message 1", 1669947792); + Message actualResult = objectMapper.readValue(response.body().toString(), Message.class); + Assert.assertEquals(expectedResult, actualResult); + } + + + /** + * Sending an http request to GET localhost:8080/messages/100 (message id 100 does not exist) + * + * Expected Response: + * Status Code: 200 + * Response Body: + */ + @Test + public void getMessageGivenMessageIdMessageNotFound() throws IOException, InterruptedException { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/messages/100")) + .build(); + HttpResponse response = webClient.send(request, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(200, status); + Assert.assertTrue(response.body().toString().isEmpty()); + } + + +} diff --git a/src/test/java/UpdateMessageTextTest.java b/src/test/java/UpdateMessageTextTest.java new file mode 100644 index 0000000..c327926 --- /dev/null +++ b/src/test/java/UpdateMessageTextTest.java @@ -0,0 +1,142 @@ +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import Controller.SocialMediaController; +import Model.Message; +import Util.ConnectionUtil; +import io.javalin.Javalin; + +public class UpdateMessageTextTest { + SocialMediaController socialMediaController; + HttpClient webClient; + ObjectMapper objectMapper; + Javalin app; + + /** + * Before every test, reset the database, restart the Javalin app, and create a new webClient and ObjectMapper + * for interacting locally on the web. + * @throws InterruptedException + */ + @Before + public void setUp() throws InterruptedException { + ConnectionUtil.resetTestDatabase(); + socialMediaController = new SocialMediaController(); + app = socialMediaController.startAPI(); + webClient = HttpClient.newHttpClient(); + objectMapper = new ObjectMapper(); + app.start(8080); + Thread.sleep(1000); + } + + @After + public void tearDown() { + app.stop(); + } + + + /** + * Sending an http request to PATCH localhost:8080/messages/1 (message id exists in db) with successfule message text + * + * Expected Response: + * Status Code: 200 + * Response Body: JSON representation of the message that was updated + */ + @Test + public void updateMessageSuccessful() throws IOException, InterruptedException { + HttpRequest postMessageRequest = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/messages/1")) + .method("PATCH", HttpRequest.BodyPublishers.ofString("{"+ + "\"message_text\": \"updated message\" }")) + .header("Content-Type", "application/json") + .build(); + HttpResponse response = webClient.send(postMessageRequest, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(200, status); + + ObjectMapper om = new ObjectMapper(); + Message expectedResult = new Message(1, 1, "updated message", 1669947792); + + Message actualResult = om.readValue(response.body().toString(), Message.class); + Assert.assertEquals(expectedResult, actualResult); + } + + + /** + * Sending an http request to PATCH localhost:8080/messages/1 (message id does NOT exist in db) + * + * Expected Response: + * Status Code: 400 + * Response Body: + */ + @Test + public void updateMessageMessageNotFound() throws IOException, InterruptedException { + HttpRequest postMessageRequest = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/messages/2")) + .method("PATCH", HttpRequest.BodyPublishers.ofString("{"+ + "\"message_text\": \"updated message\" }")) + .header("Content-Type", "application/json") + .build(); + HttpResponse response = webClient.send(postMessageRequest, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(400, status); + Assert.assertTrue(response.body().toString().isEmpty()); + } + + + /** + * Sending an http request to PATCH localhost:8080/messages/1 (message text to update is an empty string) + * + * Expected Response: + * Status Code: 400 + * Response Body: + */ + @Test + public void updateMessageMessageStringEmpty() throws IOException, InterruptedException { + HttpRequest postMessageRequest = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/messages/1")) + .method("PATCH", HttpRequest.BodyPublishers.ofString("{"+ + "\"message_text\": \"\" }")) + .header("Content-Type", "application/json") + .build(); + HttpResponse response = webClient.send(postMessageRequest, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(400, status); + Assert.assertTrue(response.body().toString().isEmpty()); + } + + + /** + * Sending an http request to PATCH localhost:8080/messages/1 (message text is too long) + * + * Expected Response: + * Status Code: 400 + * Response Body: + */ + @Test + public void updateMessageMessageTooLong() throws IOException, InterruptedException { + HttpRequest postMessageRequest = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/messages/1")) + .method("PATCH", HttpRequest.BodyPublishers.ofString("{"+ + "\"message_text\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\" }")) + .header("Content-Type", "application/json") + .build(); + HttpResponse response = webClient.send(postMessageRequest, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(400, status); + Assert.assertTrue(response.body().toString().isEmpty()); + } +} diff --git a/src/test/java/UserLoginTest.java b/src/test/java/UserLoginTest.java new file mode 100644 index 0000000..eda2efc --- /dev/null +++ b/src/test/java/UserLoginTest.java @@ -0,0 +1,122 @@ +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import Controller.SocialMediaController; +import Model.Account; +import Util.ConnectionUtil; +import io.javalin.Javalin; + +public class UserLoginTest { + + SocialMediaController socialMediaController; + HttpClient webClient; + ObjectMapper objectMapper; + Javalin app; + + /** + * Before every test, reset the database, restart the Javalin app, and create a new webClient and ObjectMapper + * for interacting locally on the web. + * @throws InterruptedException + */ + @Before + public void setUp() throws InterruptedException { + ConnectionUtil.resetTestDatabase(); + socialMediaController = new SocialMediaController(); + app = socialMediaController.startAPI(); + webClient = HttpClient.newHttpClient(); + objectMapper = new ObjectMapper(); + app.start(8080); + Thread.sleep(1000); + } + + @After + public void tearDown() { + app.stop(); + } + + /** + * Sending an http request to POST localhost:8080/login with valid username and password + * + * Expected Response: + * Status Code: 200 + * Response Body: JSON representation of user object + */ + @Test + public void loginSuccessful() throws IOException, InterruptedException { + HttpRequest postRequest = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/login")) + .POST(HttpRequest.BodyPublishers.ofString("{" + + "\"username\": \"testuser1\", " + + "\"password\": \"password\" }")) + .header("Content-Type", "application/json") + .build(); + HttpResponse response = webClient.send(postRequest, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(200, status); + ObjectMapper om = new ObjectMapper(); + Account expectedResult = new Account(1, "testuser1", "password"); + Account actualResult = om.readValue(response.body().toString(), Account.class); + Assert.assertEquals(expectedResult, actualResult); + + } + + /** + * Sending an http request to POST localhost:8080/login with invalid username + * + * Expected Response: + * Status Code: 401 + * Response Body: + */ + @Test + public void loginInvalidUsername() throws IOException, InterruptedException { + HttpRequest postRequest = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/login")) + .POST(HttpRequest.BodyPublishers.ofString("{" + + "\"username\": \"testuser404\", " + + "\"password\": \"password\" }")) + .header("Content-Type", "application/json") + .build(); + HttpResponse response = webClient.send(postRequest, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(401, status); + Assert.assertEquals("", response.body().toString()); + + } + + + /** + * Sending an http request to POST localhost:8080/login with invalid password + * + * Expected Response: + * Status Code: 401 + * Response Body: + */ + @Test + public void loginInvalidPassword() throws IOException, InterruptedException { + HttpRequest postRequest = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/login")) + .POST(HttpRequest.BodyPublishers.ofString("{" + + "\"username\": \"testuser1\", " + + "\"password\": \"pass123\" }")) + .header("Content-Type", "application/json") + .build(); + HttpResponse response = webClient.send(postRequest, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + + Assert.assertEquals(401, status); + Assert.assertEquals("", response.body().toString()); + + } +} diff --git a/src/test/java/UserRegistrationTest.java b/src/test/java/UserRegistrationTest.java new file mode 100644 index 0000000..924e2f5 --- /dev/null +++ b/src/test/java/UserRegistrationTest.java @@ -0,0 +1,144 @@ +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import Controller.SocialMediaController; +import Model.Account; +import Util.ConnectionUtil; +import io.javalin.Javalin; + +public class UserRegistrationTest { + SocialMediaController socialMediaController; + HttpClient webClient; + ObjectMapper objectMapper; + Javalin app; + + /** + * Before every test, reset the database, restart the Javalin app, and create a new webClient and ObjectMapper + * for interacting locally on the web. + * @throws InterruptedException + */ + @Before + public void setUp() throws InterruptedException { + ConnectionUtil.resetTestDatabase(); + socialMediaController = new SocialMediaController(); + app = socialMediaController.startAPI(); + webClient = HttpClient.newHttpClient(); + objectMapper = new ObjectMapper(); + app.start(8080); + Thread.sleep(1000); + } + + @After + public void tearDown() { + app.stop(); + } + + /** + * Sending an http request to POST localhost:8080/register when username does not exist in the system + * + * Expected Response: + * Status Code: 200 + * Response Body: JSON representation of user object + */ + @Test + public void registerUserSuccessful() throws IOException, InterruptedException { + HttpRequest postRequest = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/register")) + .POST(HttpRequest.BodyPublishers.ofString("{" + + "\"username\": \"user\", " + + "\"password\": \"password\" }")) + .header("Content-Type", "application/json") + .build(); + HttpResponse response = webClient.send(postRequest, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + Assert.assertEquals(200, status); + Account expectedAccount = new Account(2, "user", "password"); + Account actualAccount = objectMapper.readValue(response.body().toString(), Account.class); + Assert.assertEquals(expectedAccount, actualAccount); + + } + + + /** + * Sending an http request to POST localhost:8080/register when username already exists in system + * + * Expected Response: + * Status Code: 400 + * Response Body: + */ + @Test + public void registerUserDuplicateUsername() throws IOException, InterruptedException { + HttpRequest postRequest = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/register")) + .POST(HttpRequest.BodyPublishers.ofString("{" + + "\"username\": \"user\", " + + "\"password\": \"password\" }")) + .header("Content-Type", "application/json") + .build(); + HttpResponse response1 = webClient.send(postRequest, HttpResponse.BodyHandlers.ofString()); + HttpResponse response2 = webClient.send(postRequest, HttpResponse.BodyHandlers.ofString()); + int status1 = response1.statusCode(); + int status2 = response2.statusCode(); + Assert.assertEquals(200, status1); + Assert.assertEquals(400, status2); + Assert.assertEquals("", response2.body().toString()); + + } + + /** + * Sending an http request to POST localhost:8080/register when no username provided + * + * Expected Response: + * Status Code: 400 + * Response Body: + */ + @Test + public void registerUserUsernameBlank() throws IOException, InterruptedException { + HttpRequest postRequest = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/register")) + .POST(HttpRequest.BodyPublishers.ofString("{" + + "\"username\": \"\", " + + "\"password\": \"password\" }")) + .header("Content-Type", "application/json") + .build(); + HttpResponse response = webClient.send(postRequest, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + Assert.assertEquals(400, status); + Assert.assertEquals("", response.body().toString()); + + } + + + /** + * Sending an http request to POST localhost:8080/register when no password is less than 4 characters + * + * Expected Response: + * Status Code: 400 + * Response Body: + */ + @Test + public void registeUserPasswordLengthLessThanFour() throws IOException, InterruptedException { + HttpRequest postRequest = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/register")) + .POST(HttpRequest.BodyPublishers.ofString("{" + + "\"username\": \"username\", " + + "\"password\": \"pas\" }")) + .header("Content-Type", "application/json") + .build(); + HttpResponse response = webClient.send(postRequest, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + Assert.assertEquals(400, status); + Assert.assertEquals("", response.body().toString()); + + } +}