Skip to content

Commit

Permalink
Finished unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mlambert125 committed Nov 16, 2024
1 parent c337721 commit 2fc7168
Show file tree
Hide file tree
Showing 14 changed files with 958 additions and 76 deletions.
91 changes: 62 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ database are not yet implemented.
The application source contains the following directories:

- `src/main/java/org.example/models` - Contains the model classes that represent
the data in the database. Currently, there is only one model class, `User`,
which represents a user from the database `users` table.
the data in the database. *You will not need to create any new model classes.*
- `src/main/java/org.example/daos` - Contains the data access objects that
interact with the database. Currently, there is only one DAO class, `UserDao`,
which interacts with the `users` table.
Expand Down Expand Up @@ -63,7 +62,6 @@ directory. You will need to set the following properties:
- `spring.datasource.username` - The username for your mysql server.
- `spring.datasource.password` - The password for your mysql server.


### Running the Application

To run the application, you can right-click on the `SpringBootApplication`
Expand Down Expand Up @@ -109,21 +107,16 @@ until you implement the REST endpoints.
## Exercise

Your task is to implement the REST endpoints that allow users to interact with
the store. You will need to create a new model class, DAO class, and controller
the store. You will need to create a DAO class and a controller
class for each of the tables in the database.

Stop the application and follow the steps below to complete the exercise.

### Step 1: Create the Model Classes

In the `models` folder, create a new model class for each of the tables in the
database (other than the `users` and `roles` tables). Each model class should
contain a field for each column in the table, and should contain getters and
setters for each field. You should also create a constructor that takes all of
the fields as arguments.
### Step 1: Review the Model Classes

You can refer to the existing `User` model class for an example of what these
classes should look like.
In the `models` folder, notice that there is a class for each of the tables in
the database. You will need to use these classes to interact with the database
in the DAO classes and controller classes.

### Step 2: Create the DAO Classes

