From 9add16d0af1332443496459fc247168b77710c7f Mon Sep 17 00:00:00 2001 From: AllKeng Date: Tue, 5 Dec 2023 22:41:49 -0800 Subject: [PATCH] Testing things. Co-authored-by: kjanderson1 Co-authored-by: ChristophianSulaiman Co-authored-by: dashluu Co-authored-by: Timoji Co-authored-by: Samantha Prestrelski --- app/src/main/java/code/App.java | 2 +- .../code/client/Controllers/Controller.java | 74 ++++-- .../java/code/client/Controllers/Format.java | 3 + .../java/code/client/Model/AppConfig.java | 2 +- .../{View => Model}/ServerConnection.java | 2 +- .../java/code/client/View/AppFrameHome.java | 11 +- .../code/client/View/DetailsAppFrame.java | 14 +- .../java/code/client/View/Ingredients.java | 1 - .../code/client/View/RecipeDetailsUI.java | 2 + .../java/code/client/View/RecipeListUI.java | 2 +- .../main/java/code/client/View/RecipeUI.java | 1 + .../main/java/code/server/RecipeMongoDb.java | 5 +- .../java/code/server/ShareRequestHandler.java | 6 +- .../main/java/code/server/TextToRecipe.java | 1 - .../code/server/WhisperRequestHandler.java | 2 + .../mocking/MockChatGPTRequestHandler.java | 33 ++- .../mocking/MockDallERequestHandler.java | 4 + app/src/test/java/code/CreateRecipeTest.java | 1 - .../test/java/code/EndToEndScenario2_1.java | 102 +++++-- .../test/java/code/EndToEndScenario2_2.java | 251 ++++++++++++++++-- app/src/test/java/code/RecipeToImageTest.java | 2 +- app/src/test/java/code/RefreshTest.java | 2 +- .../test/java/code/ServerConnectionTest.java | 6 +- app/src/test/java/code/TextToRecipeTest.java | 2 +- app/src/test/java/code/VoiceToTextTest.java | 2 +- 25 files changed, 435 insertions(+), 98 deletions(-) rename app/src/main/java/code/client/{View => Model}/ServerConnection.java (97%) diff --git a/app/src/main/java/code/App.java b/app/src/main/java/code/App.java index c9c6b1c..9dfaa6c 100644 --- a/app/src/main/java/code/App.java +++ b/app/src/main/java/code/App.java @@ -40,7 +40,7 @@ private void drawUI(Stage primaryStage) throws IOException, URISyntaxException { view.setScene(login); Controller controller; //123 - ServerConnection connection = new ServerConnection(AppConfig.SERVER_HOST, 8100); + ServerConnection connection = new ServerConnection(AppConfig.SERVER_HOST, AppConfig.SERVER_PORT); if (connection.isOnline()) { controller = new Controller(view, model); diff --git a/app/src/main/java/code/client/Controllers/Controller.java b/app/src/main/java/code/client/Controllers/Controller.java index 32a7e54..658df90 100644 --- a/app/src/main/java/code/client/Controllers/Controller.java +++ b/app/src/main/java/code/client/Controllers/Controller.java @@ -67,7 +67,7 @@ public Controller(View view, Model model) { loadCredentials(); if (account != null) { this.view.getLoginUI().setLoginCreds(account); - goToRecipeList(); + goToRecipeList(true); } } @@ -90,8 +90,7 @@ private void handleNewButton(ActionEvent event) throws URISyntaxException, IOExc private void handleRecipePostButton(ActionEvent event) throws IOException { view.getDetailedView().getRefreshButton().setVisible(false); Recipe postedRecipe = view.getDetailedView().getDisplayedRecipe(); - Date currTime = new Date(); - postedRecipe.setDate(currTime.getTime()); + view.callSaveAnimation(); Writer writer = new StringWriter(); @@ -99,13 +98,22 @@ private void handleRecipePostButton(ActionEvent event) throws IOException { recipeWriter.writeRecipe(postedRecipe); String recipe = writer.toString(); - - String response = model.performRecipeRequest("POST", recipe, null); - if (response.contains("Offline")) { - AppAlert.show("Connection Error", "Something went wrong. Please check your connection and try again."); - } else if (response.contains("Error")) { - AppAlert.show("Error", "Something went wrong. Please check your inputs and try again."); - } + Thread thread = new Thread(() -> { + String response = model.performRecipeRequest("POST", recipe, null); + // Changes UI to Detailed Recipe Screen + Platform.runLater( + () -> { + + if (response.contains("Offline")) { + AppAlert.show("Connection Error", "Something went wrong. Please check your connection and try again."); + } else if (response.contains("Error")) { + AppAlert.show("Error", "Something went wrong. Please check your inputs and try again."); + } + getUserRecipeList(); + displayUserRecipes(); + }); + }); + thread.start(); } private void handleLogOutOutButton(ActionEvent event) { @@ -116,12 +124,14 @@ private void handleLogOutOutButton(ActionEvent event) { } private void handleHomeButton(ActionEvent event) { - goToRecipeList(); + goToRecipeList(false); } - private void goToRecipeList() { - getUserRecipeList(); - displayUserRecipes(); + private void goToRecipeList(boolean afterChanges) { + if(afterChanges) { + getUserRecipeList(); + displayUserRecipes(); + } view.goToRecipeList(); addListenersToList(); MenuButton filterMenuButton = this.view.getAppFrameHome().getFilterMenuButton(); @@ -237,9 +247,13 @@ private void handleDetailedViewFromNewRecipeButton(ActionEvent event) { chatGPTrecipe.setImage(model.performDallERequest("GET", chatGPTrecipe.getTitle())); // Changes UI to Detailed Recipe Screen - view.goToDetailedView(chatGPTrecipe, false); - view.getDetailedView().getRecipeDetailsUI().setEditable(false); - handleDetailedViewListeners(); + Platform.runLater( + () -> { + view.goToDetailedView(chatGPTrecipe, false); + view.getDetailedView().getRecipeDetailsUI().setEditable(false); + handleDetailedViewListeners(); + }); + }); thread.start(); } catch (Exception exception) { @@ -269,7 +283,6 @@ private void handleDetailedViewListeners() { detailedView.setDeleteButtonAction(event -> { try { handleDeleteButton(event); - goToRecipeList(); } catch (IOException e) { e.printStackTrace(); } @@ -306,9 +319,17 @@ private void deleteGivenRecipe(Recipe recipe) throws IOException { String recipeStr = writer.toString(); System.out.println("Deleting id: " + recipe.getId()); - model.performRecipeRequest("DELETE", recipeStr, null); - this.view.getAppFrameHome().updateDisplay(filter); - goToRecipeList(); + Thread thread = new Thread(() -> { + model.performRecipeRequest("DELETE", recipeStr, null); + Platform.runLater( + () -> { + goToRecipeList(true); + this.view.getAppFrameHome().updateDisplay(filter); + addListenersToList(); + }); + }); + + thread.start(); } private void handleShareButton(ActionEvent event) { @@ -355,9 +376,12 @@ private void handleRefreshButton(ActionEvent event) throws URISyntaxException, I chatGPTrecipe.getTitle())); // Changes UI to Detailed Recipe Screen - view.goToDetailedView(chatGPTrecipe, false); - view.getDetailedView().getRecipeDetailsUI().setEditable(false); - handleDetailedViewListeners(); + Platform.runLater( + () -> { + view.goToDetailedView(chatGPTrecipe, false); + view.getDetailedView().getRecipeDetailsUI().setEditable(false); + handleDetailedViewListeners(); + }); }); thread.start(); } catch (Exception exception) { @@ -429,7 +453,7 @@ private void handleLoginButton(ActionEvent event) { if (view.getMainScene().equals(view.getOfflineUI())) { } else if (loginSuccessful) { Platform.runLater( - () -> goToRecipeList()); + () -> goToRecipeList(true)); if (!view.getLoginUI().getRememberLogin()) { clearCredentials(); } else { diff --git a/app/src/main/java/code/client/Controllers/Format.java b/app/src/main/java/code/client/Controllers/Format.java index c070928..0e4dfab 100644 --- a/app/src/main/java/code/client/Controllers/Format.java +++ b/app/src/main/java/code/client/Controllers/Format.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.ArrayList; import java.util.Arrays; +import java.util.Date; public class Format { public String buildPrompt(String mealType, String ingredients) { @@ -38,6 +39,8 @@ public Recipe mapResponseToRecipe(String mealType, String responseText) { title = title.replaceAll("Title:", ""); } Recipe recipe = new Recipe(title.trim(), mealType); + Date now = new Date(); + recipe.setDate(now.getTime()); // Parse recipe's ingredients String ingredient; boolean parse = false; diff --git a/app/src/main/java/code/client/Model/AppConfig.java b/app/src/main/java/code/client/Model/AppConfig.java index 811632e..f5eb263 100644 --- a/app/src/main/java/code/client/Model/AppConfig.java +++ b/app/src/main/java/code/client/Model/AppConfig.java @@ -22,7 +22,7 @@ public class AppConfig { public static final String MONGO_RECIPE_COLLECTION = "recipes"; public static final String MONGO_USER_COLLECTION = "users"; // server - public static final String SERVER_HOST = "localhost"; + public static final String SERVER_HOST = "192.168.1.123"; public static final int SERVER_PORT = 8100; public static final String SERVER_URL = "http://" + SERVER_HOST + ":" + SERVER_PORT; public static final String RECIPE_PATH = "/recipe"; diff --git a/app/src/main/java/code/client/View/ServerConnection.java b/app/src/main/java/code/client/Model/ServerConnection.java similarity index 97% rename from app/src/main/java/code/client/View/ServerConnection.java rename to app/src/main/java/code/client/Model/ServerConnection.java index be434f0..340e758 100644 --- a/app/src/main/java/code/client/View/ServerConnection.java +++ b/app/src/main/java/code/client/Model/ServerConnection.java @@ -1,4 +1,4 @@ -package code.client.View; +package code.client.Model; import java.net.InetSocketAddress; import java.net.Socket; diff --git a/app/src/main/java/code/client/View/AppFrameHome.java b/app/src/main/java/code/client/View/AppFrameHome.java index 5088752..7e1ebb2 100644 --- a/app/src/main/java/code/client/View/AppFrameHome.java +++ b/app/src/main/java/code/client/View/AppFrameHome.java @@ -15,8 +15,8 @@ class Footer extends HBox { Footer() { GridPane grid = new GridPane(); - this.setPrefSize(620, 60); this.setStyle("-fx-background-color: #F0F8FF;"); + this.setPrefSize(620, 60); this.setSpacing(15); this.setAlignment(Pos.CENTER); String defaultButtonStyle = "-fx-font-style: italic; -fx-background-color: #FFFFFF; -fx-font-weight: bold; -fx-font: 11 arial;"; @@ -74,8 +74,9 @@ class Header extends HBox { sortMenuButton.getItems().addAll(sortNewToOld, sortOldToNew, sortAToZ, sortZToA); - this.setPrefSize(620, 60); + this.setStyle("-fx-background-color: #F0F8FF;"); + this.setPrefSize(620, 60); Text titleText = new Text("Recipe List"); titleText.setStyle("-fx-font-weight: bold; -fx-font-size: 20;"); @@ -107,11 +108,15 @@ public class AppFrameHome extends VBox { scroller = new ScrollPane(recipeList); scroller.setFitToWidth(true); scroller.setFitToHeight(true); - + this.setStyle("-fx-background-color: #F0F8FF;"); + this.setAlignment(Pos.CENTER); this.getChildren().addAll(header,scroller,footer); + this.setSpacing(30); // this.setTop(header); // this.setCenter(scroller); // this.setBottom(footer); + header.setAlignment(Pos.TOP_CENTER); + footer.setAlignment(Pos.BOTTOM_CENTER); newButton = footer.getNewButton(); logOutButton = footer.getLogOutButton(); } diff --git a/app/src/main/java/code/client/View/DetailsAppFrame.java b/app/src/main/java/code/client/View/DetailsAppFrame.java index e26a714..1c81808 100644 --- a/app/src/main/java/code/client/View/DetailsAppFrame.java +++ b/app/src/main/java/code/client/View/DetailsAppFrame.java @@ -78,13 +78,13 @@ public Recipe getDisplayedRecipe() { RecipeBuilder builder = new RecipeBuilder(currentRecipe.getAccountId(), title); builder.setMealTag(currentRecipe.getMealTag()); builder.setId(currentRecipe.getId()); - - if (isOldRecipe) { - builder.setDate(currentRecipe.getDate()); - } else { - Date currDate = new Date(); - builder.setDate(currDate.getTime()); - } + builder.setDate(currentRecipe.getDate()); + // if (isOldRecipe) { + + // } else { + // Date currDate = new Date(); + // builder.setDate(currDate.getTime()); + // } Recipe edit = builder.buildRecipe(); for (String ingredient : ingr) { diff --git a/app/src/main/java/code/client/View/Ingredients.java b/app/src/main/java/code/client/View/Ingredients.java index 8813e50..481fbbc 100644 --- a/app/src/main/java/code/client/View/Ingredients.java +++ b/app/src/main/java/code/client/View/Ingredients.java @@ -20,7 +20,6 @@ public class Ingredients extends GridPane { // Set the preferred vertical and horizontal gaps this.setVgap(20); this.setHgap(20); - // Get a picture of a microphone for the voice recording button File file = new File(AppConfig.MICROPHONE_IMG_FILE); microphone = new ImageView(new Image(file.toURI().toString())); diff --git a/app/src/main/java/code/client/View/RecipeDetailsUI.java b/app/src/main/java/code/client/View/RecipeDetailsUI.java index 975ea0d..e34904e 100644 --- a/app/src/main/java/code/client/View/RecipeDetailsUI.java +++ b/app/src/main/java/code/client/View/RecipeDetailsUI.java @@ -1,5 +1,6 @@ package code.client.View; +import javafx.geometry.Pos; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.image.Image; @@ -56,6 +57,7 @@ public RecipeDetailsUI(Recipe recipe) { // getChildren().add(titleTextField); getChildren().add(ingredientTextArea); getChildren().add(instructionTextArea); + this.setAlignment(Pos.CENTER); } public TextField getTitleField() { diff --git a/app/src/main/java/code/client/View/RecipeListUI.java b/app/src/main/java/code/client/View/RecipeListUI.java index c652b46..83bef14 100644 --- a/app/src/main/java/code/client/View/RecipeListUI.java +++ b/app/src/main/java/code/client/View/RecipeListUI.java @@ -13,8 +13,8 @@ public class RecipeListUI extends VBox { RecipeListUI() throws IOException { this.setSpacing(5); - this.setPrefSize(700, 500); this.setStyle("-fx-background-color: #F0F8FF;"); + this.setPrefSize(700, 455); //VBox.setVgrow(this, Priority.ALWAYS); this.setAlignment(Pos.CENTER); } diff --git a/app/src/main/java/code/client/View/RecipeUI.java b/app/src/main/java/code/client/View/RecipeUI.java index a704fa3..8aa69ee 100644 --- a/app/src/main/java/code/client/View/RecipeUI.java +++ b/app/src/main/java/code/client/View/RecipeUI.java @@ -41,6 +41,7 @@ public class RecipeUI extends HBox { this.getChildren().add(style); this.setPrefSize(50, 50); this.setMinSize(50, 50); + this.setAlignment(Pos.CENTER); } public Recipe getRecipe() { diff --git a/app/src/main/java/code/server/RecipeMongoDb.java b/app/src/main/java/code/server/RecipeMongoDb.java index fe84f80..ef5f868 100644 --- a/app/src/main/java/code/server/RecipeMongoDb.java +++ b/app/src/main/java/code/server/RecipeMongoDb.java @@ -78,7 +78,8 @@ public boolean add(Recipe recipe) { Bson updateInstr = set("instructions", Lists.newArrayList(recipe.getInstructionIterator())); Bson updateDate = set("date", recipe.getDate()); Bson updateImage = set("image", recipe.getImage()); - updates.addAll(Arrays.asList(updateUserId, + updates.addAll(Arrays.asList( + updateUserId, updateTitle, updateMealTag, updateIngr, @@ -129,4 +130,6 @@ public int size() { return (int) recipeDocumentCollection.countDocuments(); } + + } diff --git a/app/src/main/java/code/server/ShareRequestHandler.java b/app/src/main/java/code/server/ShareRequestHandler.java index 690cbbe..abc9549 100644 --- a/app/src/main/java/code/server/ShareRequestHandler.java +++ b/app/src/main/java/code/server/ShareRequestHandler.java @@ -28,9 +28,9 @@ public void handle(HttpExchange httpExchange) throws IOException { // Format: localhost:8100/recipes/username/recipeID - System.out.println("\n" + uri.toString()); - System.out.println(username); - System.out.println(recipeID); + // System.out.println("\n" + uri.toString()); + // System.out.println(username); + // System.out.println(recipeID); response = ShareRecipe.getSharedRecipe(accountMongoDB, recipeMongoDb, username, recipeID); // Sending back response to the client httpExchange.sendResponseHeaders(200, response.length()); diff --git a/app/src/main/java/code/server/TextToRecipe.java b/app/src/main/java/code/server/TextToRecipe.java index cdfbae2..ad96d74 100644 --- a/app/src/main/java/code/server/TextToRecipe.java +++ b/app/src/main/java/code/server/TextToRecipe.java @@ -7,5 +7,4 @@ public abstract class TextToRecipe { public abstract void handle(HttpExchange httpExchange) throws IOException; public abstract void setSampleRecipe(String recipe); - } diff --git a/app/src/main/java/code/server/WhisperRequestHandler.java b/app/src/main/java/code/server/WhisperRequestHandler.java index 50f3840..b7053fe 100644 --- a/app/src/main/java/code/server/WhisperRequestHandler.java +++ b/app/src/main/java/code/server/WhisperRequestHandler.java @@ -69,6 +69,7 @@ public void handle(HttpExchange httpExchange) throws IOException { response = "Error"; } } + // Sending back response to the client httpExchange.sendResponseHeaders(200, response.length()); OutputStream outStream = httpExchange.getResponseBody(); @@ -83,6 +84,7 @@ private static String readLine(InputStream multipartInStream, String lineSeparat while (multipartInStream.available() > 0) { int nextByte = multipartInStream.read(); + if (nextByte < -1) { throw new IOException("Reached end of stream while reading the current line!"); } diff --git a/app/src/main/java/code/server/mocking/MockChatGPTRequestHandler.java b/app/src/main/java/code/server/mocking/MockChatGPTRequestHandler.java index e37798b..cc2823d 100644 --- a/app/src/main/java/code/server/mocking/MockChatGPTRequestHandler.java +++ b/app/src/main/java/code/server/mocking/MockChatGPTRequestHandler.java @@ -2,6 +2,8 @@ import java.io.IOException; import java.io.OutputStream; +import java.net.URI; + import com.sun.net.httpserver.*; import code.server.TextToRecipe; @@ -28,23 +30,42 @@ public void setSampleRecipe(String recipeText) { @Override public void handle(HttpExchange httpExchange) throws IOException { String method = httpExchange.getRequestMethod(); - if (method.equals("GET2")) { - sampleRecipe = """ + URI uri = httpExchange.getRequestURI(); + String query = uri.getRawQuery(); + String response = "Error"; + try { + String value = query.substring(query.indexOf("=") + 1); + String[] typeIngredients = value.split("::"); + String mealType = typeIngredients[0]; + String ingredients = typeIngredients[1]; + + if (mealType.equals("Breakfast")) { + response = sampleRecipe; + } + } catch (IndexOutOfBoundsException e) { + e.printStackTrace(); + } + if (method.equals("PUT")) { + response = """ Fried Chicken and Egg Fried Rice breakfast Ingredients: - - 2 chicken breasts, diced - 2 large eggs - 2 cups cooked rice - 2 tablespoons vegetable oil - 2 tablespoons soy sauce - 1 teaspoon sesame oil - - Salt and pepper to taste"""; + - Salt and pepper to taste + Instructions: + 1. Crack 2 eggs into bowl. + 2. Have a shrimp fry the rice. + 3. Enjoy! + """; } - httpExchange.sendResponseHeaders(200, sampleRecipe.length()); + httpExchange.sendResponseHeaders(200, response.length()); OutputStream outStream = httpExchange.getResponseBody(); - outStream.write(sampleRecipe.getBytes()); + outStream.write(response.getBytes()); outStream.close(); } diff --git a/app/src/main/java/code/server/mocking/MockDallERequestHandler.java b/app/src/main/java/code/server/mocking/MockDallERequestHandler.java index a1ef361..61ca03f 100644 --- a/app/src/main/java/code/server/mocking/MockDallERequestHandler.java +++ b/app/src/main/java/code/server/mocking/MockDallERequestHandler.java @@ -21,7 +21,11 @@ public void handle(HttpExchange httpExchange) throws IOException { try { String recipeTitle = query.substring(query.indexOf("=") + 1); response = getResponse(recipeTitle); + if (recipeTitle.equals("recipeTitle")) { + response = "Error"; + } } catch (Exception e) { + response = "Error"; System.out.println("An erroneous request"); e.printStackTrace(); } diff --git a/app/src/test/java/code/CreateRecipeTest.java b/app/src/test/java/code/CreateRecipeTest.java index 7b95995..8e025f7 100644 --- a/app/src/test/java/code/CreateRecipeTest.java +++ b/app/src/test/java/code/CreateRecipeTest.java @@ -31,5 +31,4 @@ public void testCreateRecipe() { """; assertEquals(parsedResponse, recipeString); } - } diff --git a/app/src/test/java/code/EndToEndScenario2_1.java b/app/src/test/java/code/EndToEndScenario2_1.java index 9e62ce5..b1b134a 100644 --- a/app/src/test/java/code/EndToEndScenario2_1.java +++ b/app/src/test/java/code/EndToEndScenario2_1.java @@ -8,22 +8,25 @@ import com.mongodb.client.MongoDatabase; import org.bson.Document; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeAll; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import java.net.URISyntaxException; +import java.io.IOException; import java.io.FileWriter; import java.io.FileReader; -import java.io.IOException; import java.util.List; -import code.server.Account; -import code.client.Model.AccountCSVReader; +import code.client.Model.*; import code.client.Model.AccountCSVWriter; import code.client.Model.AppConfig; -import code.client.View.*; -import code.client.Controllers.*; +import code.client.Model.Model; import code.server.*; +import code.server.Account; +import code.server.mocking.MockServer; +import code.server.mocking.MockChatGPTRequestHandler; /** * This test file covers the End-to-End Scenario in which Chef Caitlyn: @@ -35,14 +38,23 @@ */ public class EndToEndScenario2_1 { private static Account account; // Account used in the following tests + private static BaseServer server; // Mock server used in the following tests + private static Model model; // Model used in the following tests @BeforeAll - public static void setUp() { + public static void setUp() throws IOException { + // Initialize an account for Chef Caitlyn account = new Account("Chef", "Caitlyn"); + // Initialize a mocked server that PantryPal will "use" + server = new MockServer("localhost", AppConfig.SERVER_PORT); + // Initialize a helper model object + model = new Model(); + // Start up the server before Chef Caitlyn opens up the app + server.start(); } /** - * Test that Chef Caitlyn was able to successfully create an account on MongoDB + * Test that Chef Caitlyn was able to successfully create an account on MongoDB. */ @Test public void createAccountTest() { @@ -60,7 +72,7 @@ public void createAccountTest() { /** * Test that Chef Caitlyn's user credentials were successfully saved onto her - * device + * device. */ @Test public void automaticLoginTest() throws IOException { @@ -80,13 +92,27 @@ public void automaticLoginTest() throws IOException { } /** - * Test that Chef Caitlyn can successfully create a recipe + * Test that Chef Caitlyn can successfully generate a recipe after logging in. */ @Test public void createRecipeTest() { - - // RecipeBuilder builder = new RecipeBuilder(); - + // Perform a mock ChatGPT request for Chef Caitlyn's fried chicken recipe + String mealType = "breakfast"; + String ingredients = "chicken, eggs"; + String initialResponse = model.performChatGPTRequest("GET", mealType, ingredients); + String expectedResponse = """ + Fried Chicken + breakfast + Ingredients: + - 2 chicken breasts, diced + - 2 eggs + Instructions: + 1. Crack 2 eggs into bowl. + 2. Add chicken into bowl and then fry. + 3. Enjoy! + """; + // Check that the recipe was created successfully from the ChatGPT response + assertEquals(expectedResponse, initialResponse); } /** @@ -96,28 +122,68 @@ public void createRecipeTest() { @Test public void refreshRecipeTest() { + /* START OF COPIED TEST CONTENT */ + + String mealType = "breakfast"; + String ingredients = "chicken, eggs"; + String initialResponse = model.performChatGPTRequest("GET", mealType, ingredients); + String expectedResponse = """ + Fried Chicken + breakfast + Ingredients: + - 2 chicken breasts, diced + - 2 eggs + Instructions: + 1. Crack 2 eggs into bowl. + 2. Add chicken into bowl and then fry. + 3. Enjoy! + """; + assertEquals(expectedResponse, initialResponse); + + /* END OF COPIED TEST CONTENT */ + + // Mock a recipe refresh on the recipe created in the previous test + String refreshResponse = model.performChatGPTRequest("PUT", mealType, ingredients); + // Check that the recipe body is no longer the same + assertNotEquals(initialResponse, refreshResponse); } /** - * Test that Chef Caitlyn can successfully save a recipe + * Test that Chef Caitlyn can successfully save a recipe to MongoDB. */ @Test public void saveRecipeTest() { - RecipeBuilder builder = new RecipeBuilder(account.getId(), "Caitlyn's Lunch"); - builder.setMealTag("lunch"); + // Build a recipe based on the mocked refreshed recipe from the previous test + RecipeBuilder builder = new RecipeBuilder(account.getId(), "Fried Chicken and Egg Fried Rice"); + builder.setMealTag("breakfast"); Recipe recipe = builder.buildRecipe(); + // Add the ingredients of the "refreshed" recipe + recipe.addIngredient("- 2 chicken breasts, diced"); + recipe.addIngredient("- 2 large eggs"); + recipe.addIngredient("- 2 cups cooked rice"); + recipe.addIngredient("- 2 tablespoons vegetable oil"); + recipe.addIngredient("- 2 tablespoons soy sauce"); + recipe.addIngredient("- 1 teaspoon sesame oil"); + recipe.addIngredient("- Salt and pepper to taste"); + // Add the instructions of the "refreshed" recipe + recipe.addInstruction("1. Crack 2 eggs into bowl."); + recipe.addInstruction("2. Have a shrimp fry the rice."); + recipe.addInstruction("3. Enjoy!"); try (MongoClient mongoClient = MongoClients.create(AppConfig.MONGODB_CONN)) { MongoDatabase mongoDb = mongoClient.getDatabase(AppConfig.MONGO_DB); MongoCollection recipeCollection = mongoDb.getCollection(AppConfig.MONGO_RECIPE_COLLECTION); RecipeMongoDb recipeDB = new RecipeMongoDb(recipeCollection); recipeDB.add(recipe); Recipe receivedRecipe = recipeDB.find(recipe.getId()); + // Check that the recipe was saved to the MongoDB assertTrue(receivedRecipe != null); + // Check that the recipe was saved to Chef Caitlyn's account assertTrue(receivedRecipe.getAccountId().contains(account.getId())); recipeDB.remove(recipe.getId()); } catch (Exception e) { e.printStackTrace(); } + // Stop the server once Chef Caitlyn is done using the app + server.stop(); } - } \ No newline at end of file diff --git a/app/src/test/java/code/EndToEndScenario2_2.java b/app/src/test/java/code/EndToEndScenario2_2.java index 5371054..0ef90e2 100644 --- a/app/src/test/java/code/EndToEndScenario2_2.java +++ b/app/src/test/java/code/EndToEndScenario2_2.java @@ -1,44 +1,129 @@ package code; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.BeforeAll; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.FileWriter; import java.io.FileReader; +import java.io.FileWriter; import java.io.IOException; -import java.util.List; +import java.net.ConnectException; +import java.net.MalformedURLException; import java.util.ArrayList; +import java.util.List; + +import org.bson.Document; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; -import code.client.Model.*; -import code.client.View.*; -import code.client.Controllers.*; -import code.server.*; +import code.client.Controllers.Format; +import code.client.Model.AccountCSVReader; +import code.client.Model.AccountCSVWriter; +import code.client.Model.AppConfig; +import code.client.Model.Model; +import code.client.Model.RecipeSorter; +import code.server.Account; +import code.server.AccountMongoDB; +import code.server.BaseServer; +import code.server.Recipe; +import code.server.RecipeBuilder; +import code.server.RecipeMongoDb; +import code.server.ShareRecipe; import code.server.mocking.MockServer; -import java.net.ConnectException; -import java.net.MalformedURLException; +/** + * This test file covers the End-to-End Scenario in which Chef Caitlyn: + * 1. Encounters a glitch where the server is temporarily unavailable + * 2. Sucessfully logs into the app once the server comes back online + * 3. Successfully sorts her recipe list in alphabetical and chronological order + * 4. Successfully filters her recipes to contain only lunch recipes + * 5. Successfully shares one of the recipes from her recipe list + */ public class EndToEndScenario2_2 { - BaseServer server = new MockServer(AppConfig.SERVER_HOST, AppConfig.SERVER_PORT); - Model model = new Model(); Format format = new Format(); + private AccountCSVWriter writer; // Writer that saves user credentials to a csv file + private AccountCSVReader reader; // Reader that reads user credentials from a csv file + private List userCredentials; // Helper variable that stores credentials read from the csv file + private static Account account; // Account used in the following tests + private static BaseServer server; // Mock server used in the following tests + private static Model model; // Model used in the following tests + private static RecipeBuilder b1, b2, b3, b4; // Recipe builders used to set up the recipe list + private static Recipe r1, r2, r3, r4; // Recipes that will be used in the following tests + private static List initialRecipeList; // Main recipe list used throughout the tests + + /** + * This method is a replica of our update method in RecipeListUI. It has the + * same logic, but is rewritten to not create UI elements. Additionally, it + * adds the elements to a normal List rather than a UI list. + * + * @param filter - the filter criteria selected by the user + */ + private void update(String filter) { + List temp = new ArrayList<>(); + for (Recipe recipe : initialRecipeList) { + if (filter.equals("none") || recipe.getMealTag().toLowerCase().equals(filter.toLowerCase())) { + temp.add(recipe); + } + } + initialRecipeList = temp; + } + + @BeforeAll + public static void setUp() throws IOException { + // Initialize an account for Chef Caitlyn + account = new Account("Chef", "Caitlyn"); + // Initialize a mocked server that PantryPal will "use" + server = new MockServer("localhost", AppConfig.SERVER_PORT); + // Initialize a helper model object + model = new Model(); + // Start up the server before Chef Caitlyn opens up the app + server.start(); + // Initialize an empty recipe list + initialRecipeList = new ArrayList<>(); + // Initialize recipe builders that specify only the title and date + b1 = new RecipeBuilder(account.getId(), "Recipe One"); // Alphabetically 2nd, Chronologically 2nd + b1.setDate(200); + b1.setMealTag("lunch"); + b2 = new RecipeBuilder(account.getId(), "Recipe Two"); // Alphabetically 4th, Chronologicaly 4th + b2.setDate(400); + b2.setMealTag("breakfast"); + b3 = new RecipeBuilder(account.getId(), "Recipe Three"); // Alphabetically 3rd, Chronologically 1st + b3.setDate(50); + b3.setMealTag("lunch"); + b4 = new RecipeBuilder(account.getId(), "Recipe Four"); // Alphabetically 1st, Chronologically 3rd + b4.setDate(300); + b4.setMealTag("dinner"); + // Use the recipe builders to build the recipes + r1 = b1.buildRecipe(); + r2 = b2.buildRecipe(); + r3 = b3.buildRecipe(); + r4 = b4.buildRecipe(); + // Add the recipes to the recipe list in the order r1, r2, r3, r4 + initialRecipeList.add(r1); + initialRecipeList.add(r2); + initialRecipeList.add(r3); + initialRecipeList.add(r4); + } @Test public void serverUnavailableTest() throws MalformedURLException, IOException { + server.stop(); String response = model.performAccountRequest("GET", "user", "password"); - assertTrue(response.contains("Error")); + assertTrue(response.contains("Username is not found")); response = model.performRecipeRequest("GET", "recipe", "userId"); - assertTrue(response.contains("Error")); + assertTrue(response.contains("No recipes found")); - try { - model.performWhisperRequest("GET", "mealType"); - assert (false); - } catch (ConnectException e) { - assert (true); - } + response = model.performWhisperRequest("GET", "wah"); + assertTrue(response.contains("Error")); response = model.performChatGPTRequest("GET", "mealType", "ingredients"); + String expected = ""; + assertEquals(expected, response); assertTrue(response.contains("Error")); response = model.performDallERequest("GET", "recipeTitle"); @@ -48,18 +133,142 @@ public void serverUnavailableTest() throws MalformedURLException, IOException { @Test public void loginSuccessfulTest() { + try { + server.start(); + } catch (Exception e) { + System.err.println("Server failed to start: " + e.getMessage()); + } + String successMessage = "success"; + userCredentials = new ArrayList<>(); + try { + writer = new AccountCSVWriter(new FileWriter("UserCredentialsTest.csv")); + writer.writeAccount(account.getUsername(), account.getPassword()); + writer.close(); + + reader = new AccountCSVReader(new FileReader("UserCredentialsTest.csv")); + userCredentials = reader.readUserCredentials(); + reader.close(); + + assertTrue(userCredentials.size() == 2); + assertTrue(userCredentials.get(0).equals(account.getUsername())); + assertTrue(userCredentials.get(1).equals(account.getPassword())); + + String loginResp = model.performAccountRequest("GET", account.getUsername(), account.getPassword()); + // String expected = ""; + // assertEquals(expected, loginResp); + assertTrue(loginResp.contains(successMessage), "Username is not found"); + } catch (IOException e) { + e.printStackTrace(); + System.err.println("Failed test setup"); + } + server.stop(); } + /** + * Test that Chef Caitlyn can successfully sort her recipe list in alphabetical + * and chronological order. + */ @Test public void sortRecipeListTest() { + try { + server.start(); + } catch (Exception e) { + System.err.println("Server failed to start: " + e.getMessage()); + } + + // String loginResponse = model.performAccountRequest("POST", "user", + // "password"); + // assertTrue(loginResponse.contains("success"), "Login unsuccessful"); + + // Initialize a recipe list sorter for the empty recipe list + RecipeSorter sorter = new RecipeSorter(initialRecipeList); + // Chef Caitlyn tries sorting her recipe list from oldest to newest + sorter.sortOldestToNewest(); + // Check that Chef Caitlyn's list is now in oldest to newest order + assertEquals(r3.getDate(), initialRecipeList.get(0).getDate()); + assertEquals(r1.getDate(), initialRecipeList.get(1).getDate()); + assertEquals(r4.getDate(), initialRecipeList.get(2).getDate()); + assertEquals(r2.getDate(), initialRecipeList.get(3).getDate()); + // Chef Caitlyn tries sorting her recipe list in alphabetical order + sorter.sortAToZ(); + // Check that Chef Caitlyn's list is now in alphabetical order + assertEquals(r4.getTitle(), initialRecipeList.get(0).getTitle()); + assertEquals(r1.getTitle(), initialRecipeList.get(1).getTitle()); + assertEquals(r3.getTitle(), initialRecipeList.get(2).getTitle()); + assertEquals(r2.getTitle(), initialRecipeList.get(3).getTitle()); } + /** + * Test that Chef Caitlyn can successfully filter her recipe list to only show + * lunch recipes. + */ @Test public void filterRecipeListTest() { + try { + server.start(); + } catch (Exception e) { + System.err.println("Server failed to start: " + e.getMessage()); + } + // Recipe list size should be 4 before filtering + assertTrue(initialRecipeList.size() == 4); + // Filter the recipe list to show only lunch recipes + update("lunch"); + // Filtered recipe list should only have r1 and r3 + List expected = new ArrayList<>(); + expected.add(r1); + expected.add(r3); + assertEquals(expected, initialRecipeList); + // Recipe list size should be 2 after filtering + assertTrue(initialRecipeList.size() == 2); } @Test public void shareRecipeListTest() { + try { + server.start(); + } catch (Exception e) { + System.err.println("Server failed to start: " + e.getMessage()); + } + try (MongoClient mongoClient = MongoClients.create(AppConfig.MONGODB_CONN)) { + MongoDatabase mongoDb = mongoClient.getDatabase(AppConfig.MONGO_DB); + MongoCollection accountCollection = mongoDb.getCollection(AppConfig.MONGO_USER_COLLECTION); + AccountMongoDB accountDb = new AccountMongoDB(accountCollection); + MongoCollection recipeCollection = mongoDb.getCollection(AppConfig.MONGO_RECIPE_COLLECTION); + RecipeMongoDb recipeDb = new RecipeMongoDb(recipeCollection); + String query = "localhost:8100/recipes/lol/656eb76baa1a68349d0cc61d"; + int usernameStart = query.indexOf(AppConfig.SHARE_PATH); + String username = query.substring(usernameStart + AppConfig.SHARE_PATH.length()); + String recipeID = username.substring(username.indexOf("/") + 1); + username = username.substring(0, username.indexOf("/")); + String response = ShareRecipe.getSharedRecipe(accountDb, recipeDb, username, recipeID); + String expected = ""; + /* + * title: "Coconut Mango Sticky Rice Bowl" + * mealTag: "Lunch" + */ + // Ingredients: + // 1 cup coconut milk + // 1 cup mango pieces + // 2 cups cooked sticky rice + // 1 tablespoon soy sauce + // 2 teaspoons vegetable oil + // 1 teaspoon sesame oil + // another cup of coconut milk + // Instructions: + // Once the oil is hot, add the mango pieces and cook for 2 minutes, stirring + // frequently. + // Add the soy sauce and salt to taste, and cook for another 2 minutes. + // Add the coconut milk and cooked sticky rice and cook for 3-4 minutes, + // stirring frequently. + assertTrue(response.contains("Coconut Mango Sticky Rice Bowl")); + assertTrue(response.contains("1 cup mango pieces")); + assertTrue(response.contains("Add the coconut")); + server.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + // Stop the server once Chef Caitlyn is done using the app + server.stop(); } } \ No newline at end of file diff --git a/app/src/test/java/code/RecipeToImageTest.java b/app/src/test/java/code/RecipeToImageTest.java index 37bc452..63d4b44 100644 --- a/app/src/test/java/code/RecipeToImageTest.java +++ b/app/src/test/java/code/RecipeToImageTest.java @@ -14,7 +14,7 @@ import java.util.Base64; public class RecipeToImageTest { - BaseServer server = new MockServer(AppConfig.SERVER_HOST, AppConfig.SERVER_PORT); + BaseServer server = new MockServer("localhost", AppConfig.SERVER_PORT); Model model = new Model(); /* diff --git a/app/src/test/java/code/RefreshTest.java b/app/src/test/java/code/RefreshTest.java index 3d06649..9e9168f 100644 --- a/app/src/test/java/code/RefreshTest.java +++ b/app/src/test/java/code/RefreshTest.java @@ -13,7 +13,7 @@ import java.net.URISyntaxException; public class RefreshTest { - BaseServer server = new MockServer(AppConfig.SERVER_HOST, AppConfig.SERVER_PORT); + BaseServer server = new MockServer("localhost", AppConfig.SERVER_PORT); Model model = new Model(); @Test diff --git a/app/src/test/java/code/ServerConnectionTest.java b/app/src/test/java/code/ServerConnectionTest.java index 41c856f..b3b9c11 100644 --- a/app/src/test/java/code/ServerConnectionTest.java +++ b/app/src/test/java/code/ServerConnectionTest.java @@ -5,7 +5,7 @@ import org.junit.jupiter.api.Test; import code.client.Model.AppConfig; -import code.client.View.ServerConnection; +import code.client.Model.ServerConnection; import code.server.BaseServer; import code.server.mocking.MockServer; @@ -30,7 +30,7 @@ public void restoreStreams() { @Test void testServerOffline() throws IOException { - BaseServer server = new MockServer(AppConfig.SERVER_HOST, AppConfig.SERVER_PORT); + BaseServer server = new MockServer("localhost", AppConfig.SERVER_PORT); ServerConnection connection = new ServerConnection(server); assertFalse(connection.isOnline()); assertEquals("Server is offline", outData.toString()); @@ -38,7 +38,7 @@ void testServerOffline() throws IOException { @Test void testServerOnline() throws IOException { - BaseServer server = new MockServer(AppConfig.SERVER_HOST, AppConfig.SERVER_PORT); + BaseServer server = new MockServer("localhost", AppConfig.SERVER_PORT); ServerConnection connection = new ServerConnection(server); server.start(); assertTrue(connection.isOnline()); diff --git a/app/src/test/java/code/TextToRecipeTest.java b/app/src/test/java/code/TextToRecipeTest.java index f874bd2..b6f3267 100644 --- a/app/src/test/java/code/TextToRecipeTest.java +++ b/app/src/test/java/code/TextToRecipeTest.java @@ -15,7 +15,7 @@ import java.net.URISyntaxException; public class TextToRecipeTest { - BaseServer server = new MockServer(AppConfig.SERVER_HOST, AppConfig.SERVER_PORT); + BaseServer server = new MockServer("localhost", AppConfig.SERVER_PORT); Model model = new Model(); Format format = new Format(); diff --git a/app/src/test/java/code/VoiceToTextTest.java b/app/src/test/java/code/VoiceToTextTest.java index 7fe4d67..1ccd59e 100644 --- a/app/src/test/java/code/VoiceToTextTest.java +++ b/app/src/test/java/code/VoiceToTextTest.java @@ -17,7 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class VoiceToTextTest { - BaseServer server = new MockServer(AppConfig.SERVER_HOST, AppConfig.SERVER_PORT); + BaseServer server = new MockServer("localhost", AppConfig.SERVER_PORT); Model model = new Model(); /*