diff --git a/README.md b/README.md index 1bbb33daed1b6183666c8dbe72ec0e570523a71e..1b3268c4ebd1c4d6b35b975b7497c4a6e07db6da 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,14 @@ maven icon. Then, run the "GrowBrosApplication". The API can be accessed at http Go into the "growbros-frontend" folder and open it in VSCode. Run "npm install" (Node.js must be installed). Then, type "npm run dev". +### Documentation + +How to run our project is documented in this README. +The different features are listed in the CHANGELOG. + +Our architectural decisions are documented as ARDs in the Gitlab Wiki as well as our reflections. + ### Links * install node.js https://nodejs.org/en +* read our Wiki: https://gitlab.mi.hdm-stuttgart.de/tomato/growbros/-/wikis/home diff --git a/growbros-frontend/index.html b/growbros-frontend/index.html index f69a4c46028bc8fa213e098940539af544f6d46a..6364cfa99010e6b17f2b461d2acf11a6b6c2baba 100644 --- a/growbros-frontend/index.html +++ b/growbros-frontend/index.html @@ -1,10 +1,10 @@ <!doctype html> -<html lang="en"> +<html lang="de"> <head> <meta charset="UTF-8"/> - <link rel="icon" type="image/svg+xml" href="/vite.svg"/> + <link rel="icon" type="image/png" href="/recources/growbros_logo.png"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> - <title>Vite + React + TS</title> + <title>GrowBros - Manage your garden</title> </head> <body> <div id="root"></div> diff --git a/growbros-frontend/public/recources/growbros_logo.png b/growbros-frontend/public/recources/growbros_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d3417b187bb50fe699e0bfda202c842feae906f7 Binary files /dev/null and b/growbros-frontend/public/recources/growbros_logo.png differ diff --git a/growbros-frontend/src/pages/BackendConnectorTestInterface.tsx b/growbros-frontend/src/pages/BackendConnectorTestInterface.tsx index b28b5617d910a39409a8daca7b2e009cdff462b4..1c58620a166b5671fe1689dd70db414c21856b31 100644 --- a/growbros-frontend/src/pages/BackendConnectorTestInterface.tsx +++ b/growbros-frontend/src/pages/BackendConnectorTestInterface.tsx @@ -5,8 +5,10 @@ const bc = new BackendConnectorImpl("http://localhost:8080/api/v1"); export function BackendConnectorTestInterface() { return <div style={{padding: "1rem"}}> <h2>Backend connector test</h2> - <p>Achtung: das ist / war ausschließlich zum Testen von meinen geschriebenen fetch-methoden. Benutzt das auf - keinen Fall in euren components... lg lukas</p> + <p><b>Achtung:</b> das ist / war ausschließlich zum Testen von meinen geschriebenen fetch-methoden. Benutzt das + auf + keinen Fall in euren components... lg lukas + </p> <h3>Plants controller</h3> <ul style={{display: "block", listStyleType: "initial"}}> <li onClick={async () => console.log(await bc.getSinglePlant(1101))}>Get single plant</li> @@ -19,7 +21,7 @@ export function BackendConnectorTestInterface() { <li onClick={async () => console.log(await bc.getGardenEntries())}>Get garden</li> <li onClick={async () => console.log(await bc.addToGarden(1000))}>Add plant to garden</li> <li onClick={async () => console.log(await bc.getGardenSize())}>garden size</li> - <li onClick={async () => console.log(await bc.removeEntryFromGarden(3))}>remove entry</li> + <li onClick={async () => console.log(await bc.removeFromGarden(3))}>remove entry</li> <li onClick={async () => console.log(await bc.clearGarden())}>clear garden</li> </ul> <h3>Wishlist controller</h3> diff --git a/growbros-frontend/src/pages/Home.tsx b/growbros-frontend/src/pages/Home.tsx index 0bef8da1af0b2c1f059103a24e910516cffd2f93..d6bc10e78662f693c6a08b7a3e5aaf66c089b638 100644 --- a/growbros-frontend/src/pages/Home.tsx +++ b/growbros-frontend/src/pages/Home.tsx @@ -1,89 +1,89 @@ -import { useState } from "react"; +import {useState} from "react"; import "../stylesheets/Home.css"; -import { NavLink } from "react-router-dom"; -import { checkJwtStatus } from "../jwt/Cookies"; +import {NavLink} from "react-router-dom"; +import {checkJwtStatus} from "../jwt/Cookies"; function Home() { - return ( - <main> - <div className="hero"> - <img - src="../../public/recources/images/Vegetables.jpeg" - className="mainPicture" - ></img> - <div className="descriptionAside"> - <h1 className="header1">GrowBros</h1> - <h2 className="header2">Keep your plants alive</h2> - <p className="text"> - "Mit GrowBros wird Gärtnern zum Kinderspiel! Finde die perfekten - Pflanzen für deinen Garten, erhalte nützliche Informationen und - verwalte sie mühelos. Starte noch heute und lass deinen Garten in - voller Blüte erstrahlen!" - </p> - <StartNowLink></StartNowLink> - </div> - </div> - <div className="secondDiv"> - Fange noch heute an dir deinen Traumgarten zusammenzustellen und ernte - bald die Früchte deiner Arbeit. - </div> - <div style={{ display: "flex" }}> - <div className="smallDiv" style={{ width: "60%" }}> - <h2>Unsere Vorteile...</h2> - <p> - Entdecke neue Pflanzen und erhalte wertvolle Informationen für - deinen Garten. Erfasse sie mühelos und erhalte Erinnerungen für - Pflege und Ernte. - </p> - </div> - <img - className="smallPicture" - src="../../public/recources/images/Broccoli.webp" - ></img> - </div> - <div style={{ display: "flex" }}> - <img - className="smallPicture" - src="../../public/recources/images/Red_Vegetable.webp" - ></img> - <div className="smallDiv"> - <p> - Organisiere deinen Garten besser. Verfolge den Fortschritt deiner - Pflanzen und erhalte Anleitungen, um sie gesund und glücklich zu - halten. - </p> - </div> - </div> - <div style={{ display: "flex" }}> - <div className="smallDiv"> - <p> - Maximiere deine Ernte. Unsere App hilft dir, den optimalen Zeitpunkt - für die Ernte deiner Pflanzen zu finden und sorgt so für eine reiche - Belohnung. - </p> - </div> - <img - className="smallPicture" - src="../../public/recources/images/Cucumber.webp" - ></img> - </div> - </main> - ); + return ( + <main> + <div className="hero"> + <img + src="../../public/recources/images/Vegetables.jpeg" + className="mainPicture" + alt="fresh plants"/> + <div className="descriptionAside"> + <h1 className="header1">GrowBros</h1> + <h2 className="header2">Keep your plants alive</h2> + <p> + "Mit GrowBros wird Gärtnern zum Kinderspiel! Finde die perfekten + Pflanzen für deinen Garten, erhalte nützliche Informationen und + verwalte sie mühelos. Starte noch heute und lass deinen Garten in + voller Blüte erstrahlen!" + </p> + <StartNowLink/> + </div> + </div> + <div className="secondDiv"> + Fange noch heute an, dir deinen Traumgarten zusammenzustellen und ernte + bald die Früchte deiner Arbeit. + </div> + <div style={{display: "flex"}}> + <div className="smallDiv" style={{width: "60%"}}> + <h2>Unsere Vorteile...</h2> + <p> + Entdecke neue Pflanzen und erhalte wertvolle Informationen für + Deinen Garten. Erfasse sie mühelos und erhalte Erinnerungen für + Pflege und Ernte. + </p> + </div> + <img + className="smallPicture" + src="../../public/recources/images/Broccoli.webp" + alt="broccoli"></img> + </div> + <div style={{display: "flex"}}> + <img + className="smallPicture" + src="../../public/recources/images/Red_Vegetable.webp" + alt="red vegetables"></img> + <div className="smallDiv"> + <p> + Organisiere deinen Garten besser. Verfolge den Fortschritt deiner + Pflanzen und erhalte Anleitungen, um sie gesund und glücklich zu + halten. + </p> + </div> + </div> + <div style={{display: "flex"}}> + <div className="smallDiv"> + <p> + Maximiere Deine Ernte. Unsere App hilft dir, den optimalen Zeitpunkt + für die Ernte deiner Pflanzen zu finden und sorgt so für eine reiche + Belohnung. + </p> + </div> + <img + className="smallPicture" + src="../../public/recources/images/Cucumber.webp" + alt="cucumber"></img> + </div> + </main> + ); } function StartNowLink() { - const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false); + const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false); - checkJwtStatus(setIsLoggedIn); + checkJwtStatus(setIsLoggedIn); - return ( - <div className="link"> - <NavLink to={isLoggedIn ? "/suchen" : "/login"}> - <h3> Starte hier</h3> - <p>und füge Pflanzen zu deinem Garten hinzu</p> - </NavLink> - </div> - ); + return ( + <div className="link"> + <NavLink to={isLoggedIn ? "/suchen" : "/login"}> + <h3>Starte hier</h3> + <p>und füge Pflanzen zu deinem Garten hinzu</p> + </NavLink> + </div> + ); } export default Home; diff --git a/growbros-frontend/src/pages/Suche.tsx b/growbros-frontend/src/pages/Suche.tsx index 7a200239abaf30ccc30212744e02c5283189774c..7a2c8bd7706d56472916d1716ea5de38cc31b813 100644 --- a/growbros-frontend/src/pages/Suche.tsx +++ b/growbros-frontend/src/pages/Suche.tsx @@ -1,146 +1,152 @@ import "font-awesome/css/font-awesome.min.css"; import "../stylesheets/Suche.css"; import PlantsOverview from "../components/PlantsOverview"; -import {useEffect, useState} from "react"; +import { useEffect, useState } from "react"; import FilterPage from "../components/FilterPage"; -import {GroundType, LightingDemand, NutrientDemand, Plant, WaterDemand,} from "../utils/schemas"; -import {IBackendConnector} from "../utils/IBackendConnector"; +import { + GroundType, + LightingDemand, + NutrientDemand, + Plant, + WaterDemand, +} from "../utils/schemas"; +import { IBackendConnector } from "../utils/IBackendConnector"; import useGrowbrosFetcher from "../utils/useGrowbrosFetcher"; type SearchBarProps = { - setError: (value: any) => void; - setPlants: (value: Plant[]) => void; + setError: (value: any) => void; + setPlants: (value: Plant[]) => void; }; function Suche() { - const [plants, setPlants] = useState<Plant[]>([]); - const [error, setError] = useState({}); - const randomPlantsCount: number = 100; + const [plants, setPlants] = useState<Plant[]>([]); + const [error, setError] = useState<object>(); + const randomPlantsCount: number = 100; - const bc: IBackendConnector = useGrowbrosFetcher(); + const bc: IBackendConnector = useGrowbrosFetcher(); - useEffect(() => { - (async () => { - const result = await bc.getRandomPlants(randomPlantsCount); - if (result.err) { - setError(result.err); - } else { - setPlants(result.value!); - } - })(); - }, []); + useEffect(() => { + (async () => { + const result = await bc.getRandomPlants(randomPlantsCount); + if (result.err) { + setError(result.err); + } else { + setPlants(result.value!); + } + })(); + }, []); - return ( - <> - <SearchBar setError={setError} setPlants={setPlants}/> - <div> - {plants.length === 0 && ( - <div> - Es wurden keine Pflanzen passend zu deiner Suchanfrage gefunden - </div> - )} - { - error && ( - <div> - Es ist ein Fehler aufgetreten. - </div> - ) - } - <PlantsOverview plants={plants}/> - </div> - </> - ); + return ( + <> + <SearchBar setError={setError} setPlants={setPlants} /> + <div> + {plants.length === 0 && ( + <div> + Es wurden keine Pflanzen passend zu deiner Suchanfrage gefunden + </div> + )} + {error && <div>Es ist ein Fehler aufgetreten.</div>} + <PlantsOverview plants={plants} /> + </div> + </> + ); } -function SearchBar({setPlants, setError}: SearchBarProps) { - const [isComponentVisible, setComponentVisible] = useState(false); - const [searchTerm, setSearchTerm] = useState<string>(""); - const [waterDemand, setWaterDemand] = useState<WaterDemand>(); - const [nutrientDemand, setNutrientDemand] = useState<NutrientDemand>(); - const [lightingDemand, setLightingDemand] = useState<LightingDemand>(); - const [groundType, setGroundType] = useState<GroundType>(); - const [plantDuration, setPlantDuration] = useState<[number, number]>(); - const [growthDuration, setGrowthDuration] = useState<[number, number]>(); - const bc: IBackendConnector = useGrowbrosFetcher(); - - const handleSearchChange = (event: any) => { - setSearchTerm(event.target.value); - }; - const loadComponent = () => { - setComponentVisible(!isComponentVisible); - }; +function SearchBar({ setPlants, setError }: SearchBarProps) { + const [isComponentVisible, setComponentVisible] = useState(false); + const [searchTerm, setSearchTerm] = useState<string>(""); + const [waterDemand, setWaterDemand] = useState<WaterDemand>(); + const [nutrientDemand, setNutrientDemand] = useState<NutrientDemand>(); + const [lightingDemand, setLightingDemand] = useState<LightingDemand>(); + const [groundType, setGroundType] = useState<GroundType>(); + const [plantDuration, setPlantDuration] = useState<[number, number]>(); + const [growthDuration, setGrowthDuration] = useState<[number, number]>(); + const bc: IBackendConnector = useGrowbrosFetcher(); - const handleSearchRequestSubmit = async () => { - let result; + const handleSearchChange = (event: any) => { + setSearchTerm(event.target.value); + }; + const loadComponent = () => { + setComponentVisible(!isComponentVisible); + }; - if (isSearchSetToDefault()) { - result = await bc.getRandomPlants(100); - } else { - result = await bc.searchPlants({ - searchTerm, - waterDemand, - nutrientDemand, - lightingDemand, - groundType, - growthDurationMin: growthDuration && growthDuration[0], - growthDurationMax: growthDuration && growthDuration[1], - plantWeekStart: plantDuration && plantDuration[0], - plantWeekEnd: plantDuration && plantDuration[1], - }); - } + const handleSearchRequestSubmit = async () => { + let result; - if (result.err) { - setError(result.err); - } else { - console.log(result.value); - setPlants(result.value!); - } - }; + if (isSearchSetToDefault()) { + result = await bc.getRandomPlants(100); + } else { + result = await bc.searchPlants({ + searchTerm, + waterDemand, + nutrientDemand, + lightingDemand, + groundType, + growthDurationMin: growthDuration && growthDuration[0], + growthDurationMax: growthDuration && growthDuration[1], + plantWeekStart: plantDuration && plantDuration[0], + plantWeekEnd: plantDuration && plantDuration[1], + }); + } - const isSearchSetToDefault = () => { - return (!searchTerm || searchTerm.trim() === "") && !waterDemand && !lightingDemand && !nutrientDemand && !groundType - && (!plantDuration || plantDuration[0] === 1 && plantDuration[1] === 52) - && (!growthDuration || growthDuration[0] === 1 && growthDuration[1] === 52) + if (result.err) { + setError(result.err); + } else { + console.log(result.value); + setPlants(result.value!); } + }; + const isSearchSetToDefault = () => { return ( - <> - <div className="searchBar"> - <h2>Suche nach einer Pflanze</h2> - <p> - und füge diese dann zu deinem Garten oder zu deiner Wunschliste hinzu - </p> - <div className="searchFilter"> - <input - onChange={handleSearchChange} - value={searchTerm} - type="text" - placeholder="Pflanze suchen..." - /> - <button onClick={loadComponent} className="filter"> - <i className="fa fa-filter"/> - </button> - <button onClick={handleSearchRequestSubmit}> - <i className="fa fa-search"></i> - </button> - </div> - <div - style={ - isComponentVisible ? {display: "block"} : {display: "none"} - } - > - <FilterPage - setGroundType={setGroundType} - setGrowthDuration={setGrowthDuration} - setLightingDemand={setLightingDemand} - setNutrientDemand={setNutrientDemand} - setPlantDuration={setPlantDuration} - setWaterDemand={setWaterDemand} - /> - </div> - </div> - </> + (!searchTerm || searchTerm.trim() === "") && + !waterDemand && + !lightingDemand && + !nutrientDemand && + !groundType && + (!plantDuration || (plantDuration[0] === 1 && plantDuration[1] === 52)) && + (!growthDuration || (growthDuration[0] === 1 && growthDuration[1] === 52)) ); + }; + + return ( + <> + <div className="searchBar"> + <h2>Suche nach einer Pflanze</h2> + <p> + und füge diese dann zu deinem Garten oder zu deiner Wunschliste hinzu + </p> + <div className="searchFilter"> + <input + onChange={handleSearchChange} + value={searchTerm} + type="text" + placeholder="Pflanze suchen..." + /> + <button onClick={loadComponent} className="filter"> + <i className="fa fa-filter" /> + </button> + <button onClick={handleSearchRequestSubmit}> + <i className="fa fa-search"></i> + </button> + </div> + <div + style={ + isComponentVisible ? { display: "block" } : { display: "none" } + } + > + <FilterPage + setGroundType={setGroundType} + setGrowthDuration={setGrowthDuration} + setLightingDemand={setLightingDemand} + setNutrientDemand={setNutrientDemand} + setPlantDuration={setPlantDuration} + setWaterDemand={setWaterDemand} + /> + </div> + </div> + </> + ); } export default Suche; diff --git a/growbros-frontend/src/stylesheets/Home.css b/growbros-frontend/src/stylesheets/Home.css index 8f651d1f643f32249636914180ac3feb9647c4e2..0112386a64837c66de2ba49a2ff082a870c4bf75 100644 --- a/growbros-frontend/src/stylesheets/Home.css +++ b/growbros-frontend/src/stylesheets/Home.css @@ -22,6 +22,7 @@ } .descriptionAside p { + font-size: 1.25rem; padding-top: 20px; font-family: Century Gothic, sans-serif; } @@ -37,6 +38,8 @@ font-size: 1.5rem; font-weight: 500; font-family: Century Gothic, sans-serif; + text-align: center; + text-wrap: pretty; /*ja, das funktioniert, auch wenns rot unterstrichen ist (keine einzelnen wörter in der letzten zeile)*/ } .smallDiv { @@ -57,20 +60,18 @@ .link a { color: #4d5927; - font-size: 1.3rem; - } .link p { - font-size: 1rem; padding-top: 5px; } -.link{ +.link { margin-top: 30px; - transition: transform 0.25s ease-in-out; + font-size: 1.25rem; + transition: transform 0.15s ease-in-out; } -.link:hover{ +.link:hover { transform: scale(1.025); -} \ No newline at end of file +} diff --git a/src/main/java/hdm/mi/growbros/config/JpaConfiguration.java b/src/main/java/hdm/mi/growbros/config/JpaConfiguration.java index 54ca4b8665dc704fb1795445508bcfb305242fcd..d0970e68fe2546c603c7a9197fa38f23b323aa4c 100644 --- a/src/main/java/hdm/mi/growbros/config/JpaConfiguration.java +++ b/src/main/java/hdm/mi/growbros/config/JpaConfiguration.java @@ -1,9 +1,11 @@ package hdm.mi.growbros.config; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @Configuration @EnableJpaAuditing +@Profile("!test") public class JpaConfiguration { } diff --git a/src/test/java/hdm/mi/growbros/config/TestJpaAuditing.java b/src/test/java/hdm/mi/growbros/config/TestJpaAuditing.java new file mode 100644 index 0000000000000000000000000000000000000000..b489c5631e393903926d3f067c240b3342a674b0 --- /dev/null +++ b/src/test/java/hdm/mi/growbros/config/TestJpaAuditing.java @@ -0,0 +1,9 @@ +package hdm.mi.growbros.config; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@TestConfiguration +@EnableJpaAuditing(setDates = false) +public class TestJpaAuditing { +} diff --git a/src/test/java/hdm/mi/growbros/controllers/WishListControllerTest.java b/src/test/java/hdm/mi/growbros/controllers/WishListControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7003e634658c5ba4d4ff9448d71112f355d22dfd --- /dev/null +++ b/src/test/java/hdm/mi/growbros/controllers/WishListControllerTest.java @@ -0,0 +1,68 @@ +package hdm.mi.growbros.controllers; + +import hdm.mi.growbros.models.plant.Plant; +import hdm.mi.growbros.service.WishListService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +public class WishListControllerTest { + + @Autowired + private MockMvc mvc; + + @MockBean + private WishListService wishlistService; + + @Test + @WithMockUser + void wishlistEntries_shouldBeOkJson_containList() throws Exception { + when(wishlistService.getWishedPlants(any(), any())).thenReturn(List.of(Plant.builder().name("Plant").build())); + + mvc.perform(get("/api/v1/wishlist").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.size()").value(1)) + .andDo(print()); + + verify(wishlistService).getWishedPlants(any(), any()); + } + + @Test + @WithMockUser + void creatingEntry_shouldBe201_andContainPlant() throws Exception { + + mvc.perform(post("/api/v1/wishlist/add/0").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andDo(print()); + + verify(wishlistService).addPlantToWishList(anyLong(), any()); + } + + @Test + @WithMockUser + void deletingEntry_shouldBe204() throws Exception { + mvc.perform(delete("/api/v1/wishlist/remove/1").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()) + .andDo(print()); + } +} \ No newline at end of file diff --git a/src/test/java/hdm/mi/growbros/integration/GardenIntegrationTest.java b/src/test/java/hdm/mi/growbros/integration/GardenIntegrationTest.java index 2e4a59a7ad1b835b18989c08727a0185b32189d9..e025d7ce715c0d57b74cb7a8f39618885476c3e7 100644 --- a/src/test/java/hdm/mi/growbros/integration/GardenIntegrationTest.java +++ b/src/test/java/hdm/mi/growbros/integration/GardenIntegrationTest.java @@ -33,8 +33,12 @@ public class GardenIntegrationTest extends BaseIntegrationTest { @Test void shouldAddPlant_code201() throws Exception { mvc.perform(post(gardenUrl + "/add/1").headers(authHeader)) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.name").value(pflanzeNumeroUno.getName())); + .andExpect(status().isCreated()); + + mvc.perform(get(gardenUrl).headers(authHeader)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.size()").value(1)) + .andExpect(jsonPath("$[0].name").value(pflanzeNumeroUno.getName())); } @Test @@ -72,5 +76,9 @@ public class GardenIntegrationTest extends BaseIntegrationTest { mvc.perform(delete(gardenUrl + "/remove/all").headers(authHeader)) .andExpect(status().isOk()) .andExpect(content().string("2")); + + mvc.perform(get(gardenUrl + "/count").headers(authHeader)) + .andExpect(status().isOk()) + .andExpect(content().string("0")); } } diff --git a/src/test/java/hdm/mi/growbros/repositories/WishListRepositoryTest.java b/src/test/java/hdm/mi/growbros/repositories/WishListRepositoryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..cc99ef4f2b53dcb3befcd25c91d3935d98d5caea --- /dev/null +++ b/src/test/java/hdm/mi/growbros/repositories/WishListRepositoryTest.java @@ -0,0 +1,119 @@ +package hdm.mi.growbros.repositories; + +import hdm.mi.growbros.models.WishListEntry; +import hdm.mi.growbros.models.plant.Plant; +import hdm.mi.growbros.models.user.Role; +import hdm.mi.growbros.models.user.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@DataJpaTest +@ActiveProfiles("test") +public class WishListRepositoryTest { + + @Autowired + private WishListRepository underTest; + + @Autowired + private TestEntityManager entityManager; + + private WishListEntry we1, we2, we3, we4; + private User user1, user2; + + @BeforeEach + void setupEach() { + user1 = User.builder() + .email("user1@gmail.com") + .role(Role.USER) + .password("safePassword123!") + .build(); + entityManager.persist(user1); + + user2 = User.builder() + .email("user2@gmail.com") + .role(Role.USER) + .password("safePassword123!") + .build(); + entityManager.persist(user2); + + Plant p1 = Plant.builder() + .name("Early plant") + .plantWeekStart(1) + .plantWeekEnd(3) + .harvestWeekStart(10) + .harvestWeekEnd(15) + .build(); + entityManager.persist(p1); + Plant p2 = Plant.builder() + .name("Medium late plant") + .plantWeekStart(4) + .plantWeekEnd(6) + .harvestWeekStart(18) + .harvestWeekEnd(20) + .build(); + entityManager.persist(p2); + Plant p3 = Plant.builder() + .name("Late plant") + .plantWeekStart(10) + .plantWeekEnd(15) + .harvestWeekStart(25) + .harvestWeekEnd(30) + .build(); + entityManager.persist(p3); + + we1 = WishListEntry.builder().id(0L).plant(p1).user(user1).build(); + we2 = WishListEntry.builder().id(0L).plant(p2).user(user1).build(); + we3 = WishListEntry.builder().id(0L).plant(p3).user(user1).build(); + we4 = WishListEntry.builder().id(0L).plant(p3).user(user2).build(); + underTest.saveAll(List.of(we1, we2, we3, we4)); + } + + @Test + void shouldReturnCorrectList_ofEntriesForUser_orderedByNearestPlantingWeek() { + //when + var result = underTest.findAllByNearestPlantingWeek(user1, 1); + + //expect + assertEquals(List.of(we1, we2, we3), result); + } + + @Test + void shouldReturnCorrectList_ofEntriesForUser_orderedByCurrentPlantingWeek() { + //when + var result = underTest.findByCurrentPlantingWeek(user1, 1); + + //expect + assertEquals(List.of(we1), result); + } + + @Test + void shouldDeleteCorrectEntries_whenClearingGarden_forUser() { + //when + int deleted = underTest.deleteAllByUser(user2); + + //expect + assertEquals(1, deleted); + assertEquals(List.of(), underTest.findByUser(user2)); + assertThat(List.of(we1, we2, we3)).hasSameElementsAs(underTest.findByUser(user1)); + } + + @Test + void countShouldBeCorrect_forUser() { + //when + int user1Count = underTest.countByUser(user1); + int user2Count = underTest.countByUser(user2); + + //expect + assertEquals(3, user1Count); + assertEquals(1, user2Count); + } +} diff --git a/src/test/java/hdm/mi/growbros/service/WishListServiceTest.java b/src/test/java/hdm/mi/growbros/service/WishListServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a0224445e83b4a66dad607891debac4e7d8e8c18 --- /dev/null +++ b/src/test/java/hdm/mi/growbros/service/WishListServiceTest.java @@ -0,0 +1,106 @@ +package hdm.mi.growbros.service; + +import hdm.mi.growbros.config.UserTestData; +import hdm.mi.growbros.exceptions.PlantNotFoundException; +import hdm.mi.growbros.models.WishListEntry; +import hdm.mi.growbros.models.plant.Plant; +import hdm.mi.growbros.models.user.Role; +import hdm.mi.growbros.models.user.User; +import hdm.mi.growbros.repositories.PlantRepository; +import hdm.mi.growbros.repositories.WishListRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.Sort; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@SpringBootTest +@ActiveProfiles("test") +public class WishListServiceTest { + + @MockBean + private WishListRepository wishListRepository; + @MockBean + private PlantRepository plantRepository; + + @Autowired + private WishListService wishListService; + + @MockBean + private TestEntityManager entityManager; + private User user1; + + @BeforeEach + void setupEach() { + user1 = User.builder() + .email("user1@gmail.com") + .role(Role.USER) + .password("safePassword123!") + .build(); + entityManager.persist(user1); + } + + @Test + void shouldThrow_whenAddingPlant_thatDoesNotExist() { + //setup + when(plantRepository.findById(100L)).thenReturn(Optional.empty()); + + assertThrows(PlantNotFoundException.class, () -> wishListService.addPlantToWishList(100L, UserTestData.getUser1())); + } + + @Test + void shouldReturn_whenAddingExistingPlant() { + //setup + Plant plant = Plant.builder().name("Plant!").build(); + when(plantRepository.findById(1L)).thenReturn(Optional.of(plant)); + when(wishListRepository.countByUser(user1)).thenReturn(1); + + //assert + wishListService.addPlantToWishList(1L, user1); + assertEquals(1, wishListService.countByUser(user1)); + } + + @Test + void shouldReturnEntries_inCorrectOrder() { + //setup + Plant p1 = Plant.builder().name("AAA").build(); + Plant p2 = Plant.builder().name("BBB").build(); + Plant p3 = Plant.builder().name("CCC").build(); + var orderedByNameMock = List.of(p1, p2, p3); + var orderedByCreatedMock = List.of(p2, p1, p3); + var orderedByPlantDateMock = List.of(p3, p2, p1); + var orderedByCurrentPlantDateMock = List.of(p2); + + User user = UserTestData.getUser1(); + + when(wishListRepository.findByUser(user, Sort.by("createdAt").descending())).thenReturn(mapPlantsToWishListEntries(orderedByCreatedMock, user)); + when(wishListRepository.findByUser(user, Sort.by("plant.name"))).thenReturn(mapPlantsToWishListEntries(orderedByNameMock, user)); + when(wishListRepository.findAllByNearestPlantingWeek(eq(user), any(int.class))).thenReturn(mapPlantsToWishListEntries(orderedByPlantDateMock, user)); + when(wishListRepository.findByCurrentPlantingWeek(eq(user), any(int.class))).thenReturn(mapPlantsToWishListEntries(orderedByCurrentPlantDateMock, user)); + + assertAll( + () -> assertEquals(orderedByNameMock, wishListService.getWishedPlants(user, null)), + () -> assertEquals(orderedByNameMock, wishListService.getWishedPlants(user, "random stuff")), + () -> assertEquals(orderedByCreatedMock, wishListService.getWishedPlants(user, "createdAt")), + () -> assertEquals(orderedByPlantDateMock, wishListService.getWishedPlants(user, "plantDate")), + () -> assertEquals(orderedByCurrentPlantDateMock, wishListService.getWishedPlants(user, "currentPlantable")) + ); + } + + private List<WishListEntry> mapPlantsToWishListEntries(List<Plant> plants, User user) { + return plants.stream() + .map(plant -> WishListEntry.builder().plant(plant).id(0L).user(user).build()) + .toList(); + } +} \ No newline at end of file