Expand Down Expand Up @@ -166,52 +159,93 @@ public class ProductDao {
}
```

Each DAO class will also need a mapping method that maps a `ResultSet` object
to a model object so that your `SELECT` queries can map the results to the
appropriate model objects. You can refer to the `UserDao` class for an example
of what this method should look like.

### Step 3: Create the Controller Classes

Create a new controller class for each of the tables in the database (other
than the `users` and `roles` tables). Each controller class should contain
methods providing REST endpoints for creating, reading, updating, and deleting
items in the table.
items in the table. These endpoints are detailed below.

You can refer to the existing `UserController` and `ProfileController` classes
for an example of what these classes should look like.

Note that each controller class injects its corresponding DAO class through
the constructor. You should do the same in your controller classes.
an `@Autowired` class member. You will need to do the same for your controller
classes.

For example:

```java
@RestController
@RequestMapping("/products")
public class ProductController {
@Autowired
private ProductDao productDao;

public ProductController(ProductDao productDao) {
this.productDao = productDao;
}

// ...
}
```
> [!Note]
> Your "Get by Id" methods should return a 404 status code if the item is not
> found in the database. You can do this by throwing a `ResponseStatusException`
> with a `HttpStatus.NOT_FOUND` status code. (Refer to the `UserController` class
> for an example of how to do this.)
When you are finished, you should have implemented all of the REST endpoints
in the Postman collection, and they should all function properly and return
appropriate data.

## Testing / Verification
### Product Endpoints

- `GET /products` - Retrieves all products.
- `GET /products/{id}` - Retrieves a product by the id in the path, return a 404 NOT FOUND status code if the product is not found.
- `POST /products` - Creates a new product from the request body and returns the created product with a 201 CREATED http status code.
- `PUT /products/{id}` - Updates an existing product from the request body and returns the updated product, return a 404 NOT FOUND status code if the product is not found.
- `DELETE /products/{id}` - Deletes a product by the id in the path and returns the number of rows affected, return a 404 NOT FOUND status code if the product is not found.

### Order Endpoints

- `GET /orders` - Retrieves all orders.
- `GET /orders/{id}` - Retrieves an order by the id in the path, return a 404 NOT FOUND status code if the order is not found.
- `POST /orders` - Creates a new order from the request body and returns the created order with a 201 CREATED http status code.
- `PUT /orders/{id}` - Updates an existing order from the request body and returns the updated order, return a 404 NOT FOUND status code if the order is not found.
- `DELETE /orders/{id}` - Deletes an order by the id in the path and returns the number of rows affected, return a 404 NOT FOUND status code if the order is not found.

### Order Item Endpoints

- `GET /order-items` - Retrieves all order items.
- `GET /order-items/{id}` - Retrieves an order item by the id in the path, return a 404 NOT FOUND status code if the order item is not found.
- `POST /order-items` - Creates a new order item from the request body and returns the created order item with a CREATED http 201 status code.
- `PUT /order-items/{id}` - Updates an existing order item from the request body and returns the updated order item, return a 404 NOT FOUND status code if the order item is not found.
- `DELETE /order-items/{id}` - Deletes an order item by the id in the path and returns the number of rows affected, return a 404 NOT FOUND status code if the order item is not found.

## Testing with Postman

You can verify that the application is working correctly by running the
application and then launching Postman to test the REST endpoints. You should
be able to create, read, update, and delete items in the database using the
REST endpoints that you created.

## Testing with Included Unit Tests

You can also verify that the application is working correctly by running the
included unit tests. The unit tests are located in the `src/test/java` directory.
To run the unit tests, right-click on the `src/test/java` directory and select
"Run All Tests".

> [!warning]
> DO NOT modify the unit tests. Your project will be evaluated based on the
> unit tests all passing. Modifying the unit tests will result in a failing
> grade.
## Evaluation

Your project will be evaluated by the test proctor cloning your repository,
running the database script, running the application, and executing the
Postman requests. You will receive full credit if all of the requests execute
successfully and return the expected data.
Your project will be evaluated based on the unit tests all passing.

## Bonus Steps

Expand All @@ -222,13 +256,13 @@ controller classes to require that users be authenticated in order to access
the endpoints. You can use the `isAuthenticated()` expression to require that
users be authenticated.

### Bonus Step 2: Constrain Product Creation and Update using `Principal`
### Bonus Step 2: Constrain Order Creation and Update using `Principal`

Add a `Principal` argument to the create and update endpoints for the
`Product` controller that will allow you to get the username of the user and
overwrite the `username` field in the passed `Product` object. This will
`Order` controller that will allow you to get the username of the user and
overwrite the `username` field in the passed `Order` object. This will
guaranatee that the `username` field is always set to the username of the
user that created or updated the product.
user that created or updated the order.

For example:

Expand Down Expand Up @@ -263,7 +297,6 @@ public List<Order> listOrders(@RequestParam(required = false) String username) {
> You will need to create a new method in the `OrderDao` class that retrieves
> orders by username.

### Bonus Step 4: Add optional query parameters to the `OrderItems` GET endpoint

Add optional `orderId` query parameters to the `OrderItems` GET endpoint that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.example.daos.UserDao;
import org.example.models.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

Expand All @@ -18,16 +19,8 @@ public class ProfileController {
/**
* The user data access object.
*/
private final UserDao userDao;

/**
* Creates a new user controller.
*
* @param userDao The user data access object.
*/
public ProfileController(UserDao userDao) {
this.userDao = userDao;
}
@Autowired
private UserDao userDao;

/**
* Gets the profile of the currently logged in user.
Expand Down Expand Up @@ -60,7 +53,7 @@ public List<String> getRoles(Principal principal) {
* @param newPassword The new password.
* @return The updated user.
*/
@PostMapping("/change-password")
@PutMapping("/change-password")
public User changePassword(Principal principal, @RequestBody String newPassword) {
String username = principal.getName();
User user = userDao.getUserByUsername(username);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import org.example.models.User;
import org.example.daos.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;

import java.util.List;

Expand All @@ -20,16 +22,8 @@ public class UserController {
/**
* The user data access object.
*/
private final UserDao userDao;

/**
* Creates a new user controller.
*
* @param userDao The user data access object.
*/
public UserController(UserDao userDao) {
this.userDao = userDao;
}
@Autowired
private UserDao userDao;

/**
* Gets all users.
Expand Down Expand Up @@ -75,6 +69,9 @@ public User create(@RequestBody User user) {
@PutMapping(path = "/{username}/password")
public User updatePassword(@RequestBody String password, @PathVariable String username) {
User user = userDao.getUserByUsername(username);
if (user == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Product not found");
}
user.setPassword(password);
return userDao.updatePassword(user);
}
Expand All @@ -84,10 +81,9 @@ public User updatePassword(@RequestBody String password, @PathVariable String us
*
* @param username The username of the user to delete.
*/
@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping(path = "/{username}")
public void delete(@PathVariable String username) {
userDao.deleteUser(username);
public int delete(@PathVariable String username) {
return userDao.deleteUser(username);
}

/**
Expand Down Expand Up @@ -118,9 +114,13 @@ public List<String> addRole(@PathVariable String username, @RequestBody String r
* @param username The username of the user.
* @param role The role to delete.
*/
@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping(path = "/{username}/roles/{role}")
public void deleteRole(@PathVariable String username, @PathVariable String role) {
userDao.deleteRole(username, role.toUpperCase());
public int deleteRole(@PathVariable String username, @PathVariable String role) {
var affectedRows = userDao.deleteRole(username, role.toUpperCase());
if (affectedRows == 0) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Role not found");
} else {
return affectedRows;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ public User updatePassword(User user) {
*
* @param username The username of the user.
*/
public void deleteUser(String username) {
public int deleteUser(String username) {
String sql = "DELETE FROM users WHERE username = ? ";
jdbcTemplate.update(sql, username);
return jdbcTemplate.update(sql, username);
}

/**
Expand Down Expand Up @@ -137,9 +137,9 @@ public List<String> addRole(String username, String role) {
* @param username The username of the user.
* @param role The role to delete.
*/
public void deleteRole(String username, String role) {
public int deleteRole(String username, String role) {
String sql = "DELETE FROM roles WHERE username = ? AND role = ?";
jdbcTemplate.update(sql, username, role);
return jdbcTemplate.update(sql, username, role);
}

/**
Expand Down
69 changes: 69 additions & 0 deletions java-springboot-final/src/main/java/org/example/models/Order.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.example.models;

/**
* Model for an order.
*/
public class Order {
/**
* The ID of the order.
*/
private int id;

/**
* The username of the order.
*/
private String username;

/**
* Creates a new order.
*/
public Order() {
}

/**
* Creates a new order.
*
* @param id The ID of the order.
* @param username The username of the order.
*/
public Order(int id, String username) {
this.id = id;
this.username = username;
}

/**
* Gets the ID of the order.
*
* @return int
*/
public int getId() {
return id;
}

/**
* Sets the ID of the order.
*
* @param id The ID of the order.
*/
public void setId(int id) {
this.id = id;
}

/**
* Gets the username of the order.
*
* @return String
*/
public String getUsername() {
return username;
}

/**
* Sets the username of the order.
*
* @param username The username of the order.
*/
public void setUsername(String username) {
this.username = username;
}
}
Loading

0 comments on commit 2fc7168

Please sign in to comment.