diff options
Diffstat (limited to 'ufund-api')
32 files changed, 1572 insertions, 406 deletions
diff --git a/ufund-api/data/cupboard.json b/ufund-api/data/cupboard.json index bb7ec03..abc2293 100644 --- a/ufund-api/data/cupboard.json +++ b/ufund-api/data/cupboard.json @@ -1,3 +1 @@ -[ -    {"name":"Money for coral","id":1,"maxGoal":100.0,"type":"MONETARY","filterAttributes":null,"Current":0.0} -]
\ No newline at end of file +[{"name":"Jellyfish Hats","id":26,"maxGoal":10.0,"type":"MONETARY","filterAttributes":["#savethejellyfish","Clothing","Storefront"],"current":0.0},{"name":"Pollution Re-Filtering","id":27,"maxGoal":1.0E7,"type":"MONETARY","filterAttributes":["Cleanup","Donations","Third-Party"],"current":0.0},{"name":"Coral re-re-habilitation","id":28,"maxGoal":10000.0,"type":"MONETARY","filterAttributes":["Preservation","#savethecoral","#helloPhil"],"current":0.0}]
\ No newline at end of file diff --git a/ufund-api/data/userAuths.json b/ufund-api/data/userAuths.json new file mode 100644 index 0000000..fea8a6f --- /dev/null +++ b/ufund-api/data/userAuths.json @@ -0,0 +1 @@ +[{"key":"e48872fa-b89f-494a-b681-11a809d32ff4","username":"phil","expiration":"2025-04-14T17:20:23.265745224"},{"key":"31fcbc15-9902-41d2-8d6f-5b0e40ebddd2","username":"phil","expiration":"2025-04-14T16:45:41.082560826"},{"key":"3fdd4e7e-bc59-4e3a-ba5c-177d0833022a","username":"sowgro","expiration":"2025-04-14T18:35:26.42935739"},{"key":"13d12a6d-6825-4c1d-8b22-ba960de140b8","username":"phil","expiration":"2025-04-14T17:20:58.531711142"},{"key":"a07ae51f-f80b-4001-95f1-48c11d4917a4","username":"phil","expiration":"2025-04-05T15:04:30.900359001"},{"key":"cc49c007-fd36-4828-b8fa-f5b85ad0676d","username":"phil","expiration":"2025-04-14T16:46:12.80566798"},{"key":"da61796e-402a-4a80-88ae-7607a37989a4","username":"phil","expiration":"2025-04-14T17:07:10.618039573"},{"key":"e14f8ee5-5780-4b9b-bf34-7a41c2bbfcb4","username":"phil","expiration":"2025-04-05T13:46:10.90733016"},{"key":"d7cef571-0f76-49fe-941f-ecbeae69557a","username":"phil","expiration":"2025-04-05T15:14:00.363201102"},{"key":"125a4847-3a1c-4834-961f-7f96e997f92e","username":"sowgro","expiration":"2025-04-14T18:35:38.922687686"},{"key":"3fc557b6-0306-4779-9b74-b7292a5cf1cc","username":"phil","expiration":"2025-04-14T16:06:08.564069822"},{"key":"77392d17-6e0c-45ec-857d-6595a55ddd97","username":"phil","expiration":"2025-04-14T16:06:48.335542315"},{"key":"eeea7b02-7265-4a26-96de-a8ad1860c533","username":"phil","expiration":"2025-03-31T23:04:47.455490668"},{"key":"e121c7c6-e534-4fde-8a78-4f175e9db9c8","username":"phil","expiration":"2025-04-14T17:23:23.218442063"},{"key":"af38add5-b100-4b96-9ffb-5afaccd59979","username":"adf","expiration":"2025-04-14T18:18:47.670506361"},{"key":"2aeaab28-99c9-4b45-bdef-82096c70945e","username":"phil","expiration":"2025-04-14T17:19:48.552121268"},{"key":"58e4e2a2-3a36-4fd6-8bb1-ad0831664d01","username":"phil","expiration":"2025-04-12T23:17:42.638952959"},{"key":"4df8bb43-f597-49ca-863a-6e0da5280d79","username":"phil","expiration":"2025-04-14T01:13:53.799331844"},{"key":"ad6d92d4-c496-407c-823a-edaa386e67ed","username":"phil","expiration":"2025-04-14T17:07:36.032623002"},{"key":"718be1e2-cfc7-44a6-b3c6-965684d1d0a9","username":"adf","expiration":"2025-04-14T18:35:58.888847176"},{"key":"85319427-4603-4a16-af33-2e9525dda8c0","username":"phil","expiration":"2025-04-14T00:39:34.952183453"},{"key":"f5f53053-ef5e-4850-93a0-3dc20646f78b","username":"sowgro","expiration":"2025-04-14T18:11:29.438554549"},{"key":"efc531fb-ab24-4d5a-a2f5-7f4ede74819f","username":"phil","expiration":"2025-04-13T19:41:51.017327545"},{"key":"f1d6a110-4232-4ef3-b6ec-9a2962664158","username":"phil","expiration":"2025-04-14T17:23:40.834526839"}]
\ No newline at end of file diff --git a/ufund-api/data/users.json b/ufund-api/data/users.json index 4e98a14..8543a55 100644 --- a/ufund-api/data/users.json +++ b/ufund-api/data/users.json @@ -1 +1,14 @@ -[{"name":"steve","password":null}]
\ No newline at end of file +[ +  { +    "username": "phil", +    "passwordHash": -1054080181, +    "basket": [], +    "type": "HELPER" +  }, +  { +    "username": "admin", +    "passwordHash": 92668751, +    "basket": [], +    "type": "MANAGER" +  } +]
\ No newline at end of file diff --git a/ufund-api/pom.xml b/ufund-api/pom.xml index ce96d60..d2e8fb8 100644 --- a/ufund-api/pom.xml +++ b/ufund-api/pom.xml @@ -73,8 +73,10 @@  				<artifactId>jacoco-maven-plugin</artifactId>  				<version>${jacoco.version}</version>  				<configuration> -					<destfile>/target/coverage-reports/jacoco-unit.exec</destfile> -					<datafile>/target/coverage-reports/jacoco-unit.exec</datafile> +					<!-- not sure what these were supposed to do, but 'datafile' is not a +					configuration key for jacoco, and changing it to 'dataFile' breaks the build --> +					<!-- <datafile>/target/coverage-reports/jacoco-unit.exec</datafile>--> +					<!-- <datafile>/target/coverage-reports/jacoco-unit.exec</datafile>-->  				</configuration>  				<executions>  					<execution> diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/DuplicateKeyException.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/DuplicateKeyException.java new file mode 100644 index 0000000..69ce6c0 --- /dev/null +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/DuplicateKeyException.java @@ -0,0 +1,7 @@ +package com.ufund.api.ufundapi; + +public class DuplicateKeyException extends Exception { +    public DuplicateKeyException(String message) { +        super(message); +    } +} diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java new file mode 100644 index 0000000..b46d4ee --- /dev/null +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java @@ -0,0 +1,62 @@ +package com.ufund.api.ufundapi.controller; + +import java.io.IOException; +import java.util.Map; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ufund.api.ufundapi.service.AuthService; + +@RestController +@RequestMapping("auth") +public class AuthController { +    private final AuthService authService; + +    public AuthController(AuthService authService) { +        this.authService = authService; +    } + +    /** +     * Attempts to log in as a user +     * +     * @param params A json object in the format {username: string, password: string} +     * @return An api key and status OK if the authentication was successful, +     * Status UNAUTHORIZED if the authentication failed and INTERNAL SERVER ERROR otherwise. +     */ +    @PostMapping("") +    public ResponseEntity<String> login(@RequestBody Map<String, String> params) { +        String username = params.get("username"); +        String password = params.get("password"); +        try { +            String key = authService.login(username, password); +            return new ResponseEntity<>(key, HttpStatus.OK); +        } catch (IllegalAccessException e) { +            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); +        } catch (IOException ex) { +            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); +        } +    } + +    /** +     * Logs out the current user +     * +     * @param key The API sent by the client in the header +     * @return OK if the user was successfully logged out, INTERNAL_SERVER_ERROR otherwise. +     */ +    @DeleteMapping("") +    public ResponseEntity<Object> logout(@RequestHeader("jelly-api-key") String key) { +        try { +            authService.logout(key); +            return new ResponseEntity<>(HttpStatus.OK); +        } catch (IOException e) { +            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); +        } +    } +} diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java index faaa98a..9592490 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java @@ -1,6 +1,7 @@  package com.ufund.api.ufundapi.controller;  import java.io.IOException; +import java.util.Map;  import java.util.logging.Level;  import java.util.logging.Logger; @@ -17,37 +18,48 @@ import org.springframework.web.bind.annotation.RequestParam;  import org.springframework.web.bind.annotation.RestController;  import com.ufund.api.ufundapi.model.Need; -import com.ufund.api.ufundapi.persistence.CupboardDAO; +import com.ufund.api.ufundapi.model.Need.GoalType; +import com.ufund.api.ufundapi.service.CupboardService; +import com.ufund.api.ufundapi.DuplicateKeyException;  @RestController  @RequestMapping("cupboard")  public class CupboardController {      private static final Logger LOG = Logger.getLogger(CupboardController.class.getName()); -    private final CupboardDAO cupboardDAO; +    private final CupboardService cupboardService;      /**       * Create a cupboard controller to receive REST signals       * -     * @param cupboardDAO The Data Access Object +     * @param cupboardService The Data Access Object       */ -    public CupboardController(CupboardDAO cupboardDAO) { -        this.cupboardDAO = cupboardDAO; +    public CupboardController(CupboardService cupboardService) { +        this.cupboardService = cupboardService;      }      /**       * Creates a Need with the provided object       * -     * @param need The need to create -     * @return OK response and the need if it was successful, INTERNAL_SERVER_ERROR otherwise +     * @param params The need to create +     * @return OK response and the need if it was successful, +     *         CONFLICT if another need with the same name exists +     *         UNPROCESSABLE_ENTITY if the need contains bad data +     *         INTERNAL_SERVER_ERROR otherwise       */      @PostMapping("") -    public ResponseEntity<Need> createNeed(@RequestBody Need need) { +    public ResponseEntity<Need> createNeed(@RequestBody Map<String, Object> params) { +        System.out.println(params); +        String name = (String) params.get("name"); +        int maxGoal = (int) params.get("maxGoal"); +        Need.GoalType goalType = GoalType.valueOf((String) params.get("type")); +          try { -            if (need.getMaxGoal() <= 0) { -                return new ResponseEntity<>(HttpStatus.BAD_REQUEST); -            } -            cupboardDAO.createNeed(need); +            Need need = cupboardService.createNeed(name, maxGoal, goalType);              return new ResponseEntity<>(need, HttpStatus.OK); +        } catch (DuplicateKeyException ex) { +            return new ResponseEntity<>(HttpStatus.CONFLICT); +        } catch (IllegalArgumentException ex) { +            return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY);          } catch (IOException ex) {              return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);          } @@ -66,7 +78,7 @@ public class CupboardController {          LOG.info("GET /needs");          try { -            Need[] needs = cupboardDAO.getNeeds(); +            Need[] needs = cupboardService.getNeeds();              return new ResponseEntity<>(needs, HttpStatus.OK);          } catch (IOException e) {              LOG.log(Level.SEVERE, e.getLocalizedMessage()); @@ -90,8 +102,8 @@ public class CupboardController {          LOG.info("GET /need/?name="+name);          try { -            Need[] needArray = cupboardDAO.findNeeds(name); -            return new ResponseEntity<>(needArray, HttpStatus.OK); +            Need[] needs = cupboardService.searchNeeds(name); +            return new ResponseEntity<>(needs, HttpStatus.OK);          } catch (IOException e) {              LOG.log(Level.SEVERE,e.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); @@ -103,23 +115,20 @@ public class CupboardController {       *        * @param id The id used to locate the {@link Need need}       *  -     * @return ResponseEntity with {@link Need need} object and HTTP status of OK if -     *         found<br> +     * @return ResponseEntity with {@link Need need} object and HTTP status of OK if found<br>       *         ResponseEntity with HTTP status of NOT_FOUND if not found<br> -     *         ResponseEntity with HTTP status of INTERNAL_SERVER_ERROR otherwise       */      @GetMapping("/{id}")      public ResponseEntity<Need> getNeed(@PathVariable int id) {          LOG.log(Level.INFO, "GET /need/{0}", id);          try { -            Need need = cupboardDAO.getNeed(id); +            Need need = cupboardService.getNeed(id);              if (need != null) {                  return new ResponseEntity<>(need, HttpStatus.OK);              } else {                  return new ResponseEntity<>(HttpStatus.NOT_FOUND);              } -          } catch (IOException e) {              LOG.log(Level.SEVERE, e.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); @@ -133,13 +142,20 @@ public class CupboardController {       * @param need The need to update       * @return OK response and the need if it was successful, or INTERNAL_SERVER_ERROR if there was an issue       */ - -    @PutMapping("") -    public ResponseEntity<Need> updateNeed(@RequestBody Need need) { +    @PutMapping("/{id}") +    public ResponseEntity<Need> updateNeed(@RequestBody Need need, @PathVariable int id) {          try { -            need = cupboardDAO.updateNeed(need); -            return new ResponseEntity<>(need, HttpStatus.OK); -        } catch (IOException e) { +            Need updatedNeed = cupboardService.updateNeed(need, id); +            if (updatedNeed != null) { +                return new ResponseEntity<>(need, HttpStatus.OK); +            } else { +                return new ResponseEntity<>(HttpStatus.NOT_FOUND); +            } +        } catch (IllegalArgumentException ex) { +            ex.printStackTrace(); +            return new ResponseEntity<>(HttpStatus.BAD_REQUEST); +        } catch (IOException ex) { +            ex.printStackTrace();              return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);          }      } @@ -153,9 +169,9 @@ public class CupboardController {      @DeleteMapping("/{id}")      public ResponseEntity<Need> deleteNeed(@PathVariable int id) {          try { -            if (cupboardDAO.getNeed(id) != null) { -                cupboardDAO.deleteNeed(id); -                return new ResponseEntity<>(HttpStatus.OK); +            Need need = cupboardService.getNeed(id); +            if (cupboardService.deleteNeed(id)) { +                return new ResponseEntity<>(need, HttpStatus.OK);              } else {                  return new ResponseEntity<>(HttpStatus.NOT_FOUND);              }  diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java index 4e5f156..adf17a1 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java @@ -1,6 +1,8 @@  package com.ufund.api.ufundapi.controller;  import java.io.IOException; +import java.security.InvalidParameterException; +import java.util.Map;  import java.util.logging.Level;  import java.util.logging.Logger; @@ -12,43 +14,47 @@ import org.springframework.web.bind.annotation.PathVariable;  import org.springframework.web.bind.annotation.PostMapping;  import org.springframework.web.bind.annotation.PutMapping;  import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader;  import org.springframework.web.bind.annotation.RequestMapping;  import org.springframework.web.bind.annotation.RestController; +import com.ufund.api.ufundapi.DuplicateKeyException;  import com.ufund.api.ufundapi.model.User; -import com.ufund.api.ufundapi.persistence.UserDAO; +import com.ufund.api.ufundapi.service.AuthService; +import com.ufund.api.ufundapi.service.UserService;  @RestController  @RequestMapping("users")  public class UserController { -    private static final Logger LOG = Logger.getLogger(CupboardController.class.getName()); -    private final UserDAO UserDAO; +    private static final Logger LOG = Logger.getLogger(UserController.class.getName()); +    private final UserService userService; +    private final AuthService authService;     -    /** -     * Create a user controller to receive REST signals -     * -     * @param userDAO The Data Access Object -     */ -    public UserController(UserDAO userDAO) { -        this.UserDAO = userDAO; +    public UserController(UserService userService, AuthService authService) { +        this.userService = userService; +        this.authService = authService;      }      /**       * Creates a User with the provided object -     * -     * @param user The user to create +     * @param params A map consisting of the parameters for a user       * @return OK response and the user if it was successful, INTERNAL_SERVER_ERROR       *         otherwise       */      @PostMapping("") -    public ResponseEntity<User> createUser(@RequestBody User user) { +    public ResponseEntity<User> createUser(@RequestBody Map<String, String> params) { +        String username = params.get("username"); +        String password = params.get("password"); +          try { -            if (UserDAO.createUser(user) != null) { +            User user = userService.createUser(username, password); +            if (user != null) {                  return new ResponseEntity<>(user, HttpStatus.CREATED);              } else {                  return new ResponseEntity<>(HttpStatus.CONFLICT);              } - +        } catch (DuplicateKeyException ex) { +            return new ResponseEntity<>(HttpStatus.CONFLICT);          } catch (IOException ex) {              return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);          } @@ -57,23 +63,27 @@ public class UserController {      /**       * Responds to the GET request for a {@linkplain User user} for the given id       *  +     * @param username The name of the user +     * @param key      The authentication key of the user       * @return ResponseEntity with {@link User user} object and HTTP status of OK if       *         found<br>       *         ResponseEntity with HTTP status of NOT_FOUND if not found<br>       *         ResponseEntity with HTTP status of INTERNAL_SERVER_ERROR otherwise       */ -    @GetMapping("/{name}") -    public ResponseEntity<User> getUser(@PathVariable String name) { -        LOG.log(Level.INFO, "GET /user/{0}", name); +    @GetMapping("/{username}") +    public ResponseEntity<User> getUser(@PathVariable String username, @RequestHeader("jelly-api-key") String key) { +        LOG.log(Level.INFO, "GET /user/{0}", username);          try { -            User user = UserDAO.getUser(name); +            authService.authenticate(username, key); +            User user = userService.getUser(username);              if (user != null) { -                return new ResponseEntity<>(user, HttpStatus.OK); +                return new ResponseEntity<>(user.withoutPasswordHash(), HttpStatus.OK);              } else {                  return new ResponseEntity<>(HttpStatus.NOT_FOUND);              } - +        } catch (IllegalAccessException ex) { +            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);          } catch (IOException e) {              LOG.log(Level.SEVERE, e.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); @@ -84,42 +94,53 @@ public class UserController {      /**       * Updates a User with the provided one       *  -     * @param user The user to update +     * @param user     The user to update +     * @param username The name of the user +     * @param key      The authentication key of the user       * @return OK response and the user if it was successful, or       *         INTERNAL_SERVER_ERROR if there was an issue       */ -    @PutMapping("/{name}") -    public ResponseEntity<User> updateUser(@RequestBody User user, @PathVariable String name) { +    @PutMapping("/{username}") +    public ResponseEntity<User> updateUser(@RequestBody User user, @PathVariable String username, @RequestHeader("jelly-api-key") String key) {          try { -            user = UserDAO.updateUser(user, name); +            authService.authenticate(username, key); +            user = userService.updateUser(user, username);              if (user != null) {                  return new ResponseEntity<>(user, HttpStatus.OK);              } else {                  return new ResponseEntity<>(HttpStatus.NOT_FOUND);              } - +        } catch (InvalidParameterException ex) { +            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);          } catch (IOException e) {              return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); +        } catch (IllegalAccessException e) { +            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);          }      }      /**       * Deletes a user with the desired name       *  -     * @param name The name of the user +     * @param username The name of the user +     * @param key      The authentication key of the user       * @return OK if the user was deleted, NOT_FOUND if the user was not found, or       *         INTERNAL_SERVER_ERROR if an error occurred       */ -    @DeleteMapping("/{name}") -    public ResponseEntity<User> deleteUser(@PathVariable String name) { +    @DeleteMapping("/{username}") +    public ResponseEntity<Boolean> deleteUser(@PathVariable String username, @RequestHeader("jelly-api-key") String key) { +          try { -            if (UserDAO.deleteUser(name)) { +            authService.authenticate(username, key); +            if (userService.deleteUser(username)) {                  return new ResponseEntity<>(HttpStatus.OK);              } else {                  return new ResponseEntity<>(HttpStatus.NOT_FOUND);              }          } catch (IOException e) {              return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); +        } catch (IllegalAccessException e) { +            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);          }      } diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java index 2611357..c0e9214 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java @@ -14,7 +14,7 @@ public class Need {      @JsonProperty("filterAttributes") private String[] filterAttributes;      @JsonProperty("type") final private GoalType type;      @JsonProperty("maxGoal") private double maxGoal; -    @JsonProperty("Current") private double current; +    @JsonProperty("current") private double current;      /**       * Create a new need @@ -32,6 +32,19 @@ public class Need {      }      /** +     * Create a new need +     * +     * @param name    The name of the need +     * @param maxGoal The maximum goal for this need +     * @param type    The type of need (monetary, physical) +     */ +    public Need(String name, GoalType type, double maxGoal) { +        this.name = name; +        this.type = type; +        this.maxGoal = maxGoal; +    } + +    /**       * Create a deep copy of another need       *       * @param other The need to copy from diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java index 59f4c46..f08f9f0 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java @@ -7,46 +7,40 @@ import com.fasterxml.jackson.annotation.JsonProperty;  public class User { -    @JsonProperty("name") -    private final String name; -    @JsonProperty("passwordHash") -    private int passwordHash; -    @JsonProperty("basket") -    private final List<Need> basket; - -    /** -     * Create a new user -     *  -     * @param name The name of the user -     */ -    public User(String name) { -        this.name = name; -        basket = new ArrayList<>(); +    public enum UserType { +        HELPER, +        MANAGER      } +    @JsonProperty("username") private final String username; +    @JsonProperty("passwordHash") private int passwordHash; +    @JsonProperty("basket") private final List<Integer> basket; +    @JsonProperty("type") private final UserType type; +      /**       * Create a new user       *  -     * @param name   The name of the user +     * @param username   The name of the user       * @param basket A basket to copy from       */ -    public User(@JsonProperty("name") String name, @JsonProperty("basket") List<Need> basket) { -        this.name = name; +    public User(@JsonProperty("username") String username, @JsonProperty("passwordHash") int passwordHash, @JsonProperty("basket") List<Integer> basket, @JsonProperty("type") UserType userType) { +        this.username = username;          this.basket = basket; +        this.passwordHash = passwordHash; +        this.type = userType;      } -    /** -     * Create a deep copy of another user -     * -     * @param other The user to copy from -     */ -    public User(User other) { -        this.name = other.name; -        this.basket = other.basket; +    public static User create(String username, String password) { +        return new User( +                username, +                password.hashCode(), +                new ArrayList<>(), +                UserType.HELPER +        );      } -    public String getName() { -        return name; +    public String getUsername() { +        return username;      }      public boolean verifyPassword(String password) { @@ -54,15 +48,23 @@ public class User {      }      public void addToBasket(Need need) { -        basket.add(need); +        basket.add(need.getId());      } -    public Need[] getBasketNeeds() { -        return basket.toArray(Need[]::new); +    public Integer[] getBasketNeeds() { +        return basket.toArray(Integer[]::new);      }      public void removeBasketNeed(Need need) { -        basket.remove(need); +        basket.remove(need.getId()); +    } + +    public User withoutPasswordHash() { +        return new User(this.username, 0, this.basket, this.type); +    } + +    public UserType getType() { +        return type;      }  } diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/UserAuth.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/UserAuth.java new file mode 100644 index 0000000..1c11a28 --- /dev/null +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/UserAuth.java @@ -0,0 +1,43 @@ +package com.ufund.api.ufundapi.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.time.LocalDateTime; +import java.util.UUID; + +public class UserAuth { +    @JsonProperty("key") String key; +    @JsonProperty("username") String username; +    @JsonProperty("expiration") LocalDateTime expiration; + +    public UserAuth(@JsonProperty("key") String key, @JsonProperty("username") String username, @JsonProperty("expiration") LocalDateTime expiration) { +        this.key = key; +        this.expiration = expiration; +        this.username = username; +    } + +    /** +     * Generate a new user authentication profile +     * @param username the username the key will belong to +     * @return The new user authentication profile +     */ +    public static UserAuth generate(String username) { +        return new UserAuth( +                UUID.randomUUID().toString(), +                username, +                LocalDateTime.now().plusDays(30) +        ); +    } + +    public String getKey() { +        return key; +    } + +    public String getUsername() { +        return username; +    } + +    public LocalDateTime getExpiration() { +        return expiration; +    } +} diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardDAO.java index 1435410..c8285a0 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardDAO.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardDAO.java @@ -1,9 +1,9 @@  package com.ufund.api.ufundapi.persistence; -import com.ufund.api.ufundapi.model.Need; -  import java.io.IOException; +import com.ufund.api.ufundapi.model.Need; +  /**   * Defines the interface for Need object persistence   *  @@ -14,23 +14,10 @@ public interface CupboardDAO {       * Retrieves all {@linkplain Need needs}       *        * @return An array of {@link Need need} objects, may be empty -     *  -     * @throws IOException if an issue with underlying storage       */      Need[] getNeeds() throws IOException;      /** -     * Finds all {@linkplain Need needs} whose name contains the given text -     *  -     * @param targetName The text to match against -     *  -     * @return An array of {@link Need needs} whose names contains the given text, may be empty -     *  -     * @throws IOException if an issue with underlying storage -     */ -    Need[] findNeeds(String targetName) throws IOException; - -    /**       * Retrieves a {@linkplain Need need} with the given name       *        * @param id The ID of the {@link Need need} to get @@ -38,8 +25,6 @@ public interface CupboardDAO {       * @return a {@link Need need} object with the matching name       * <br>       * null if no {@link Need need} with a matching name is found -     *  -     * @throws IOException if an issue with underlying storage       */      Need getNeed(int id) throws IOException; @@ -54,7 +39,7 @@ public interface CupboardDAO {       *        * @throws IOException if an issue with underlying storage       */ -    Need createNeed(Need need) throws IOException; +    Need addNeed(Need need) throws IOException;      /**       * Updates and saves a {@linkplain Need need} diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDao.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java index 81ee7c0..521acae 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDao.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java @@ -11,18 +11,18 @@ import java.util.Map;  import java.util.TreeMap;  @Component -public class CupboardFileDao implements CupboardDAO { +public class CupboardFileDAO implements CupboardDAO {      private final Map<Integer, Need> needs; // cache      private final ObjectMapper objectMapper;      private static int nextId;      private final String filename; -    public CupboardFileDao(@Value("${cupboard.file}") String filename, ObjectMapper objectMapper) throws IOException { +    public CupboardFileDAO(@Value("${cupboard.file}") String filename, ObjectMapper objectMapper) throws IOException {          this.filename = filename;          this.objectMapper = objectMapper;          needs = new TreeMap<>(); -        load();  // load the heroes from the file +        load();      }      private synchronized static int nextId() { @@ -61,18 +61,6 @@ public class CupboardFileDao implements CupboardDAO {      }      /** -     * Returns an array of needs filtered by a search -     * -     * @param search The search substring -     * @return The requested array -     */ -    private Need[] getNeedsArray(String search) { -        return needs.values().stream() -                .filter(i -> i.getName().toLowerCase().contains(search.toLowerCase())) -                .toArray(Need[]::new); -    } - -    /**       * Saves the needs to json       *       * @return True if the save was successful, false otherwise @@ -93,13 +81,6 @@ public class CupboardFileDao implements CupboardDAO {      }      @Override -    public Need[] findNeeds(String targetName) { -        synchronized (needs) { -            return getNeedsArray(targetName); -        } -    } - -    @Override      public Need getNeed(int id) {          synchronized (needs) {              return needs.getOrDefault(id, null); @@ -107,7 +88,7 @@ public class CupboardFileDao implements CupboardDAO {      }      @Override -    public Need createNeed(Need need) throws IOException { +    public Need addNeed(Need need) throws IOException {          synchronized (needs) {              Need newNeed = new Need(need);              newNeed.setID(nextId()); diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthDAO.java new file mode 100644 index 0000000..355aae4 --- /dev/null +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthDAO.java @@ -0,0 +1,32 @@ +package com.ufund.api.ufundapi.persistence; + +import com.ufund.api.ufundapi.model.UserAuth; + +import java.io.IOException; + +public interface UserAuthDAO { + +    /** +     * Get a user authentication profile +     * +     * @param key The auth key +     * @return The authentication profile or null if there was none +     */ +    UserAuth getUserAuth(String key) throws IOException; + +    /** +     * Add a user authentication profile +     * +     * @param userAuth The user auth profile to add +     * @throws IOException Thrown on any file writing error +     */ +    void addUserAuth(UserAuth userAuth) throws IOException; + +    /** +     * Remove a user authentication profile +     * +     * @param key The key of the user auth profile to remove +     * @throws IOException Thrown on any file writing error +     */ +    void removeUserAuth(String key) throws IOException; +} diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java new file mode 100644 index 0000000..1fc1e92 --- /dev/null +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java @@ -0,0 +1,73 @@ +package com.ufund.api.ufundapi.persistence; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ufund.api.ufundapi.model.UserAuth; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +@Component +public class UserAuthFIleDAO implements UserAuthDAO { + +    private final Map<String, UserAuth> userAuthMap; +    private final ObjectMapper objectMapper; +    private final String filename; + +    public UserAuthFIleDAO(ObjectMapper objectMapper, @Value("${authKeys.file}") String filename) throws IOException { +        this.userAuthMap = new HashMap<>(); +        this.objectMapper = objectMapper; +        this.filename = filename; +        load(); +    } + +    /** +     * Loads the data from the file into the map +     * +     * @throws IOException Thrown if there was an issue reading the file +     */ +    private void load() throws IOException { +        userAuthMap.clear(); + +        UserAuth[] userAuthKeysArray = objectMapper.readValue(new File(filename), UserAuth[].class); + +        for (UserAuth userAuth : userAuthKeysArray) { +            userAuthMap.put(userAuth.getKey(), userAuth); +        } +    } + +    /** +     * Saves the data from the map into the json file +     * +     * @throws IOException Thrown on any problem writing the file +     */ +    private void save() throws IOException { +        objectMapper.writeValue(new File(filename), userAuthMap.values()); +    } + +    @Override +    public UserAuth getUserAuth(String key) { +        synchronized (userAuthMap) { +            return userAuthMap.get(key); +        } +    } + +    @Override +    public void addUserAuth(UserAuth userAuth) throws IOException { +        synchronized (userAuthMap) { +            userAuthMap.put(userAuth.getKey(), userAuth); +            save(); +        } +    } + +    @Override +    public void removeUserAuth(String key) throws IOException { +        synchronized (userAuthMap) { +            userAuthMap.remove(key); +            save(); +        } +    } +} diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserDAO.java index d456abc..29d46cf 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserDAO.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserDAO.java @@ -21,17 +21,17 @@ public interface UserDAO {      User[] getUsers() throws IOException;      /** -     * Retrieves a {@linkplain User user} with the given name +     * Retrieves a {@linkplain User user} with the given username       *  -     * @param id The ID of the {@link User user} to get +     * @param username The ID of the {@link User user} to get       *  -     * @return a {@link User user} object with the matching name +     * @return a {@link User user} object with the matching username       *         <br> -     *         null if no {@link User user} with a matching name is found +     *         null if no {@link User user} with a matching username is found       *        * @throws IOException if an issue with underlying storage       */ -    User getUser(String name) throws IOException; +    User getUser(String username) throws IOException;      /**       * Creates and saves a {@linkplain User user} @@ -44,25 +44,24 @@ public interface UserDAO {       *        * @throws IOException if an issue with underlying storage       */ -    User createUser(User user) throws IOException; +    User addUser(User user) throws IOException;      /**       * Updates and saves a {@linkplain User user}       *  -     * @param newUser {@link User user} object to be updated and saved -     * @param name {@link String name} name of object to be updated +     * @param user {@link User user} object to be updated and saved       *        * @return updated {@link User user} if successful, null if       *         {@link User user} could not be found       *        * @throws IOException if underlying storage cannot be accessed       */ -    User updateUser(User newUser, String name) throws IOException; +    User updateUser(User user) throws IOException;      /**       * Deletes a {@linkplain User user} with the given id       *  -     * @param id The id of the {@link User user} +     * @param username The id of the {@link User user}       *        * @return true if the {@link User user} was deleted       *         <br> @@ -70,5 +69,5 @@ public interface UserDAO {       *        * @throws IOException if underlying storage cannot be accessed       */ -    boolean deleteUser(String name) throws IOException; +    boolean deleteUser(String username) throws IOException;  } diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java index 18eec18..f17f8f2 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java @@ -2,8 +2,8 @@ package com.ufund.api.ufundapi.persistence;  import java.io.File;  import java.io.IOException; +import java.util.HashMap;  import java.util.Map; -import java.util.TreeMap;  import org.springframework.beans.factory.annotation.Value;  import org.springframework.stereotype.Component; @@ -21,7 +21,7 @@ public class UserFileDAO implements UserDAO {      public UserFileDAO(@Value("${users.file}") String filename, ObjectMapper objectMapper) throws IOException {          this.filename = filename;          this.objectMapper = objectMapper; -        users = new TreeMap<>(); +        users = new HashMap<>();          load(); // load the users from the file      } @@ -36,7 +36,7 @@ public class UserFileDAO implements UserDAO {          User[] usersArray = objectMapper.readValue(new File(filename), User[].class);          for (User user : usersArray) { -            users.put(user.getName(), user); +            users.put(user.getUsername(), user);          }      } @@ -47,100 +47,54 @@ public class UserFileDAO implements UserDAO {       * @throws IOException If there was an IO issue saving the file       */      private boolean save() throws IOException { -        User[] userArray = getUserArray(); - -        objectMapper.writeValue(new File(filename), userArray); +        objectMapper.writeValue(new File(filename), users.values());          return true;      } -    /** -     * Return an array of the needs -     * -     * @return An array of all the needs -     */ -    private User[] getUserArray() { -        return users.values().toArray(User[]::new); -    } -      @Override -    public User[] getUsers() throws IOException { +    public User[] getUsers() {          synchronized (users) { -            return getUserArray(); +            return users.values().toArray(User[]::new);          }      } -    /** -     * Return the user with the String name name or null otherwise -     *  -     * @param name Name of desired user -     *  -     * @return Desired user, null otherwise -     * @throws IOException If there was an IO issue saving the file -     */      @Override -    public User getUser(String name) throws IOException { +    public User getUser(String username) {          synchronized (users) { -            return users.getOrDefault(name, null); +            return users.getOrDefault(username, null);          }      } -    /** -     * Create a User user -     *  -     * @param user User to create -     *  -     * @return Desired created user -     * @throws IOException If there was an IO issue saving the file -     */      @Override -    public User createUser(User user) throws IOException { +    public User addUser(User user) throws IOException {          synchronized (users) { -            if (getUser(user.getName()) == null) { -                User newUser = new User(user); -                users.put(newUser.getName(), newUser); -                save(); -                return newUser; -            } else { -                return null; +            var res = users.putIfAbsent(user.getUsername(), user); +            save(); +            if (res == null) { +                return user;              } +            return res;          }      } -    /** -     * Update a user that matches the supplied name -     *  -     * @param name    The name of the user -     * @param newUser New user data -     *  -     * @return Desired user, null otherwise -     * @throws IOException If there was an IO issue saving the file -     */      @Override -    public User updateUser(User newUser, String name) throws IOException { +    public User updateUser(User user) throws IOException {          synchronized (users) { -            if (users.containsKey(name)) { -                users.put(name, newUser); +            if (users.containsKey(user.getUsername())) { +                users.put(user.getUsername(), user);                  save(); -                return newUser; +                return user;              } else {                  return null;              }          }      } -    /** -     * Delete a user matching the name -     *  -     * @param name The name of the user -     *  -     * @return True if deleted, false otherwise -     * @throws IOException If there was an IO issue saving the file -     */      @Override -    public boolean deleteUser(String name) throws IOException { +    public boolean deleteUser(String username) throws IOException {          synchronized (users) { -            if (users.containsKey(name)) { -                users.remove(name); +            if (users.containsKey(username)) { +                users.remove(username);                  return save();              } else {                  return false; diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java new file mode 100644 index 0000000..87a16a6 --- /dev/null +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java @@ -0,0 +1,70 @@ +package com.ufund.api.ufundapi.service; + +import com.ufund.api.ufundapi.model.User; +import com.ufund.api.ufundapi.model.UserAuth; +import com.ufund.api.ufundapi.persistence.UserAuthDAO; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class AuthService { + +    private final UserAuthDAO userAuthDAO; +    private final UserService userService; + +    public AuthService(UserAuthDAO userAuthDAO, UserService userService) { +        this.userAuthDAO = userAuthDAO; +        this.userService = userService; +    } + +    /** +     * Check if the provided key has access to the provided user. +     * +     * @param targetUsername The targetUsername of the user trying to be accessed. +     * @param key The api key obtained by the client from logging in. +     * @throws IllegalAccessException Thrown if access was denied to the user. +     */ +    public void authenticate(String targetUsername, String key) throws IllegalAccessException, IOException { +        var userAuth = userAuthDAO.getUserAuth(key); +        if (userAuth == null) { +            throw new IllegalAccessException("Unauthenticated"); +        } +// +//        var username = userAuth.getUsername(); +//        var userType = userService.getUser(username).getType(); +//        if (!username.equals(targetUsername) && userType != User.UserType.MANAGER) { +//            throw new IllegalAccessException("Unauthorized"); +//        } +    } + +    /** +     * Attempt to log in with the provided credentials +     * +     * @param username The username of the user +     * @param password The password of the user +     * @return An API key if the authentication was successful. +     * @throws IllegalAccessException Thrown if the username or password was incorrect +     * @throws IOException If there was an issue saving the authentication +     */ +    public String login(String username, String password) throws IllegalAccessException, IOException { +        var usr = userService.getUser(username); +        if (usr == null || !usr.verifyPassword(password)) { +            throw new IllegalAccessException("Unauthorized"); +        } +        var userAuth = UserAuth.generate(username); +        userAuthDAO.addUserAuth(userAuth); +        return userAuth.getKey(); +    } + +    /** +     * Logs out the current user +     * +     * @param key The API key to of the client +     * @throws IOException Thrown if there was an error saving the authentication +     */ +    public void logout(String key) throws IOException { +        userAuthDAO.removeUserAuth(key); +    } + +} diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java new file mode 100644 index 0000000..2398745 --- /dev/null +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java @@ -0,0 +1,109 @@ +package com.ufund.api.ufundapi.service; + +import java.io.IOException; +import java.util.Arrays; + +import org.springframework.stereotype.Component; + +import com.ufund.api.ufundapi.DuplicateKeyException; +import com.ufund.api.ufundapi.model.Need; +import com.ufund.api.ufundapi.persistence.CupboardDAO; + +@Component +public class CupboardService { + +    private final CupboardDAO cupboardDAO; + +    public CupboardService(CupboardDAO cupboardDAO) { +        this.cupboardDAO = cupboardDAO; +    } + +    /** +     * Creates a new Need +     * +     * @param name The name of the need to create +     * @param maxGoal The max goal of the new need +     * @param goalType The goal type of the new need +     * @return The need that was created +     * @throws IOException Thrown if there was any issue saving the data +     * @throws DuplicateKeyException If there already exists a need with the same name +     */ +    public Need createNeed(String name, double maxGoal, Need.GoalType goalType) throws IOException, DuplicateKeyException { + +        if (maxGoal <= 0) { +            throw new IllegalArgumentException("Max Goal must be greater than zero"); +        } + +        for (Need searchNeed : cupboardDAO.getNeeds()) { +            if (searchNeed.getName().equalsIgnoreCase(name)) { +                throw new DuplicateKeyException("Duplicate names are not allowed"); +            } +        } + +        Need need = new Need(name, goalType, maxGoal); +        return cupboardDAO.addNeed(need); + +    } + +    /** +     * Get all the needs in the cupboard +     * +     * @return An array containing all needs +     * @throws IOException Thrown if there was any issue saving the data +     */ +    public Need[] getNeeds() throws IOException { +        return cupboardDAO.getNeeds(); +    } + +    /** +     * Returns an array of needs filtered by a search +     * +     * @param search The search substring +     * @return The requested array +     * @throws IOException Thrown if there was any issue saving the data +     */ +    public Need[] searchNeeds(String search) throws IOException { +        return Arrays.stream(cupboardDAO.getNeeds()) +                .filter(i -> i.getName().toLowerCase().contains(search.toLowerCase())) +                .toArray(Need[]::new); +    } + +    /** +     * Gets a need with the specified ID +     * +     * @param id the ID of the need +     * @return The resulting Need or null if the need was not found +     */ +    public Need getNeed(int id) throws IOException { +        return cupboardDAO.getNeed(id); +    } + +    /** +     * Updates a need +     * +     * @param id The ID of the need to update +     * @param need The need object to set (note: the ID is ignored) +     * @return The updated need object +     * @throws IOException Thrown if there was an issue saving the changes +     */ +    public Need updateNeed(Need need, int id) throws IOException { +        if (need.getId() != id) { +            throw new IllegalArgumentException("ID in URL and body must match"); +        } +        if (need.getMaxGoal() <= 0) { +            throw new IllegalArgumentException("Goal must be greater than 0"); +        } +        return cupboardDAO.updateNeed(need); +    } + +    /** +     * Delete a need from the cupboard +     * +     * @param id the ID of the need +     * @return True if the need was deleted +     * @throws IOException Thrown on any problem removing the need +     */ +    public boolean deleteNeed(int id) throws IOException { +        return cupboardDAO.deleteNeed(id); +    } +} diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java new file mode 100644 index 0000000..935ee72 --- /dev/null +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java @@ -0,0 +1,72 @@ +package com.ufund.api.ufundapi.service; + +import java.io.IOException; + +import com.ufund.api.ufundapi.DuplicateKeyException; +import com.ufund.api.ufundapi.model.User; +import com.ufund.api.ufundapi.persistence.UserDAO; +import org.springframework.stereotype.Component; + +@Component +public class UserService { + +    private final UserDAO userDAO; + +    public UserService(UserDAO userDao) { +        this.userDAO = userDao; +    } + +    /** +     * Creates a new user +     * +     * @param username The username of the user +     * @param password The password of the user +     * @return The created user object +     * @throws IOException Thrown on any problem saving the file +     */ +    public User createUser(String username, String password) throws IOException, DuplicateKeyException { +        if (userDAO.getUser(username) != null) { +            throw new DuplicateKeyException("A user with this name already exists"); +        } +        User user = User.create(username, password); +        return userDAO.addUser(user); +    } + +    /** +     * Gets a user with the given username +     * +     * @param username The username of the user +     * @return The user object with that username +     * @throws IOException If there was any problem saving the file +     */ +    public User getUser(String username) throws IOException { +        return userDAO.getUser(username); +    } + +    /** +     * Updates a user +     * +     * @param user The ID of the user to update +     * @param username The user object to set (note: the ID is ignored) +     * @return The updated user object +     * @throws IOException Thrown if there was any issue saving the data +     */ +    public User updateUser(User user, String username) throws IOException { +        if (!user.getUsername().equals(username)) { +            throw new IllegalArgumentException("ID in URL and body must match"); +        } +        return userDAO.updateUser(user); +    } + +    /** +     * Deletes a user +     * +     * @param username The username of the user to delete +     * @return True if the user was deleted +     * @throws IOException Thrown if there was any issue saving the data +     */ +    public boolean deleteUser(String username) throws IOException { +        return userDAO.deleteUser(username); +    } +     +} diff --git a/ufund-api/src/main/resources/application.properties b/ufund-api/src/main/resources/application.properties index a866f98..c742063 100644 --- a/ufund-api/src/main/resources/application.properties +++ b/ufund-api/src/main/resources/application.properties @@ -1,8 +1,8 @@ -# rename to application.properties  server.error.include-message=always  cupboard.file=data/cupboard.json  users.file=data/users.json +authKeys.file=data/userAuths.json  spring.jackson.mapper.auto-detect-getters=false  spring.jackson.mapper.auto-detect-setters=false diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/AuthControllerTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/AuthControllerTest.java new file mode 100644 index 0000000..3d4637d --- /dev/null +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/AuthControllerTest.java @@ -0,0 +1,104 @@ +package com.ufund.api.ufundapi.controller; + +import java.io.IOException; +import java.util.Map; +import static java.util.Map.entry; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.mockito.ArgumentMatchers.any; +import org.mockito.Mockito; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.ufund.api.ufundapi.service.AuthService; + +@RequestMapping("auth") +public class AuthControllerTest { + +    private AuthController authController; +    private AuthService mockAuthService; +    private Map<String, String> authMap; + +    @BeforeEach +    private void setupAuthController() { +        mockAuthService = mock(AuthService.class); +        authController = new AuthController(mockAuthService); + +        authMap = Map.ofEntries( +                entry("Bob", "123") +        ); +    } + +    @Test +    public void testLogin() throws IllegalAccessException, IOException { +        // Setup +        String key = "123"; + +        // Mock +        when(mockAuthService.login(any(), any())).thenReturn(key); + +        // Invoke +        ResponseEntity<String> response = authController.login(authMap); + +        // Analyze +        assertEquals(HttpStatus.OK, response.getStatusCode()); +        assertEquals(key, response.getBody()); +    } + +    @Test +    public void testLoginUnauthorized() throws IllegalAccessException, IOException { +        // Mock +        when(mockAuthService.login(any(), any())).thenThrow(IllegalAccessException.class); + +        // Invoke +        ResponseEntity<String> response = authController.login(authMap); + +        // Analyze +        assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); +    } + +    @Test +    public void testLoginIOException() throws IllegalAccessException, IOException { +        // Mock +        when(mockAuthService.login(any(), any())).thenThrow(IOException.class); + +        // Invoke +        ResponseEntity<String> response = authController.login(authMap); + +        // Analyze +        assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); +    } + +    @Test +    public void testLogout() throws IllegalAccessException, IOException { +        // Setup +        String key = "123"; + +        // Invoke +        ResponseEntity<Object> response = authController.logout(key); + +        // Analyze +        assertEquals(HttpStatus.OK, response.getStatusCode()); +    } + +    @Test +    public void testLogoutIOException() throws IllegalAccessException, IOException { +        // Setup +        String key = "123"; + +        // Mock +        doThrow(new IOException()).when(mockAuthService).logout(key); + +        // Invoke +        ResponseEntity<Object> response = authController.logout(key); + +        // Analyze +        assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); +    } +} diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java index 839c518..94f93cb 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java @@ -1,6 +1,8 @@  package com.ufund.api.ufundapi.controller;  import java.io.IOException; +import java.util.Map; +import static java.util.Map.entry;  import static org.junit.jupiter.api.Assertions.assertArrayEquals;  import static org.junit.jupiter.api.Assertions.assertEquals; @@ -11,54 +13,74 @@ import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.when;  import org.springframework.http.HttpStatus; +import com.ufund.api.ufundapi.DuplicateKeyException;  import com.ufund.api.ufundapi.model.Need; -import com.ufund.api.ufundapi.persistence.CupboardFileDao; +import com.ufund.api.ufundapi.model.Need.GoalType; +import com.ufund.api.ufundapi.service.CupboardService;  public class CupboardControllerTest {      private CupboardController cupboardController; -    private CupboardFileDao mockCupboardDAO; +    private CupboardService mockCupboardService;      @BeforeEach      public void setupCupboardDAO() { -        mockCupboardDAO = mock(CupboardFileDao.class); -        cupboardController = new CupboardController(mockCupboardDAO); +        mockCupboardService = mock(CupboardService.class); +        cupboardController = new CupboardController(mockCupboardService);      }      @Test -    public void createNeed() throws IOException { -        var need = new Need("Name", 1, 100, Need.GoalType.MONETARY); -        when(mockCupboardDAO.createNeed(need)).thenReturn(need); +    public void createNeed() throws IOException, DuplicateKeyException { +        String name = "Test"; +        int maxGoal = 100; +        GoalType type = Need.GoalType.MONETARY; +        var need = new Need(name, type, maxGoal); +        when(mockCupboardService.createNeed(name, maxGoal, type)).thenReturn(need); + -        var res = cupboardController.createNeed(need); +        Map<String, Object> needMap = Map.ofEntries( +                entry("name", "Test"), +                entry("maxGoal", 100), +                entry("type", "MONETARY") +        ); + +        var res = cupboardController.createNeed(needMap);          assertEquals(HttpStatus.OK, res.getStatusCode());          assertEquals(need, res.getBody());      }      @Test -    public void createNeedBadMaxGoal() throws IOException { -        var need = new Need("Name", 1, -100, Need.GoalType.MONETARY); -        when(mockCupboardDAO.createNeed(need)).thenReturn(need); +    public void createNeedBadMaxGoal() throws IOException, DuplicateKeyException { +        when(mockCupboardService.createNeed("Name", -100, Need.GoalType.MONETARY)).thenThrow(new IllegalArgumentException()); + +        Map<String, Object> needMap = Map.ofEntries( +                entry("name", "Name"), +                entry("maxGoal", -100), +                entry("type", "MONETARY")); -        var res = cupboardController.createNeed(need); +        var res = cupboardController.createNeed(needMap); -        assertEquals(HttpStatus.BAD_REQUEST, res.getStatusCode()); +        assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, res.getStatusCode());      }      @Test -    public void createNeedIOException() throws IOException { -        var need = new Need("Name", 1, 100, Need.GoalType.MONETARY); -        when(mockCupboardDAO.createNeed(need)).thenThrow(new IOException()); +    public void createNeedIOException() throws IOException, DuplicateKeyException { +        when(mockCupboardService.createNeed("Name", 100, Need.GoalType.MONETARY)).thenThrow(new IOException()); + +        Map<String, Object> needMap = Map.ofEntries( +                entry("name", "Name"), +                entry("maxGoal", 100), +                entry("type", "MONETARY")); -        var res = cupboardController.createNeed(need); +        var res = cupboardController.createNeed(needMap);          assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode());      }      @Test -    public void getNeeds() { +    public void getNeeds() throws IOException {          var need = new Need("Name", 1, 100, Need.GoalType.MONETARY); -        when(mockCupboardDAO.getNeeds()).thenReturn(new Need[]{need}); +        when(mockCupboardService.getNeeds()).thenReturn(new Need[]{need});          var res = cupboardController.getNeeds(); @@ -67,9 +89,8 @@ public class CupboardControllerTest {      }      @Test -    public void getNeedsIOException() { -        var need = new Need("Name", 1, 100, Need.GoalType.MONETARY); -        when(mockCupboardDAO.getNeeds()).thenThrow(new IOException()); +    public void getNeedsIOException() throws IOException { +        when(mockCupboardService.getNeeds()).thenThrow(new IOException());          var res = cupboardController.getNeeds(); @@ -77,8 +98,8 @@ public class CupboardControllerTest {      }      @Test -    public void getNeedsEmpty() { -        when(mockCupboardDAO.getNeeds()).thenReturn(new Need[]{}); +    public void getNeedsEmpty() throws IOException { +        when(mockCupboardService.getNeeds()).thenReturn(new Need[]{});          var res = cupboardController.getNeeds(); @@ -87,9 +108,9 @@ public class CupboardControllerTest {      }      @Test -    public void searchNeeds() { +    public void searchNeeds() throws IOException {          var need = new Need("Name", 1, 100, Need.GoalType.MONETARY); -        when(mockCupboardDAO.findNeeds("Na")).thenReturn(new Need[]{need}); +        when(mockCupboardService.searchNeeds("Na")).thenReturn(new Need[]{need});          var res = cupboardController.searchNeeds("Na"); @@ -98,9 +119,8 @@ public class CupboardControllerTest {      }      @Test -    public void searchNeedsIOException() { -        var need = new Need("Name", 1, 100, Need.GoalType.MONETARY); -        when(mockCupboardDAO.findNeeds("Na")).thenThrow(new IOException()); +    public void searchNeedsIOException() throws IOException { +        when(mockCupboardService.searchNeeds("Na")).thenThrow(new IOException());          var res = cupboardController.searchNeeds("Na"); @@ -108,8 +128,8 @@ public class CupboardControllerTest {      }      @Test -    public void searchNeedsEmpty() { -        when(mockCupboardDAO.findNeeds("Na")).thenReturn(new Need[]{}); +    public void searchNeedsEmpty() throws IOException { +        when(mockCupboardService.searchNeeds("Na")).thenReturn(new Need[]{});          var res = cupboardController.searchNeeds("Na"); @@ -118,9 +138,9 @@ public class CupboardControllerTest {      }      @Test -    public void getNeed() { +    public void getNeed() throws IOException {          var need = new Need("Name", 1, 100, Need.GoalType.MONETARY); -        when(mockCupboardDAO.getNeed(need.getId())).thenReturn(need); +        when(mockCupboardService.getNeed(need.getId())).thenReturn(need);          var res = cupboardController.getNeed(need.getId()); @@ -129,9 +149,9 @@ public class CupboardControllerTest {      }      @Test -    public void getNeedIOException() { +    public void getNeedIOException() throws IOException {          var need = new Need("Name", 1, 100, Need.GoalType.MONETARY); -        when(mockCupboardDAO.getNeed(need.getId())).thenThrow(new IOException()); +        when(mockCupboardService.getNeed(need.getId())).thenThrow(new IOException());          var res = cupboardController.getNeed(need.getId()); @@ -139,9 +159,9 @@ public class CupboardControllerTest {      }      @Test -    public void getNeedFail() { +    public void getNeedFail() throws IOException {          var need = new Need("Name", 1, 100, Need.GoalType.MONETARY); -        when(mockCupboardDAO.getNeed(need.getId())).thenReturn(null); +        when(mockCupboardService.getNeed(need.getId())).thenReturn(null);          var res = cupboardController.getNeed(need.getId()); @@ -152,9 +172,9 @@ public class CupboardControllerTest {      @Test      public void updateNeeds() throws IOException {          var need = new Need("Name", 1, 100, Need.GoalType.MONETARY); -        when(mockCupboardDAO.updateNeed(need)).thenReturn(need); +        when(mockCupboardService.updateNeed(need, 1)).thenReturn(need); -        var res = cupboardController.updateNeed(need); +        var res = cupboardController.updateNeed(need, 1);          assertEquals(HttpStatus.OK, res.getStatusCode());          assertEquals(need, res.getBody()); @@ -163,9 +183,9 @@ public class CupboardControllerTest {      @Test      public void updateNeedsIOException() throws IOException {          var need = new Need("Name", 1, 100, Need.GoalType.MONETARY); -        when(mockCupboardDAO.updateNeed(need)).thenThrow(new IOException()); +        when(mockCupboardService.updateNeed(need, 1)).thenThrow(new IOException()); -        var res = cupboardController.updateNeed(need); +        var res = cupboardController.updateNeed(need, 1);          assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode());      } @@ -173,8 +193,8 @@ public class CupboardControllerTest {      @Test      public void deleteNeed() throws IOException {          var need = new Need("Name", 1, 100, Need.GoalType.MONETARY); -        when(mockCupboardDAO.getNeed(1)).thenReturn(need); -        when(mockCupboardDAO.deleteNeed(1)).thenReturn(true); +        when(mockCupboardService.getNeed(1)).thenReturn(need); +        when(mockCupboardService.deleteNeed(1)).thenReturn(true);          var res = cupboardController.deleteNeed(1); @@ -183,8 +203,8 @@ public class CupboardControllerTest {      @Test      public void deleteNeedFail() throws IOException { -        when(mockCupboardDAO.getNeed(1)).thenReturn(null); -        when(mockCupboardDAO.deleteNeed(1)).thenReturn(false); +        when(mockCupboardService.getNeed(1)).thenReturn(null); +        when(mockCupboardService.deleteNeed(1)).thenReturn(false);          var res = cupboardController.deleteNeed(1); @@ -194,8 +214,8 @@ public class CupboardControllerTest {      @Test      public void deleteNeedIOException() throws IOException {          var need = new Need("Name", 1, 100, Need.GoalType.MONETARY); -        when(mockCupboardDAO.getNeed(1)).thenReturn(need); -        when(mockCupboardDAO.deleteNeed(1)).thenThrow(new IOException()); +        when(mockCupboardService.getNeed(1)).thenReturn(need); +        when(mockCupboardService.deleteNeed(1)).thenThrow(new IOException());          var res = cupboardController.deleteNeed(1); diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java index 681f47c..b6367ad 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java @@ -1,8 +1,12 @@  package com.ufund.api.ufundapi.controller;  import java.io.IOException; +import java.security.InvalidParameterException; +import java.util.Map; +import static java.util.Map.entry;  import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull;  import org.junit.jupiter.api.BeforeEach;  import org.junit.jupiter.api.Tag;  import org.junit.jupiter.api.Test; @@ -12,82 +16,111 @@ import static org.mockito.Mockito.when;  import org.springframework.http.HttpStatus;  import org.springframework.http.ResponseEntity; +import com.ufund.api.ufundapi.DuplicateKeyException;  import com.ufund.api.ufundapi.model.User; -import com.ufund.api.ufundapi.persistence.UserFileDAO; +import com.ufund.api.ufundapi.model.UserAuth; +import com.ufund.api.ufundapi.service.AuthService; +import com.ufund.api.ufundapi.service.UserService;  @Tag("Controller-tier")  public class UserControllerTest {      private UserController userController; -    private UserFileDAO mockUserDAO; +    private AuthService mockAuthService; +    private UserService mockUserService; +    private Map<String, String> userMap;      @BeforeEach      public void setupUserController() { -        mockUserDAO = mock(UserFileDAO.class); -        userController = new UserController(mockUserDAO); - +        mockUserService = mock(UserService.class); +        mockAuthService = mock(AuthService.class); +        userController = new UserController(mockUserService, mockAuthService); +        userMap = Map.ofEntries( +                entry("username", "Test"), +                entry("password", "Pass") +        );      }      @Test      public void testGetUser() throws IOException { // getUser may throw IOException          // Setup          String username = "Test"; -        User user = new User(username); +        User user = User.create(username, "pass"); +        String key = UserAuth.generate(username).getKey(    );          // When the same id is passed in, our mock User DAO will return the User object -        when(mockUserDAO.getUser(username)).thenReturn(user); +        when(mockUserService.getUser(username)).thenReturn(user); +                  // Invoke -        ResponseEntity<User> response = userController.getUser(username); +        ResponseEntity<User> response = userController.getUser(username, key);          // Analyze          assertEquals(HttpStatus.OK, response.getStatusCode()); -        assertEquals(user, response.getBody()); +        assertNotNull(response.getBody()); +        assertEquals(user.getUsername(), response.getBody().getUsername());      }      @Test      public void testGetUserNotFound() throws Exception { // createUser may throw IOException          // Setup          String username = "Test"; -        // When the same id is passed in, our mock User DAO will return null, simulating +        String key = UserAuth.generate(username).getKey(); +        // When the same id is passed in, our mock User service will return null, simulating          // no User found -        when(mockUserDAO.getUser(username)).thenReturn(null); +        when(mockUserService.getUser(username)).thenReturn(null); +                  // Invoke -        ResponseEntity<User> response = userController.getUser(username); +        ResponseEntity<User> response = userController.getUser(username, key);          // Analyze          assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());      }      @Test +    public void testGetUserUnauthorized() throws Exception { // createUser may throw IOException +        // Setup +        String username = "Test"; +        String key = UserAuth.generate(username).getKey(); +        // When getUser is called on the Mock User service, throw an IOException +        // doThrow(new IllegalAccessException()).when(mockUserService).getUser(username); +        doThrow(new IllegalAccessException()).when(mockAuthService).authenticate(username, key); + +        // Invoke +        ResponseEntity<User> response = userController.getUser(username, key); + +        // Analyze +        assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); +    } + +    @Test      public void testGetUserHandleException() throws Exception { // createUser may throw IOException          // Setup          String username = "Test"; -        // When getUser is called on the Mock User DAO, throw an IOException -        doThrow(new IOException()).when(mockUserDAO).getUser(username); +        String key = UserAuth.generate(username).getKey(); +        // When getUser is called on the Mock User service, throw an IOException +        doThrow(new IOException()).when(mockUserService).getUser(username);          // Invoke -        ResponseEntity<User> response = userController.getUser(username); +        ResponseEntity<User> response = userController.getUser(username, key);          // Analyze          assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());      } -    /***************************************************************** -     * The following tests will fail until all userController methods -     * are implemented. -     ****************************************************************/ -      @Test -    public void testCreateUser() throws IOException { // createUser may throw IOException +    public void testCreateUser() throws IOException, DuplicateKeyException { // createUser may throw IOException          // Setup          String username = "Test"; -        User user = new User(username); +        String password = "Pass"; +        User user = User.create(username, "pass");          // when createUser is called, return true simulating successful          // creation and save -        when(mockUserDAO.createUser(user)).thenReturn(user); +        when(mockUserService.createUser(username, password)).thenReturn(user); + +                          // Invoke -        ResponseEntity<User> response = userController.createUser(user); +        ResponseEntity<User> response = userController.createUser(userMap);          // Analyze          assertEquals(HttpStatus.CREATED, response.getStatusCode()); @@ -95,32 +128,52 @@ public class UserControllerTest {      }      @Test -    public void testCreateUserFailed() throws IOException { // createUser may throw IOException +    public void testCreateUserFailed() throws IOException, DuplicateKeyException { // createUser may throw IOException          // Setup          String username = "Test"; -        User user = new User(username); +        String password = "Pass";          // when createUser is called, return false simulating failed          // creation and save -        when(mockUserDAO.createUser(user)).thenReturn(null); +        when(mockUserService.createUser(username, password)).thenReturn(null); + +                  // Invoke -        ResponseEntity<User> response = userController.createUser(user); +        ResponseEntity<User> response = userController.createUser(userMap);          // Analyze          assertEquals(HttpStatus.CONFLICT, response.getStatusCode());      }      @Test -    public void testCreateUserHandleException() throws IOException { // createUser may throw IOException +    public void testCreateUserDuplicate() throws IOException, DuplicateKeyException { // createUser may throw IOException          // Setup          String username = "Test"; -        User user = new User(username); +        String password = "Pass"; +        // when createUser is called, return false simulating failed +        // creation and save +        when(mockUserService.createUser(username, password)).thenThrow(DuplicateKeyException.class); -        // When createUser is called on the Mock User DAO, throw an IOException -        doThrow(new IOException()).when(mockUserDAO).createUser(user); +        // Invoke +        ResponseEntity<User> response = userController.createUser(userMap); + +        // Analyze +        assertEquals(HttpStatus.CONFLICT, response.getStatusCode()); +    } + +    @Test +    public void testCreateUserHandleException() throws IOException, DuplicateKeyException { // createUser may throw IOException +        // Setup +        String username = "Test"; +        String password = "Pass"; + +        // When createUser is called on the Mock User service, throw an IOException +        doThrow(new IOException()).when(mockUserService).createUser(username, password); + +                  // Invoke -        ResponseEntity<User> response = userController.createUser(user); +        ResponseEntity<User> response = userController.createUser(userMap);          // Analyze          assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); @@ -130,13 +183,14 @@ public class UserControllerTest {      public void testUpdateUser() throws IOException { // updateUser may throw IOException          // Setup          String username = "Test"; -        User user = new User("Bob"); +        User user = User.create(username, "pass"); +        String key = UserAuth.generate(username).getKey();          // when updateUser is called, return true simulating successful          // update and save -        when(mockUserDAO.updateUser(user, username)).thenReturn(user); +        when(mockUserService.updateUser(user, username)).thenReturn(user);          // Invoke -        ResponseEntity<User> response = userController.updateUser(user, username); +        ResponseEntity<User> response = userController.updateUser(user, username, key);          // Analyze          assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -147,42 +201,62 @@ public class UserControllerTest {      public void testUpdateUserFailed() throws IOException { // updateUser may throw IOException          // Setup          String username = "Test"; -        User user = new User("Bob"); +        User user = User.create(username, "pass"); +        String key = UserAuth.generate(username).getKey();          // when updateUser is called, return true simulating successful          // update and save -        when(mockUserDAO.updateUser(user, username)).thenReturn(null); +        when(mockUserService.updateUser(user, username)).thenReturn(null);          // Invoke -        ResponseEntity<User> response = userController.updateUser(user, username); +        ResponseEntity<User> response = userController.updateUser(user, username, key);          // Analyze          assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());      }      @Test -    public void testUpdateUserHandleException() throws IOException { // updateUser may throw IOException +    public void testUpdateUserInvalidParameter() throws IOException { // updateUser may throw IOException          // Setup          String username = "Test"; -        User user = new User("Bob"); +        User user = User.create(username, "pass"); +        String key = UserAuth.generate(username).getKey();          // When updateUser is called on the Mock User DAO, throw an IOException -        doThrow(new IOException()).when(mockUserDAO).updateUser(user, username); +        doThrow(new IOException()).when(mockUserService).updateUser(user, username);          // Invoke -        ResponseEntity<User> response = userController.updateUser(user, username); +        ResponseEntity<User> response = userController.updateUser(user, username, key);          // Analyze          assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());      }      @Test +    public void testUpdateUserUnauthorized() throws IOException, IllegalAccessException { // updateUser may throw IOException +        // Setup +        String username = "Test"; +        User user = User.create(username, "pass"); +        String key = UserAuth.generate(username).getKey(); +        // When updateUser is called on the Mock User service, throw a Invalid Parameter exception +        // exception +        doThrow(new IllegalAccessException()).when(mockAuthService).authenticate(username, key); + +        // Invoke +        ResponseEntity<User> response = userController.updateUser(user, username, key); + +        // Analyze +        assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); +    } + +    @Test      public void testDeleteUser() throws IOException { // deleteUser may throw IOException          // Setup          String username = "Test"; +        String key = UserAuth.generate(username).getKey();          // when deleteUser is called return true, simulating successful deletion -        when(mockUserDAO.deleteUser(username)).thenReturn(true); +        when(mockUserService.deleteUser(username)).thenReturn(true);          // Invoke -        ResponseEntity<User> response = userController.deleteUser(username); +        ResponseEntity<Boolean> response = userController.deleteUser(username, key);          // Analyze          assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -192,11 +266,12 @@ public class UserControllerTest {      public void testDeleteUserNotFound() throws IOException { // deleteUser may throw IOException          // Setup          String username = "Test"; +        String key = UserAuth.generate(username).getKey();          // when deleteUser is called return false, simulating failed deletion -        when(mockUserDAO.deleteUser(username)).thenReturn(false); +        when(mockUserService.deleteUser(username)).thenReturn(false);          // Invoke -        ResponseEntity<User> response = userController.deleteUser(username); +        ResponseEntity<Boolean> response = userController.deleteUser(username, key);          // Analyze          assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); @@ -206,14 +281,30 @@ public class UserControllerTest {      public void testDeleteUserHandleException() throws IOException { // deleteUser may throw IOException          // Setup          String username = "Test"; -        // When deleteUser is called on the Mock User DAO, throw an IOException -        doThrow(new IOException()).when(mockUserDAO).deleteUser(username); +        String key = UserAuth.generate(username).getKey(); +        // When deleteUser is called on the Mock User service, throw an IOException +        doThrow(new IOException()).when(mockUserService).deleteUser(username);          // Invoke -        ResponseEntity<User> response = userController.deleteUser(username); +        ResponseEntity<Boolean> response = userController.deleteUser(username, key);          // Analyze          assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());      } +    @Test +    public void testDeleteUserUnauthorized() throws IOException, IllegalAccessException { // deleteUser may throw IOException +        // Setup +        String username = "Test"; +        String key = UserAuth.generate(username).getKey(); +        // When deleteUser is called on the Mock User service, throw an IOException +        doThrow(new IllegalAccessException()).when(mockAuthService).authenticate(username, key); + +        // Invoke +        ResponseEntity<Boolean> response = userController.deleteUser(username, key); + +        // Analyze +        assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); +    } +  } diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/model/NeedTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/model/NeedTest.java index ffcd808..6b4ddfc 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/model/NeedTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/model/NeedTest.java @@ -14,13 +14,10 @@ public class NeedTest {      public void createNeed() {          String name = "Jellyfish"; -        int id = 0;          double maxGoal = 100.00;          GoalType type = GoalType.MONETARY; -        Need need = new Need(name, id, maxGoal, type); - +        Need need = new Need(name, type, maxGoal);          assertNotNull(need); -      }      @Test @@ -29,7 +26,7 @@ public class NeedTest {          int id = 0;          double maxGoal = 100.00;          GoalType type = GoalType.MONETARY; -        Need need = new Need(name, id, maxGoal, type); +        Need need = new Need(name, type, maxGoal);          assertEquals(name, need.getName()); @@ -41,22 +38,21 @@ public class NeedTest {      @Test      public void testCurrentGoal() {          String name = "Jellyfish"; -        int id = 0;          double maxGoal = 100.00;          GoalType type = GoalType.MONETARY; -        Need need = new Need(name, id, maxGoal, type); +        Need need = new Need(name, type, maxGoal);          double current = 0.00;          need.setCurrent(current); -        assertEquals(need.getCurrent(), current); +        assertEquals(current, need.getCurrent());          current = 100.00;          need.setCurrent(current); -        assertEquals(need.getCurrent(), current); +        assertEquals(current, need.getCurrent());          current = -100.00;          need.setCurrent(current); -        assertEquals(need.getCurrent(), current); +        assertEquals(current, need.getCurrent());      } @@ -64,10 +60,9 @@ public class NeedTest {      public void testFilterAttributes() {          String name = "Jellyfish"; -        int id = 0;          double maxGoal = 100.00;          GoalType type = GoalType.MONETARY; -        Need need = new Need(name, id, maxGoal, type); +        Need need = new Need(name, type, maxGoal);          String[] filterAttributes = {"seaweed", "divers", "pacific", "plankton"}; @@ -80,10 +75,9 @@ public class NeedTest {      public void testSetMaxGoal() {          String name = "Jellyfish"; -        int id = 0;          double maxGoal = 100.00;          GoalType type = GoalType.MONETARY; -        Need need = new Need(name, id, maxGoal, type); +        Need need = new Need(name, type, maxGoal);          double newGoal = 200.00;          need.setMaxGoal(newGoal); @@ -96,10 +90,9 @@ public class NeedTest {      public void testSetName() {          String name = "Jellyfish"; -        int id = 0;          double maxGoal = 100.00;          GoalType type = GoalType.MONETARY; -        Need need = new Need(name, id, maxGoal, type); +        Need need = new Need(name, type, maxGoal);          String newName = "TESTINGFUN";          need.setName(newName); diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/model/UserTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/model/UserTest.java index 716fbfd..54aa4d1 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/model/UserTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/model/UserTest.java @@ -1,19 +1,34 @@  package com.ufund.api.ufundapi.model; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull;  import org.junit.jupiter.api.Tag;  import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import java.io.IOException; + +import org.junit.jupiter.api.BeforeEach; +import static org.mockito.Mockito.when; + +import com.ufund.api.ufundapi.service.CupboardService; +  @Tag("Model-tier")  public class UserTest { +    private CupboardService cupboardService; + +    @BeforeEach +    public void setup() { +        cupboardService = mock(CupboardService.class); +    } +      @Test      public void createUser() {          String name = "Bob"; -        User user = new User(name); +        User user = User.create(name, "pass");          assertNotNull(user); @@ -23,42 +38,51 @@ public class UserTest {      public void testUsername() {          String expectedName = "Bob"; +        String password = "password"; -        User user = new User(expectedName); +        User user = User.create(expectedName, password); -        assertEquals(expectedName, user.getName()); +        assertEquals(expectedName, user.getUsername());      }      @Test -    public void addNeedToBasket() { +    public void addNeedToBasket() throws IOException {          String expectedName = "Bob"; -        User user = new User(expectedName); +        User user = User.create(expectedName, "pass");          Need need = new Need("Test", 0, 100, Need.GoalType.MONETARY);          Need[] needs = { need }; +        when(cupboardService.getNeed(0)).thenReturn(need); +          user.addToBasket(need); -        assertEquals(needs[0], user.getBasketNeeds()[0]); +        Need getNeed = cupboardService.getNeed(user.getBasketNeeds()[0]); + +        assertEquals(needs[0], getNeed);      }      @Test -    public void testRemoveBasketNeed() { +    public void testRemoveBasketNeed() throws IOException {          String expectedName = "Bob"; -        User user = new User(expectedName); +        User user = User.create(expectedName, "pass");          Need need = new Need("Test", 0, 100, Need.GoalType.MONETARY);          Need need2 = new Need("Test2", 0, 100, Need.GoalType.MONETARY); +        when(cupboardService.getNeed(0)).thenReturn(need2); +          user.addToBasket(need);          user.removeBasketNeed(need);          user.addToBasket(need2); -        assertEquals(need2, user.getBasketNeeds()[0]); +        Need getNeed = cupboardService.getNeed(user.getBasketNeeds()[0]); + +        assertEquals(need2, getNeed);      } @@ -67,9 +91,9 @@ public class UserTest {          String expectedName = "Bob"; -        User user = new User(expectedName); +        User user = User.create(expectedName, "pass"); -        assertEquals(false, user.verifyPassword(expectedName)); +        assertFalse(user.verifyPassword(expectedName));      } diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDaoTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDAOTest.java index 8aa6fe0..f786a8c 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDaoTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDAOTest.java @@ -1,79 +1,65 @@  package com.ufund.api.ufundapi.persistence; +import java.io.File; +import java.io.IOException; +  import static org.junit.jupiter.api.Assertions.assertEquals;  import static org.junit.jupiter.api.Assertions.assertNotEquals;  import static org.junit.jupiter.api.Assertions.assertNotNull;  import static org.junit.jupiter.api.Assertions.assertNull;  import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test;  import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.when; -import java.io.File; -import java.io.IOException; -  import com.fasterxml.jackson.databind.ObjectMapper;  import com.ufund.api.ufundapi.model.Need; -import com.ufund.api.ufundapi.model.User; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -  import com.ufund.api.ufundapi.model.Need.GoalType;  @Tag("Persistence-tier") -public class CupboardFileDaoTest { -    CupboardFileDao cupboardFileDao; -    Need[] testNeeds; -    ObjectMapper mockObjectMapper; +public class CupboardFileDAOTest { +    private CupboardFileDAO cupboardFileDao; +    private Need[] testNeeds; +    private ObjectMapper mockObjectMapper;      @BeforeEach      public void setupCupboardFileDao() throws IOException {          mockObjectMapper = mock(ObjectMapper.class); -        testNeeds = new Need[3]; -        testNeeds[0] = new Need("one", 0, 100, Need.GoalType.MONETARY); -		testNeeds[1] = new Need("two", 1, 100, Need.GoalType.MONETARY); -		testNeeds[2] = new Need("three", 2, 100, Need.GoalType.MONETARY); +        testNeeds = new Need[]{ +        	new Need("one", 0, 100, Need.GoalType.MONETARY), +			new Need("two", 1, 100, Need.GoalType.MONETARY), +			new Need("three", 2, 100, Need.GoalType.MONETARY) +		};          // When the object mapper is supposed to read from the file          // the mock object mapper will return the hero array above          when(mockObjectMapper              .readValue(new File("doesnt_matter.txt"),Need[].class))                  .thenReturn(testNeeds); -        cupboardFileDao = new CupboardFileDao("doesnt_matter.txt",mockObjectMapper); +        cupboardFileDao = new CupboardFileDAO("doesnt_matter.txt",mockObjectMapper);      }      @Test -    public void GetNeedsTest() throws IOException { +    public void getNeedsTest() {          Need[] needs = cupboardFileDao.getNeeds();          assertEquals(needs.length,testNeeds.length);  		assertEquals(needs[0].getName(), testNeeds[0].getName());      }      @Test -    public void GetNeedTest() throws IOException { +    public void getNeedTest() {  		Need need1 = cupboardFileDao.getNeed(0);  		assertEquals(testNeeds[0], need1);      }  	@Test -    public void Fet() throws IOException { -		String targetName1 = "one"; -		String targetName2 = "two"; - -		Need need1 = cupboardFileDao.findNeeds(targetName1)[0]; -		Need need2 = cupboardFileDao.findNeeds(targetName2)[0]; - -		assertEquals(testNeeds[0], need1); -		assertEquals(testNeeds[1], need2); -    } - -	@Test -	public void CreateNeedTest() throws IOException { +	public void createNeedTest() throws IOException {  		Need newNeed = new Need("sea urchin hats", 3, 100, GoalType.PHYSICAL); -		Need actualNeed = cupboardFileDao.createNeed(newNeed); +		Need actualNeed = cupboardFileDao.addNeed(newNeed);  		assertNotNull(actualNeed); @@ -81,7 +67,7 @@ public class CupboardFileDaoTest {  	}  	@Test -	public void DeleteNeedTest() throws IOException { +	public void deleteNeedTest() throws IOException {  		Need undeletedNeed = cupboardFileDao.getNeed(0);  		assertNotNull(undeletedNeed); @@ -93,7 +79,7 @@ public class CupboardFileDaoTest {  	}  	@Test -	public void UpdateNeedTest() throws IOException { +	public void updateNeedTest() throws IOException {  		Need[] needs = cupboardFileDao.getNeeds();  		Need unupdatedNeed = needs[needs.length - 1];  		assertNotNull(unupdatedNeed); diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserAuthFileDAOTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserAuthFileDAOTest.java new file mode 100644 index 0000000..f7db747 --- /dev/null +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserAuthFileDAOTest.java @@ -0,0 +1,63 @@ +package com.ufund.api.ufundapi.persistence; + +import java.io.File; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ufund.api.ufundapi.model.UserAuth; + +@Tag("Persistence-tier") +public class UserAuthFileDAOTest { + +    private UserAuthFIleDAO userAuthFIleDAO; +    private ObjectMapper mockObjectMapper; +    private UserAuth[] userAuths; + +    @BeforeEach +    public void setupUserAuthFileDAO() throws IOException { + +        mockObjectMapper = mock(ObjectMapper.class); +        userAuths = new UserAuth[]{ +        	new UserAuth("123", "Phil", null), +            new UserAuth("456", "Bob", null), +            new UserAuth("789", "Steve", null) +		}; +        // When the object mapper is supposed to read from the file +        // the mock object mapper will return the hero array above +        when(mockObjectMapper +            .readValue(new File("doesnt_matter.txt"),UserAuth[].class)) +                .thenReturn(userAuths); +        userAuthFIleDAO = new UserAuthFIleDAO(mockObjectMapper, "doesnt_matter.txt"); +    } + +    @Test +    public void getUserAuthTest() { +        String key = "123"; +        UserAuth auth = userAuthFIleDAO.getUserAuth(key); + +        assertEquals(auth, userAuths[0]); +    } + +    @Test +    public void addUserAuthTest() throws IOException { +        UserAuth auth = new UserAuth("999", "Fish", null); + +        assertDoesNotThrow(() -> userAuthFIleDAO.addUserAuth(auth)); +    } + +    @Test +    public void removeUserAuthTest() throws IOException { +        String key = "123"; + +        assertDoesNotThrow(() -> userAuthFIleDAO.removeUserAuth(key)); +    } +     +} diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserFileDAOTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserFileDAOTest.java index dfe9b10..9361188 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserFileDAOTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserFileDAOTest.java @@ -1,24 +1,22 @@  package com.ufund.api.ufundapi.persistence; +import java.io.File; +import java.io.IOException; +  import static org.junit.jupiter.api.Assertions.assertEquals;  import static org.junit.jupiter.api.Assertions.assertNotEquals;  import static org.junit.jupiter.api.Assertions.assertNotNull;  import static org.junit.jupiter.api.Assertions.assertNull;  import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test;  import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.when; -import java.io.File; -import java.io.IOException; -  import com.fasterxml.jackson.databind.ObjectMapper; -  import com.ufund.api.ufundapi.model.User; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -  @Tag("Persistence-tier")  public class UserFileDAOTest {      UserFileDAO userFileDAO; @@ -29,9 +27,9 @@ public class UserFileDAOTest {      public void setupHeroFileDAO() throws IOException {          mockObjectMapper = mock(ObjectMapper.class);          testUsers = new User[3]; -        testUsers[0] = new User("bob"); -		testUsers[1] = new User("admin"); -		testUsers[2] = new User("jelly12"); +        testUsers[0] = User.create("bob", "pass"); +		testUsers[1] = User.create("admin", "pass"); +		testUsers[2] = User.create("jelly12", "pass");          // When the object mapper is supposed to read from the file          // the mock object mapper will return the hero array above @@ -42,7 +40,7 @@ public class UserFileDAOTest {      }      @Test -    public void GetUsersTest() throws IOException { +    public void getUsersTest() {          User[] users = userFileDAO.getUsers();          assertEquals(users.length,testUsers.length); @@ -50,16 +48,17 @@ public class UserFileDAOTest {          for (int i = 0; i < testUsers.length;++i) {  			boolean isInArray = false;              for (User user : testUsers) { -				if (users[i].getName().equals(user.getName())) { -					isInArray = true; -				} +                if (users[i].getUsername().equals(user.getUsername())) { +                    isInArray = true; +                    break; +                }  			}  			assertTrue(isInArray);  		}      }      @Test -    public void FindUsersTest() throws IOException { +    public void findUsersTest() {  		User realUser1 = userFileDAO.getUser("bob");  		User realUser2 = userFileDAO.getUser("admin"); @@ -68,25 +67,25 @@ public class UserFileDAOTest {      }  	@Test -    public void FindUsersNullTest() throws IOException { +    public void findUsersNullTest() {  		User fakeUser = userFileDAO.getUser("phil.n.thropist");  		assertNull(fakeUser);      }  	@Test -	public void CreateUserTest() throws IOException { -		User newUser = new User("keshey"); -		userFileDAO.createUser(newUser); +	public void createUserTest() throws IOException { +		User newUser = User.create("keshey", "pass"); +		userFileDAO.addUser(newUser);  		User actualUser = userFileDAO.getUser("keshey");  		assertNotNull(actualUser); -		assertEquals(actualUser.getName(), newUser.getName()); +		assertEquals(actualUser.getUsername(), newUser.getUsername());  	}  	@Test -	public void DeleteUserTest() throws IOException { +	public void deleteUserTest() throws IOException {  		User notDeletedUser = userFileDAO.getUser("jelly12");  		assertNotNull(notDeletedUser); @@ -98,15 +97,15 @@ public class UserFileDAOTest {  	}  	@Test -	public void UpdateUserTest() throws IOException { +	public void updateUserTest() throws IOException {  		User toBeUpdatedUser = userFileDAO.getUser("admin");  		assertNotNull(toBeUpdatedUser); -		User updatedUser = new User("jellinadmin"); +		User updatedUser = User.create("admin", "newPass"); -		updatedUser = userFileDAO.updateUser(updatedUser, "admin"); +		updatedUser = userFileDAO.updateUser(updatedUser);  		assertNotEquals(toBeUpdatedUser, updatedUser); -		assertEquals("jellinadmin", updatedUser.getName()); +		assertEquals("admin", updatedUser.getUsername());  	}  } diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java new file mode 100644 index 0000000..55cf7a9 --- /dev/null +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java @@ -0,0 +1,110 @@ +package com.ufund.api.ufundapi.service; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import com.ufund.api.ufundapi.DuplicateKeyException; +import com.ufund.api.ufundapi.model.User; +import com.ufund.api.ufundapi.model.UserAuth; +import com.ufund.api.ufundapi.persistence.UserAuthDAO; + +@Tag("Service-tier") +public class AuthServiceTest { + +    private UserAuthDAO mockAuthDAO; +    private UserService mockUserService; +    private AuthService authService; +    private String username; +    private String key; +    private String password; +    private User user; + +    @BeforeEach +    public void setupAuthService() { +        mockAuthDAO = mock(UserAuthDAO.class); +        mockUserService = mock(UserService.class); +        authService = new AuthService(mockAuthDAO, mockUserService); +         +        username = "Fish"; +        password = "sticks"; +        key = UserAuth.generate(username).getKey(); +        user = User.create(username, password); + +    } + +    @Test +    public void testAuthenticate() throws IOException { +        // Mock +        when(mockAuthDAO.getUserAuth(key)).thenReturn(new UserAuth(key, username, null)); +        when(mockUserService.getUser(username)).thenReturn(user); + +        // Analyze +        assertDoesNotThrow(() -> authService.authenticate(username, key)); + +    } + +//    @Test +//    public void testAuthenticateMismatchName() throws IOException { +//        // Mock +//        when(mockAuthDAO.getUserAuth(key)).thenReturn(new UserAuth(key, "EvilFish", null)); +//        when(mockUserService.getUser("EvilFish")).thenReturn(user); +// +//        // Analyze +//        assertThrows(IllegalAccessException.class, () -> authService.authenticate(username, key)); +// +//    } + +    @Test +    public void testAuthenticateMissingUserAuth() throws IOException { +        // Mock +        when(mockAuthDAO.getUserAuth(key)).thenReturn(null); + +        // Analyze +        assertThrows(IllegalAccessException.class, () -> authService.authenticate(username, key)); +         +    } + +    @Test +    public void testLogin() throws IOException, DuplicateKeyException, IllegalAccessException { +        // Mock +        when(mockUserService.getUser(username)).thenReturn(user); +                 + +        // Analyze +        assertDoesNotThrow(() -> authService.login(username, password)); +    } + +    @Test +    public void testLoginNullUser() throws IOException, DuplicateKeyException, IllegalAccessException { +        // Mock +        when(mockUserService.getUser(username)).thenReturn(null); + +        // Analyze +        assertThrows(IllegalAccessException.class, () -> authService.login(username, password)); +    } + +    @Test +    public void testLoginMismatchPasswords() throws IOException, DuplicateKeyException, IllegalAccessException { +        // Mock +        when(mockUserService.getUser(username)).thenReturn(User.create(username, "fries")); + +        // Analyze +        assertThrows(IllegalAccessException.class, () -> authService.login(username, password)); +    } + +    @Test +    public void testLogout() throws IOException, DuplicateKeyException, IllegalAccessException { +        // Analyze +        assertDoesNotThrow(() -> authService.logout(key)); +         +    } +     +} diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/CupboardServiceTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/CupboardServiceTest.java new file mode 100644 index 0000000..99ca23c --- /dev/null +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/CupboardServiceTest.java @@ -0,0 +1,197 @@ +package com.ufund.api.ufundapi.service; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import com.ufund.api.ufundapi.DuplicateKeyException; +import com.ufund.api.ufundapi.model.Need; +import com.ufund.api.ufundapi.model.Need.GoalType; +import com.ufund.api.ufundapi.persistence.CupboardDAO; + +@Tag("Service-tier") +public class CupboardServiceTest { + +    private CupboardDAO mockCupboardDAO; +    private CupboardService cupboardService; + +    @BeforeEach +    public void setupCupboardService() { +        mockCupboardDAO = mock(CupboardDAO.class); +        cupboardService = new CupboardService(mockCupboardDAO); + +    } + +    @Test +    public void testCreateNeed() throws IOException, DuplicateKeyException { +        // Setup +        String name = "Jellyfish"; +        double maxGoal = 100.00; +        GoalType type = GoalType.MONETARY; +        Need need = new Need(name, type, maxGoal); + +        // When the same id is passed in, our mock User DAO will return the User object +        when(mockCupboardDAO.addNeed(any())).thenReturn(need); +        when(mockCupboardDAO.getNeeds()).thenReturn(new Need[0]); + +        // Invoke +        Need response = cupboardService.createNeed(name, maxGoal, type); + +        // Analyze +        assertNotNull(response); +        assertEquals(need, response); +    } + +    @Test +    public void testCreateNeedBadGoal() throws IOException, DuplicateKeyException { +        // Setup +        String name = "Jellyfish"; +        double maxGoal = -100.00; +        GoalType type = GoalType.MONETARY; +        Need need = new Need(name, type, maxGoal); + +        // When the same id is passed in, our mock User DAO will return the User object +        when(mockCupboardDAO.addNeed(any())).thenReturn(need); +        when(mockCupboardDAO.getNeeds()).thenReturn(new Need[0]); + +        // Invoke +        // Need response = cupboardService.createNeed(name, maxGoal, type); + +        // Analyze +        assertThrows(IllegalArgumentException.class, () -> { +            cupboardService.createNeed(name, maxGoal, type); +        }); +    } + +    @Test +    public void testCreateNeedDuplicate() throws IOException, DuplicateKeyException { +        // Setup +        String name = "Jellyfish"; +        double maxGoal = 100.00; +        GoalType type = GoalType.MONETARY; +        Need need = new Need(name, type, maxGoal); +        Need[] needs = { need }; + +        // When the same id is passed in, our mock User DAO will return the User object +        when(mockCupboardDAO.addNeed(any())).thenReturn(need); +        when(mockCupboardDAO.getNeeds()).thenReturn(needs); + +        // Invoke +        // Need response = cupboardService.createNeed(name, maxGoal, type); + +        // Analyze +        assertThrows(DuplicateKeyException.class, () -> { +            cupboardService.createNeed(name, maxGoal, type); +        }); +    } + +    @Test +    public void testSearchNeeds() throws IOException, DuplicateKeyException { +        // Setup +        String name = "Jellyfish"; +        double maxGoal = 100.00; +        GoalType type = GoalType.MONETARY; +        Need need = new Need(name, type, maxGoal); +        Need[] needs = { need }; + +        // When the same id is passed in, our mock User DAO will return the User object +        when(mockCupboardDAO.getNeeds()).thenReturn(needs); + +        // Invoke +        Need[] response = cupboardService.searchNeeds("Jelly"); + +        // Analyze +        assertEquals(need, response[0]); +        assertEquals(need.getName(), response[0].getName()); +    } + +    @Test +    public void testSearchNeedsFail() throws IOException, DuplicateKeyException { +        // Setup +        String name = "Jellyfish"; +        double maxGoal = 100.00; +        GoalType type = GoalType.MONETARY; +        Need need = new Need(name, type, maxGoal); +        Need[] needs = { need }; + +        // When the same id is passed in, our mock User DAO will return the User object +        when(mockCupboardDAO.getNeeds()).thenReturn(needs); + +        // Invoke +        Need[] response = cupboardService.searchNeeds("Octopus"); + +        // Analyze +        assertArrayEquals(new Need[0], response); +    } + +    @Test +    public void testGetNeed() throws IOException, DuplicateKeyException { +        // Setup +        String name = "Jellyfish"; +        double maxGoal = 100.00; +        int id = 0; +        GoalType type = GoalType.MONETARY; +        Need need = new Need(name, type, maxGoal); + +        // When the same id is passed in, our mock User DAO will return the User object +        when(mockCupboardDAO.getNeed(id)).thenReturn(need); + +        // Invoke +        Need response = cupboardService.getNeed(id); + +        // Analyze +        assertEquals(need, response); +    } + +    @Test +    public void testUpdateNeed() throws IOException, DuplicateKeyException { +        // Setup +        String name = "Jellyfish"; +        double maxGoal = 100.00; +        int id = 0; +        GoalType type = GoalType.MONETARY; +        Need need = new Need(name, type, maxGoal); +        Need newNeed = new Need("Octopus", type, maxGoal); + +        // When the same id is passed in, our mock User DAO will return the User object +        when(mockCupboardDAO.updateNeed(any())).thenReturn(newNeed); + +        // Invoke +        Need response = cupboardService.updateNeed(newNeed, id); + +        // Analyze +        assertEquals(newNeed, response); +    } + +    @Test +    public void testDeleteNeed() throws IOException, DuplicateKeyException { +        // Setup +        String name = "Jellyfish"; +        double maxGoal = 100.00; +        int id = 0; +        GoalType type = GoalType.MONETARY; +        Need need = new Need(name, type, maxGoal); + +        // When the same id is passed in, our mock User DAO will return the User object +        when(mockCupboardDAO.deleteNeed(id)).thenReturn(true); +        when(mockCupboardDAO.getNeeds()).thenReturn(new Need[0]); + +        // Invoke +        boolean response = cupboardService.deleteNeed(id); +        Need[] responseNeeds = cupboardService.getNeeds(); + +        // Analyze +        assertTrue(response); +        assertArrayEquals(new Need[0], responseNeeds); +    } + +}
\ No newline at end of file diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/UserServiceTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/UserServiceTest.java new file mode 100644 index 0000000..0a0cb71 --- /dev/null +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/UserServiceTest.java @@ -0,0 +1,126 @@ +package com.ufund.api.ufundapi.service; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.ufund.api.ufundapi.DuplicateKeyException; +import com.ufund.api.ufundapi.model.User; +import com.ufund.api.ufundapi.persistence.UserDAO; + +public class UserServiceTest { + +    private UserService userService; +    private UserDAO mockUserDAO; + + +    @BeforeEach +    public void setupUserService() { +        mockUserDAO = mock(UserDAO.class); +        userService = new UserService(mockUserDAO); +    } + +    @Test +    public void testCreateUser() throws IOException, DuplicateKeyException { +        // Setup +        String username = "Jelly"; +        String password = "Fish"; +        User user = User.create(username, password); + +        // Mock +        when(mockUserDAO.getUser(username)).thenReturn(null); +        when(mockUserDAO.addUser(any())).thenReturn(user); + +        // Invoke + +        // Analyze +        assertEquals(user, userService.createUser(username, password)); +    } + +    @Test +    public void testCreateUserDuplicate() throws IOException, DuplicateKeyException { +        // Setup +        String username = "Jelly"; +        String password = "Fish"; +        User user = User.create(username, password); + +        // Mock +        when(mockUserDAO.getUser(username)).thenReturn(User.create("Phil", "Phil")); +        when(mockUserDAO.addUser(any())).thenReturn(user); + +        // Analyze +        assertThrows(DuplicateKeyException.class, () -> userService.createUser(username, password)); +    } + +    @Test +    public void testGetUser() throws IOException, DuplicateKeyException { +        // Setup +        String username = "Jelly"; +        String password = "Fish"; +        User user = User.create(username, password); + +        // Mock +        when(mockUserDAO.getUser(username)).thenReturn(user); + +        // Analyze +        assertEquals(user, userService.getUser(username)); +    } + +    @Test +    public void testUpdateUser() throws IOException, DuplicateKeyException { +        // Setup +        String username = "Jelly"; +        String password = "Fish"; +        User oldUser = User.create(username, password); + +        String newUsername = "Jelly"; +        String newPassword = "Dog"; +        User newUser = User.create(newUsername, newPassword); + +        // Mock +        when(mockUserDAO.updateUser(newUser)).thenReturn(newUser); + +        // Analyze +        assertEquals(newUser, userService.updateUser(newUser, oldUser.getUsername())); +    } + +    @Test +    public void testUpdateUserDifferentUsernames() throws IOException, DuplicateKeyException { +        // Setup +        String username = "Jelly"; +        String password = "Fish"; +        User oldUser = User.create(username, password); + +        String newUsername = "Cat"; +        String newPassword = "Fish"; +        User newUser = User.create(newUsername, newPassword); + +        // Mock +        when(mockUserDAO.updateUser(newUser)).thenReturn(newUser); + +        // Analyze +        assertThrows(IllegalArgumentException.class, () -> userService.updateUser(newUser, oldUser.getUsername())); +    } + +    @Test +    public void testDeleteUser() throws IOException, DuplicateKeyException { +        // Setup +        String username = "Jelly"; +        String password = "Fish"; +        User user = User.create(username, password); + +        // Mock +        when(mockUserDAO.deleteUser(username)).thenReturn(true); + +        // Analyze +        assertTrue(userService.deleteUser(username)); +    } +     +}  | 
