generated from rit-ecet-notes/new-course
Init rest api
This commit is contained in:
parent
d7f3ce80a1
commit
3cbabfec12
25 changed files with 1397 additions and 0 deletions
BIN
REST API Starter Project.zip
Normal file
BIN
REST API Starter Project.zip
Normal file
Binary file not shown.
22
heroesRestAPIStarter/HELP.md
Normal file
22
heroesRestAPIStarter/HELP.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Read Me First
|
||||
The following was discovered as part of building this project:
|
||||
|
||||
* The original package name 'com.heroes.api.heroes-api' is invalid and this project uses 'com.heroes.api.heroesapi' instead.
|
||||
|
||||
# Getting Started
|
||||
|
||||
### Reference Documentation
|
||||
For further reference, please consider the following sections:
|
||||
|
||||
* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html)
|
||||
* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.5.6/maven-plugin/reference/html/)
|
||||
* [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.5.6/maven-plugin/reference/html/#build-image)
|
||||
* [Spring Web](https://docs.spring.io/spring-boot/docs/2.5.6/reference/htmlsingle/#boot-features-developing-web-applications)
|
||||
|
||||
### Guides
|
||||
The following guides illustrate how to use some features concretely:
|
||||
|
||||
* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/)
|
||||
* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/)
|
||||
* [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/)
|
||||
|
42
heroesRestAPIStarter/data/heroes.json
Normal file
42
heroesRestAPIStarter/data/heroes.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
[
|
||||
{
|
||||
"id": 11,
|
||||
"name": "Mr. Nice"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "Narco"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "Bombasto"
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"name": "Celeritas"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"name": "Magneta"
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"name": "RubberMan"
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"name": "Dynama"
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"name": "Dr IQ"
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"name": "Magma"
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"name": "Tornado"
|
||||
}
|
||||
]
|
185
heroesRestAPIStarter/pom.xml
Normal file
185
heroesRestAPIStarter/pom.xml
Normal file
|
@ -0,0 +1,185 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.6</version>
|
||||
<relativePath /> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.heroes.api</groupId>
|
||||
<artifactId>heroes-api</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>heroes-api</name>
|
||||
<description>API for Tour of Heroes</description>
|
||||
<properties>
|
||||
<java.version>11</java.version>
|
||||
<jacoco.version>0.8.7</jacoco.version>
|
||||
<maven.exec.version>3.0.0</maven.exec.version>
|
||||
<maven.assembly.version>3.1.0</maven.assembly.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>${maven.exec.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>java</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>tests-and-coverage</id>
|
||||
<configuration>
|
||||
<executable>mvn</executable>
|
||||
<arguments>
|
||||
<argument>clean</argument>
|
||||
<argument>test-compile</argument>
|
||||
<argument>surefire:test@controller</argument>
|
||||
<argument>jacoco:report@controller</argument>
|
||||
<argument>surefire:test@model</argument>
|
||||
<argument>jacoco:report@model</argument>
|
||||
<argument>surefire:test@persistence</argument>
|
||||
<argument>jacoco:report@persistence</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>zip</id>
|
||||
<configuration>
|
||||
<executable>mvn</executable>
|
||||
<arguments>
|
||||
<argument>assembly:single@zip</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<mainClass>com.heroes.api.heroesapi.HeroesApiApplication</mainClass>
|
||||
<arguments></arguments>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<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>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>jacoco-initialize</id>
|
||||
<configuration>
|
||||
<!-- throw away the old data with each test run -->
|
||||
<append>false</append>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<!-- attached to Maven test phase -->
|
||||
<execution>
|
||||
<id>report</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>controller</id>
|
||||
<configuration>
|
||||
<footer>Controller Tier</footer>
|
||||
<title>Heroes API Controller Tier Test Coverage</title>
|
||||
<outputDirectory>target/site/jacoco/controller</outputDirectory>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>persistence</id>
|
||||
<configuration>
|
||||
<footer>Persistence Tier</footer>
|
||||
<title>Heroes API Persistence Tier Test Coverage</title>
|
||||
<outputDirectory>target/site/jacoco/persistence</outputDirectory>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>model</id>
|
||||
<configuration>
|
||||
<footer>Model Tier</footer>
|
||||
<title>Heroes API Model Tier Test Coverage</title>
|
||||
<outputDirectory>target/site/jacoco/model</outputDirectory>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>jacoco-check</id>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<rule>
|
||||
<element>BUNDLE</element>
|
||||
<limits>
|
||||
<limit>
|
||||
<counter>INSTRUCTION</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>0.90</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>${maven.assembly.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>zip</id>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>src/assembly/zip.xml</descriptor>
|
||||
</descriptors>
|
||||
<finalName>heroes-api</finalName>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
26
heroesRestAPIStarter/src/assembly/zip.xml
Normal file
26
heroesRestAPIStarter/src/assembly/zip.xml
Normal file
|
@ -0,0 +1,26 @@
|
|||
<assembly
|
||||
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/3.1.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/3.1.0 http://maven.apache.org/xsd/assembly-3.1.0.xsd">
|
||||
<id>no-tests</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<useDefaultExcludes>false</useDefaultExcludes>
|
||||
<includes>
|
||||
<include>HELP.md</include>
|
||||
<include>pom.xml</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<includes>
|
||||
<include>src/**</include>
|
||||
<include>src/assembly/zip.xml</include>
|
||||
<include>data/**</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
|
@ -0,0 +1,13 @@
|
|||
package com.heroes.api.heroesapi;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class HeroesApiApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(HeroesApiApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.heroes.api.heroesapi;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
package com.heroes.api.heroesapi.controller;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.heroes.api.heroesapi.persistence.HeroDAO;
|
||||
import com.heroes.api.heroesapi.model.Hero;
|
||||
|
||||
/**
|
||||
* Handles the REST API requests for the Hero resource
|
||||
* <p>
|
||||
* {@literal @}RestController Spring annotation identifies this class as a REST API
|
||||
* method handler to the Spring framework
|
||||
*
|
||||
* @author SWEN Faculty
|
||||
*/
|
||||
|
||||
@RestController
|
||||
@RequestMapping("heroes")
|
||||
public class HeroController {
|
||||
private static final Logger LOG = Logger.getLogger(HeroController.class.getName());
|
||||
private HeroDAO heroDao;
|
||||
|
||||
/**
|
||||
* Creates a REST API controller to reponds to requests
|
||||
*
|
||||
* @param heroDao The {@link HeroDAO Hero Data Access Object} to perform CRUD operations
|
||||
* <br>
|
||||
* This dependency is injected by the Spring Framework
|
||||
*/
|
||||
public HeroController(HeroDAO heroDao) {
|
||||
this.heroDao = heroDao;
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to the GET request for a {@linkplain Hero hero} for the given id
|
||||
*
|
||||
* @param id The id used to locate the {@link Hero hero}
|
||||
*
|
||||
* @return ResponseEntity with {@link Hero hero} 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<Hero> getHero(@PathVariable int id) {
|
||||
LOG.info("GET /heroes/" + id);
|
||||
try {
|
||||
Hero hero = heroDao.getHero(id);
|
||||
if (hero != null)
|
||||
return new ResponseEntity<Hero>(hero,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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to the GET request for all {@linkplain Hero heroes}
|
||||
*
|
||||
* @return ResponseEntity with array of {@link Hero hero} objects (may be empty) and
|
||||
* HTTP status of OK<br>
|
||||
* ResponseEntity with HTTP status of INTERNAL_SERVER_ERROR otherwise
|
||||
*/
|
||||
@GetMapping("")
|
||||
public ResponseEntity<Hero[]> getHeroes() {
|
||||
LOG.info("GET /heroes");
|
||||
|
||||
// Replace below with your implementation
|
||||
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to the GET request for all {@linkplain Hero heroes} whose name contains
|
||||
* the text in name
|
||||
*
|
||||
* @param name The name parameter which contains the text used to find the {@link Hero heroes}
|
||||
*
|
||||
* @return ResponseEntity with array of {@link Hero hero} objects (may be empty) and
|
||||
* HTTP status of OK<br>
|
||||
* ResponseEntity with HTTP status of INTERNAL_SERVER_ERROR otherwise
|
||||
* <p>
|
||||
* Example: Find all heroes that contain the text "ma"
|
||||
* GET http://localhost:8080/heroes/?name=ma
|
||||
*/
|
||||
@GetMapping("/")
|
||||
public ResponseEntity<Hero[]> searchHeroes(@RequestParam String name) {
|
||||
LOG.info("GET /heroes/?name="+name);
|
||||
|
||||
// Replace below with your implementation
|
||||
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@linkplain Hero hero} with the provided hero object
|
||||
*
|
||||
* @param hero - The {@link Hero hero} to create
|
||||
*
|
||||
* @return ResponseEntity with created {@link Hero hero} object and HTTP status of CREATED<br>
|
||||
* ResponseEntity with HTTP status of CONFLICT if {@link Hero hero} object already exists<br>
|
||||
* ResponseEntity with HTTP status of INTERNAL_SERVER_ERROR otherwise
|
||||
*/
|
||||
@PostMapping("")
|
||||
public ResponseEntity<Hero> createHero(@RequestBody Hero hero) {
|
||||
LOG.info("POST /heroes " + hero);
|
||||
|
||||
// Replace below with your implementation
|
||||
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the {@linkplain Hero hero} with the provided {@linkplain Hero hero} object, if it exists
|
||||
*
|
||||
* @param hero The {@link Hero hero} to update
|
||||
*
|
||||
* @return ResponseEntity with updated {@link Hero hero} object and HTTP status of OK if updated<br>
|
||||
* ResponseEntity with HTTP status of NOT_FOUND if not found<br>
|
||||
* ResponseEntity with HTTP status of INTERNAL_SERVER_ERROR otherwise
|
||||
*/
|
||||
@PutMapping("")
|
||||
public ResponseEntity<Hero> updateHero(@RequestBody Hero hero) {
|
||||
LOG.info("PUT /heroes " + hero);
|
||||
|
||||
// Replace below with your implementation
|
||||
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a {@linkplain Hero hero} with the given id
|
||||
*
|
||||
* @param id The id of the {@link Hero hero} to deleted
|
||||
*
|
||||
* @return ResponseEntity HTTP status of OK if deleted<br>
|
||||
* ResponseEntity with HTTP status of NOT_FOUND if not found<br>
|
||||
* ResponseEntity with HTTP status of INTERNAL_SERVER_ERROR otherwise
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Hero> deleteHero(@PathVariable int id) {
|
||||
LOG.info("DELETE /heroes/" + id);
|
||||
|
||||
// Replace below with your implementation
|
||||
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package com.heroes.api.heroesapi.model;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
/**
|
||||
* Represents a Hero entity
|
||||
*
|
||||
* @author SWEN Faculty
|
||||
*/
|
||||
public class Hero {
|
||||
private static final Logger LOG = Logger.getLogger(Hero.class.getName());
|
||||
|
||||
// Package private for tests
|
||||
static final String STRING_FORMAT = "Hero [id=%d, name=%s]";
|
||||
|
||||
@JsonProperty("id") private int id;
|
||||
@JsonProperty("name") private String name;
|
||||
|
||||
/**
|
||||
* Create a hero with the given id and name
|
||||
* @param id The id of the hero
|
||||
* @param name The name of the hero
|
||||
*
|
||||
* {@literal @}JsonProperty is used in serialization and deserialization
|
||||
* of the JSON object to the Java object in mapping the fields. If a field
|
||||
* is not provided in the JSON object, the Java field gets the default Java
|
||||
* value, i.e. 0 for int
|
||||
*/
|
||||
public Hero(@JsonProperty("id") int id, @JsonProperty("name") String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the id of the hero
|
||||
* @return The id of the hero
|
||||
*/
|
||||
public int getId() {return id;}
|
||||
|
||||
/**
|
||||
* Sets the name of the hero - necessary for JSON object to Java object deserialization
|
||||
* @param name The name of the hero
|
||||
*/
|
||||
public void setName(String name) {this.name = name;}
|
||||
|
||||
/**
|
||||
* Retrieves the name of the hero
|
||||
* @return The name of the hero
|
||||
*/
|
||||
public String getName() {return name;}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(STRING_FORMAT,id,name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package com.heroes.api.heroesapi.persistence;
|
||||
|
||||
import java.io.IOException;
|
||||
import com.heroes.api.heroesapi.model.Hero;
|
||||
|
||||
/**
|
||||
* Defines the interface for Hero object persistence
|
||||
*
|
||||
* @author SWEN Faculty
|
||||
*/
|
||||
public interface HeroDAO {
|
||||
/**
|
||||
* Retrieves all {@linkplain Hero heroes}
|
||||
*
|
||||
* @return An array of {@link Hero hero} objects, may be empty
|
||||
*
|
||||
* @throws IOException if an issue with underlying storage
|
||||
*/
|
||||
Hero[] getHeroes() throws IOException;
|
||||
|
||||
/**
|
||||
* Finds all {@linkplain Hero heroes} whose name contains the given text
|
||||
*
|
||||
* @param containsText The text to match against
|
||||
*
|
||||
* @return An array of {@link Hero heroes} whose nemes contains the given text, may be empty
|
||||
*
|
||||
* @throws IOException if an issue with underlying storage
|
||||
*/
|
||||
Hero[] findHeroes(String containsText) throws IOException;
|
||||
|
||||
/**
|
||||
* Retrieves a {@linkplain Hero hero} with the given id
|
||||
*
|
||||
* @param id The id of the {@link Hero hero} to get
|
||||
*
|
||||
* @return a {@link Hero hero} object with the matching id
|
||||
* <br>
|
||||
* null if no {@link Hero hero} with a matching id is found
|
||||
*
|
||||
* @throws IOException if an issue with underlying storage
|
||||
*/
|
||||
Hero getHero(int id) throws IOException;
|
||||
|
||||
/**
|
||||
* Creates and saves a {@linkplain Hero hero}
|
||||
*
|
||||
* @param hero {@linkplain Hero hero} object to be created and saved
|
||||
* <br>
|
||||
* The id of the hero object is ignored and a new uniqe id is assigned
|
||||
*
|
||||
* @return new {@link Hero hero} if successful, false otherwise
|
||||
*
|
||||
* @throws IOException if an issue with underlying storage
|
||||
*/
|
||||
Hero createHero(Hero hero) throws IOException;
|
||||
|
||||
/**
|
||||
* Updates and saves a {@linkplain Hero hero}
|
||||
*
|
||||
* @param {@link Hero hero} object to be updated and saved
|
||||
*
|
||||
* @return updated {@link Hero hero} if successful, null if
|
||||
* {@link Hero hero} could not be found
|
||||
*
|
||||
* @throws IOException if underlying storage cannot be accessed
|
||||
*/
|
||||
Hero updateHero(Hero hero) throws IOException;
|
||||
|
||||
/**
|
||||
* Deletes a {@linkplain Hero hero} with the given id
|
||||
*
|
||||
* @param id The id of the {@link Hero hero}
|
||||
*
|
||||
* @return true if the {@link Hero hero} was deleted
|
||||
* <br>
|
||||
* false if hero with the given id does not exist
|
||||
*
|
||||
* @throws IOException if underlying storage cannot be accessed
|
||||
*/
|
||||
boolean deleteHero(int id) throws IOException;
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
package com.heroes.api.heroesapi.persistence;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.heroes.api.heroesapi.model.Hero;
|
||||
|
||||
/**
|
||||
* Implements the functionality for JSON file-based peristance for Heroes
|
||||
*
|
||||
* {@literal @}Component Spring annotation instantiates a single instance of this
|
||||
* class and injects the instance into other classes as needed
|
||||
*
|
||||
* @author SWEN Faculty
|
||||
*/
|
||||
@Component
|
||||
public class HeroFileDAO implements HeroDAO {
|
||||
private static final Logger LOG = Logger.getLogger(HeroFileDAO.class.getName());
|
||||
Map<Integer,Hero> heroes; // Provides a local cache of the hero objects
|
||||
// so that we don't need to read from the file
|
||||
// each time
|
||||
private ObjectMapper objectMapper; // Provides conversion between Hero
|
||||
// objects and JSON text format written
|
||||
// to the file
|
||||
private static int nextId; // The next Id to assign to a new hero
|
||||
private String filename; // Filename to read from and write to
|
||||
|
||||
/**
|
||||
* Creates a Hero File Data Access Object
|
||||
*
|
||||
* @param filename Filename to read from and write to
|
||||
* @param objectMapper Provides JSON Object to/from Java Object serialization and deserialization
|
||||
*
|
||||
* @throws IOException when file cannot be accessed or read from
|
||||
*/
|
||||
public HeroFileDAO(@Value("${heroes.file}") String filename,ObjectMapper objectMapper) throws IOException {
|
||||
this.filename = filename;
|
||||
this.objectMapper = objectMapper;
|
||||
load(); // load the heroes from the file
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the next id for a new {@linkplain Hero hero}
|
||||
*
|
||||
* @return The next id
|
||||
*/
|
||||
private synchronized static int nextId() {
|
||||
int id = nextId;
|
||||
++nextId;
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of {@linkplain Hero heroes} from the tree map
|
||||
*
|
||||
* @return The array of {@link Hero heroes}, may be empty
|
||||
*/
|
||||
private Hero[] getHeroesArray() {
|
||||
return getHeroesArray(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of {@linkplain Hero heroes} from the tree map for any
|
||||
* {@linkplain Hero heroes} that contains the text specified by containsText
|
||||
* <br>
|
||||
* If containsText is null, the array contains all of the {@linkplain Hero heroes}
|
||||
* in the tree map
|
||||
*
|
||||
* @return The array of {@link Hero heroes}, may be empty
|
||||
*/
|
||||
private Hero[] getHeroesArray(String containsText) { // if containsText == null, no filter
|
||||
ArrayList<Hero> heroArrayList = new ArrayList<>();
|
||||
|
||||
for (Hero hero : heroes.values()) {
|
||||
if (containsText == null || hero.getName().contains(containsText)) {
|
||||
heroArrayList.add(hero);
|
||||
}
|
||||
}
|
||||
|
||||
Hero[] heroArray = new Hero[heroArrayList.size()];
|
||||
heroArrayList.toArray(heroArray);
|
||||
return heroArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the {@linkplain Hero heroes} from the map into the file as an array of JSON objects
|
||||
*
|
||||
* @return true if the {@link Hero heroes} were written successfully
|
||||
*
|
||||
* @throws IOException when file cannot be accessed or written to
|
||||
*/
|
||||
private boolean save() throws IOException {
|
||||
Hero[] heroArray = getHeroesArray();
|
||||
|
||||
// Serializes the Java Objects to JSON objects into the file
|
||||
// writeValue will thrown an IOException if there is an issue
|
||||
// with the file or reading from the file
|
||||
objectMapper.writeValue(new File(filename),heroArray);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads {@linkplain Hero heroes} from the JSON file into the map
|
||||
* <br>
|
||||
* Also sets next id to one more than the greatest id found in the file
|
||||
*
|
||||
* @return true if the file was read successfully
|
||||
*
|
||||
* @throws IOException when file cannot be accessed or read from
|
||||
*/
|
||||
private boolean load() throws IOException {
|
||||
heroes = new TreeMap<>();
|
||||
nextId = 0;
|
||||
|
||||
// Deserializes the JSON objects from the file into an array of heroes
|
||||
// readValue will throw an IOException if there's an issue with the file
|
||||
// or reading from the file
|
||||
Hero[] heroArray = objectMapper.readValue(new File(filename),Hero[].class);
|
||||
|
||||
// Add each hero to the tree map and keep track of the greatest id
|
||||
for (Hero hero : heroArray) {
|
||||
heroes.put(hero.getId(),hero);
|
||||
if (hero.getId() > nextId)
|
||||
nextId = hero.getId();
|
||||
}
|
||||
// Make the next id one greater than the maximum from the file
|
||||
++nextId;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
** {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Hero[] getHeroes() {
|
||||
synchronized(heroes) {
|
||||
return getHeroesArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
** {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Hero[] findHeroes(String containsText) {
|
||||
synchronized(heroes) {
|
||||
return getHeroesArray(containsText);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
** {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Hero getHero(int id) {
|
||||
synchronized(heroes) {
|
||||
if (heroes.containsKey(id))
|
||||
return heroes.get(id);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
** {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Hero createHero(Hero hero) throws IOException {
|
||||
synchronized(heroes) {
|
||||
// We create a new hero object because the id field is immutable
|
||||
// and we need to assign the next unique id
|
||||
Hero newHero = new Hero(nextId(),hero.getName());
|
||||
heroes.put(newHero.getId(),newHero);
|
||||
save(); // may throw an IOException
|
||||
return newHero;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
** {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Hero updateHero(Hero hero) throws IOException {
|
||||
synchronized(heroes) {
|
||||
if (heroes.containsKey(hero.getId()) == false)
|
||||
return null; // hero does not exist
|
||||
|
||||
heroes.put(hero.getId(),hero);
|
||||
save(); // may throw an IOException
|
||||
return hero;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
** {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean deleteHero(int id) throws IOException {
|
||||
synchronized(heroes) {
|
||||
if (heroes.containsKey(id)) {
|
||||
heroes.remove(id);
|
||||
return save();
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
server.error.include-message=always
|
||||
heroes.file=data/heroes.json
|
|
@ -0,0 +1,18 @@
|
|||
package com.heroes.api.heroesapi;
|
||||
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
/**
|
||||
* This is a built in test that comes with the Spring framework that validates
|
||||
* that the REST API service starts (that's it)
|
||||
*/
|
||||
@Tag("Controller-tier")
|
||||
@SpringBootTest
|
||||
class HeroesApiApplicationTests {
|
||||
|
||||
@Test
|
||||
void testContextLoads() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
package com.heroes.api.heroesapi.controller;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.heroes.api.heroesapi.persistence.HeroDAO;
|
||||
import com.heroes.api.heroesapi.model.Hero;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
/**
|
||||
* Test the Hero Controller class
|
||||
*
|
||||
* @author SWEN Faculty
|
||||
*/
|
||||
@Tag("Controller-tier")
|
||||
public class HeroControllerTest {
|
||||
private HeroController heroController;
|
||||
private HeroDAO mockHeroDAO;
|
||||
|
||||
/**
|
||||
* Before each test, create a new HeroController object and inject
|
||||
* a mock Hero DAO
|
||||
*/
|
||||
@BeforeEach
|
||||
public void setupHeroController() {
|
||||
mockHeroDAO = mock(HeroDAO.class);
|
||||
heroController = new HeroController(mockHeroDAO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHero() throws IOException { // getHero may throw IOException
|
||||
// Setup
|
||||
Hero hero = new Hero(99,"Galactic Agent");
|
||||
// When the same id is passed in, our mock Hero DAO will return the Hero object
|
||||
when(mockHeroDAO.getHero(hero.getId())).thenReturn(hero);
|
||||
|
||||
// Invoke
|
||||
ResponseEntity<Hero> response = heroController.getHero(hero.getId());
|
||||
|
||||
// Analyze
|
||||
assertEquals(HttpStatus.OK,response.getStatusCode());
|
||||
assertEquals(hero,response.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHeroNotFound() throws Exception { // createHero may throw IOException
|
||||
// Setup
|
||||
int heroId = 99;
|
||||
// When the same id is passed in, our mock Hero DAO will return null, simulating
|
||||
// no hero found
|
||||
when(mockHeroDAO.getHero(heroId)).thenReturn(null);
|
||||
|
||||
// Invoke
|
||||
ResponseEntity<Hero> response = heroController.getHero(heroId);
|
||||
|
||||
// Analyze
|
||||
assertEquals(HttpStatus.NOT_FOUND,response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHeroHandleException() throws Exception { // createHero may throw IOException
|
||||
// Setup
|
||||
int heroId = 99;
|
||||
// When getHero is called on the Mock Hero DAO, throw an IOException
|
||||
doThrow(new IOException()).when(mockHeroDAO).getHero(heroId);
|
||||
|
||||
// Invoke
|
||||
ResponseEntity<Hero> response = heroController.getHero(heroId);
|
||||
|
||||
// Analyze
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR,response.getStatusCode());
|
||||
}
|
||||
|
||||
/*****************************************************************
|
||||
* The following tests will fail until all HeroController methods
|
||||
* are implemented.
|
||||
****************************************************************/
|
||||
|
||||
@Test
|
||||
public void testCreateHero() throws IOException { // createHero may throw IOException
|
||||
// Setup
|
||||
Hero hero = new Hero(99,"Wi-Fire");
|
||||
// when createHero is called, return true simulating successful
|
||||
// creation and save
|
||||
when(mockHeroDAO.createHero(hero)).thenReturn(hero);
|
||||
|
||||
// Invoke
|
||||
ResponseEntity<Hero> response = heroController.createHero(hero);
|
||||
|
||||
// Analyze
|
||||
assertEquals(HttpStatus.CREATED,response.getStatusCode());
|
||||
assertEquals(hero,response.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateHeroFailed() throws IOException { // createHero may throw IOException
|
||||
// Setup
|
||||
Hero hero = new Hero(99,"Bolt");
|
||||
// when createHero is called, return false simulating failed
|
||||
// creation and save
|
||||
when(mockHeroDAO.createHero(hero)).thenReturn(null);
|
||||
|
||||
// Invoke
|
||||
ResponseEntity<Hero> response = heroController.createHero(hero);
|
||||
|
||||
// Analyze
|
||||
assertEquals(HttpStatus.CONFLICT,response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateHeroHandleException() throws IOException { // createHero may throw IOException
|
||||
// Setup
|
||||
Hero hero = new Hero(99,"Ice Gladiator");
|
||||
|
||||
// When createHero is called on the Mock Hero DAO, throw an IOException
|
||||
doThrow(new IOException()).when(mockHeroDAO).createHero(hero);
|
||||
|
||||
// Invoke
|
||||
ResponseEntity<Hero> response = heroController.createHero(hero);
|
||||
|
||||
// Analyze
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR,response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateHero() throws IOException { // updateHero may throw IOException
|
||||
// Setup
|
||||
Hero hero = new Hero(99,"Wi-Fire");
|
||||
// when updateHero is called, return true simulating successful
|
||||
// update and save
|
||||
when(mockHeroDAO.updateHero(hero)).thenReturn(hero);
|
||||
ResponseEntity<Hero> response = heroController.updateHero(hero);
|
||||
hero.setName("Bolt");
|
||||
|
||||
// Invoke
|
||||
response = heroController.updateHero(hero);
|
||||
|
||||
// Analyze
|
||||
assertEquals(HttpStatus.OK,response.getStatusCode());
|
||||
assertEquals(hero,response.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateHeroFailed() throws IOException { // updateHero may throw IOException
|
||||
// Setup
|
||||
Hero hero = new Hero(99,"Galactic Agent");
|
||||
// when updateHero is called, return true simulating successful
|
||||
// update and save
|
||||
when(mockHeroDAO.updateHero(hero)).thenReturn(null);
|
||||
|
||||
// Invoke
|
||||
ResponseEntity<Hero> response = heroController.updateHero(hero);
|
||||
|
||||
// Analyze
|
||||
assertEquals(HttpStatus.NOT_FOUND,response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateHeroHandleException() throws IOException { // updateHero may throw IOException
|
||||
// Setup
|
||||
Hero hero = new Hero(99,"Galactic Agent");
|
||||
// When updateHero is called on the Mock Hero DAO, throw an IOException
|
||||
doThrow(new IOException()).when(mockHeroDAO).updateHero(hero);
|
||||
|
||||
// Invoke
|
||||
ResponseEntity<Hero> response = heroController.updateHero(hero);
|
||||
|
||||
// Analyze
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR,response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHeroes() throws IOException { // getHeroes may throw IOException
|
||||
// Setup
|
||||
Hero[] heroes = new Hero[2];
|
||||
heroes[0] = new Hero(99,"Bolt");
|
||||
heroes[1] = new Hero(100,"The Great Iguana");
|
||||
// When getHeroes is called return the heroes created above
|
||||
when(mockHeroDAO.getHeroes()).thenReturn(heroes);
|
||||
|
||||
// Invoke
|
||||
ResponseEntity<Hero[]> response = heroController.getHeroes();
|
||||
|
||||
// Analyze
|
||||
assertEquals(HttpStatus.OK,response.getStatusCode());
|
||||
assertEquals(heroes,response.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHeroesHandleException() throws IOException { // getHeroes may throw IOException
|
||||
// Setup
|
||||
// When getHeroes is called on the Mock Hero DAO, throw an IOException
|
||||
doThrow(new IOException()).when(mockHeroDAO).getHeroes();
|
||||
|
||||
// Invoke
|
||||
ResponseEntity<Hero[]> response = heroController.getHeroes();
|
||||
|
||||
// Analyze
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR,response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchHeroes() throws IOException { // findHeroes may throw IOException
|
||||
// Setup
|
||||
String searchString = "la";
|
||||
Hero[] heroes = new Hero[2];
|
||||
heroes[0] = new Hero(99,"Galactic Agent");
|
||||
heroes[1] = new Hero(100,"Ice Gladiator");
|
||||
// When findHeroes is called with the search string, return the two
|
||||
/// heroes above
|
||||
when(mockHeroDAO.findHeroes(searchString)).thenReturn(heroes);
|
||||
|
||||
// Invoke
|
||||
ResponseEntity<Hero[]> response = heroController.searchHeroes(searchString);
|
||||
|
||||
// Analyze
|
||||
assertEquals(HttpStatus.OK,response.getStatusCode());
|
||||
assertEquals(heroes,response.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchHeroesHandleException() throws IOException { // findHeroes may throw IOException
|
||||
// Setup
|
||||
String searchString = "an";
|
||||
// When createHero is called on the Mock Hero DAO, throw an IOException
|
||||
doThrow(new IOException()).when(mockHeroDAO).findHeroes(searchString);
|
||||
|
||||
// Invoke
|
||||
ResponseEntity<Hero[]> response = heroController.searchHeroes(searchString);
|
||||
|
||||
// Analyze
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR,response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteHero() throws IOException { // deleteHero may throw IOException
|
||||
// Setup
|
||||
int heroId = 99;
|
||||
// when deleteHero is called return true, simulating successful deletion
|
||||
when(mockHeroDAO.deleteHero(heroId)).thenReturn(true);
|
||||
|
||||
// Invoke
|
||||
ResponseEntity<Hero> response = heroController.deleteHero(heroId);
|
||||
|
||||
// Analyze
|
||||
assertEquals(HttpStatus.OK,response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteHeroNotFound() throws IOException { // deleteHero may throw IOException
|
||||
// Setup
|
||||
int heroId = 99;
|
||||
// when deleteHero is called return false, simulating failed deletion
|
||||
when(mockHeroDAO.deleteHero(heroId)).thenReturn(false);
|
||||
|
||||
// Invoke
|
||||
ResponseEntity<Hero> response = heroController.deleteHero(heroId);
|
||||
|
||||
// Analyze
|
||||
assertEquals(HttpStatus.NOT_FOUND,response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteHeroHandleException() throws IOException { // deleteHero may throw IOException
|
||||
// Setup
|
||||
int heroId = 99;
|
||||
// When deleteHero is called on the Mock Hero DAO, throw an IOException
|
||||
doThrow(new IOException()).when(mockHeroDAO).deleteHero(heroId);
|
||||
|
||||
// Invoke
|
||||
ResponseEntity<Hero> response = heroController.deleteHero(heroId);
|
||||
|
||||
// Analyze
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR,response.getStatusCode());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package com.heroes.api.heroesapi.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* The unit test suite for the Hero class
|
||||
*
|
||||
* @author SWEN Faculty
|
||||
*/
|
||||
@Tag("Model-tier")
|
||||
public class HeroTest {
|
||||
@Test
|
||||
public void testCtor() {
|
||||
// Setup
|
||||
int expected_id = 99;
|
||||
String expected_name = "Wi-Fire";
|
||||
|
||||
// Invoke
|
||||
Hero hero = new Hero(expected_id,expected_name);
|
||||
|
||||
// Analyze
|
||||
assertEquals(expected_id,hero.getId());
|
||||
assertEquals(expected_name,hero.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testName() {
|
||||
// Setup
|
||||
int id = 99;
|
||||
String name = "Wi-Fire";
|
||||
Hero hero = new Hero(id,name);
|
||||
|
||||
String expected_name = "Galactic Agent";
|
||||
|
||||
// Invoke
|
||||
hero.setName(expected_name);
|
||||
|
||||
// Analyze
|
||||
assertEquals(expected_name,hero.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString() {
|
||||
// Setup
|
||||
int id = 99;
|
||||
String name = "Wi-Fire";
|
||||
String expected_string = String.format(Hero.STRING_FORMAT,id,name);
|
||||
Hero hero = new Hero(id,name);
|
||||
|
||||
// Invoke
|
||||
String actual_string = hero.toString();
|
||||
|
||||
// Analyze
|
||||
assertEquals(expected_string,actual_string);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
package com.heroes.api.heroesapi.persistence;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
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.heroes.api.heroesapi.model.Hero;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test the Hero File DAO class
|
||||
*
|
||||
* @author SWEN Faculty
|
||||
*/
|
||||
@Tag("Persistence-tier")
|
||||
public class HeroFileDAOTest {
|
||||
HeroFileDAO heroFileDAO;
|
||||
Hero[] testHeroes;
|
||||
ObjectMapper mockObjectMapper;
|
||||
|
||||
/**
|
||||
* Before each test, we will create and inject a Mock Object Mapper to
|
||||
* isolate the tests from the underlying file
|
||||
* @throws IOException
|
||||
*/
|
||||
@BeforeEach
|
||||
public void setupHeroFileDAO() throws IOException {
|
||||
mockObjectMapper = mock(ObjectMapper.class);
|
||||
testHeroes = new Hero[3];
|
||||
testHeroes[0] = new Hero(99,"Wi-Fire");
|
||||
testHeroes[1] = new Hero(100,"Galactic Agent");
|
||||
testHeroes[2] = new Hero(101,"Ice Gladiator");
|
||||
|
||||
// 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"),Hero[].class))
|
||||
.thenReturn(testHeroes);
|
||||
heroFileDAO = new HeroFileDAO("doesnt_matter.txt",mockObjectMapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHeroes() {
|
||||
// Invoke
|
||||
Hero[] heroes = heroFileDAO.getHeroes();
|
||||
|
||||
// Analyze
|
||||
assertEquals(heroes.length,testHeroes.length);
|
||||
for (int i = 0; i < testHeroes.length;++i)
|
||||
assertEquals(heroes[i],testHeroes[i]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindHeroes() {
|
||||
// Invoke
|
||||
Hero[] heroes = heroFileDAO.findHeroes("la");
|
||||
|
||||
// Analyze
|
||||
assertEquals(heroes.length,2);
|
||||
assertEquals(heroes[0],testHeroes[1]);
|
||||
assertEquals(heroes[1],testHeroes[2]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHero() {
|
||||
// Invoke
|
||||
Hero hero = heroFileDAO.getHero(99);
|
||||
|
||||
// Analzye
|
||||
assertEquals(hero,testHeroes[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteHero() {
|
||||
// Invoke
|
||||
boolean result = assertDoesNotThrow(() -> heroFileDAO.deleteHero(99),
|
||||
"Unexpected exception thrown");
|
||||
|
||||
// Analzye
|
||||
assertEquals(result,true);
|
||||
// We check the internal tree map size against the length
|
||||
// of the test heroes array - 1 (because of the delete)
|
||||
// Because heroes attribute of HeroFileDAO is package private
|
||||
// we can access it directly
|
||||
assertEquals(heroFileDAO.heroes.size(),testHeroes.length-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateHero() {
|
||||
// Setup
|
||||
Hero hero = new Hero(102,"Wonder-Person");
|
||||
|
||||
// Invoke
|
||||
Hero result = assertDoesNotThrow(() -> heroFileDAO.createHero(hero),
|
||||
"Unexpected exception thrown");
|
||||
|
||||
// Analyze
|
||||
assertNotNull(result);
|
||||
Hero actual = heroFileDAO.getHero(hero.getId());
|
||||
assertEquals(actual.getId(),hero.getId());
|
||||
assertEquals(actual.getName(),hero.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateHero() {
|
||||
// Setup
|
||||
Hero hero = new Hero(99,"Galactic Agent");
|
||||
|
||||
// Invoke
|
||||
Hero result = assertDoesNotThrow(() -> heroFileDAO.updateHero(hero),
|
||||
"Unexpected exception thrown");
|
||||
|
||||
// Analyze
|
||||
assertNotNull(result);
|
||||
Hero actual = heroFileDAO.getHero(hero.getId());
|
||||
assertEquals(actual,hero);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSaveException() throws IOException{
|
||||
doThrow(new IOException())
|
||||
.when(mockObjectMapper)
|
||||
.writeValue(any(File.class),any(Hero[].class));
|
||||
|
||||
Hero hero = new Hero(102,"Wi-Fire");
|
||||
|
||||
assertThrows(IOException.class,
|
||||
() -> heroFileDAO.createHero(hero),
|
||||
"IOException not thrown");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHeroNotFound() {
|
||||
// Invoke
|
||||
Hero hero = heroFileDAO.getHero(98);
|
||||
|
||||
// Analyze
|
||||
assertEquals(hero,null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteHeroNotFound() {
|
||||
// Invoke
|
||||
boolean result = assertDoesNotThrow(() -> heroFileDAO.deleteHero(98),
|
||||
"Unexpected exception thrown");
|
||||
|
||||
// Analyze
|
||||
assertEquals(result,false);
|
||||
assertEquals(heroFileDAO.heroes.size(),testHeroes.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateHeroNotFound() {
|
||||
// Setup
|
||||
Hero hero = new Hero(98,"Bolt");
|
||||
|
||||
// Invoke
|
||||
Hero result = assertDoesNotThrow(() -> heroFileDAO.updateHero(hero),
|
||||
"Unexpected exception thrown");
|
||||
|
||||
// Analyze
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructorException() throws IOException {
|
||||
// Setup
|
||||
ObjectMapper mockObjectMapper = mock(ObjectMapper.class);
|
||||
// We want to simulate with a Mock Object Mapper that an
|
||||
// exception was raised during JSON object deseerialization
|
||||
// into Java objects
|
||||
// When the Mock Object Mapper readValue method is called
|
||||
// from the HeroFileDAO load method, an IOException is
|
||||
// raised
|
||||
doThrow(new IOException())
|
||||
.when(mockObjectMapper)
|
||||
.readValue(new File("doesnt_matter.txt"),Hero[].class);
|
||||
|
||||
// Invoke & Analyze
|
||||
assertThrows(IOException.class,
|
||||
() -> new HeroFileDAO("doesnt_matter.txt",mockObjectMapper),
|
||||
"IOException not thrown");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
server.error.include-message=always
|
||||
heroes.file=data/heroes.json
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,6 @@
|
|||
com/heroes/api/heroesapi/persistence/HeroFileDAO.class
|
||||
com/heroes/api/heroesapi/WebConfig.class
|
||||
com/heroes/api/heroesapi/HeroesApiApplication.class
|
||||
com/heroes/api/heroesapi/persistence/HeroDAO.class
|
||||
com/heroes/api/heroesapi/controller/HeroController.class
|
||||
com/heroes/api/heroesapi/model/Hero.class
|
|
@ -0,0 +1,6 @@
|
|||
/home/icestar/Documents/2231-notes/swen-261-02/coursework/heroesRestAPIStarter/src/main/java/com/heroes/api/heroesapi/controller/HeroController.java
|
||||
/home/icestar/Documents/2231-notes/swen-261-02/coursework/heroesRestAPIStarter/src/main/java/com/heroes/api/heroesapi/WebConfig.java
|
||||
/home/icestar/Documents/2231-notes/swen-261-02/coursework/heroesRestAPIStarter/src/main/java/com/heroes/api/heroesapi/persistence/HeroDAO.java
|
||||
/home/icestar/Documents/2231-notes/swen-261-02/coursework/heroesRestAPIStarter/src/main/java/com/heroes/api/heroesapi/model/Hero.java
|
||||
/home/icestar/Documents/2231-notes/swen-261-02/coursework/heroesRestAPIStarter/src/main/java/com/heroes/api/heroesapi/persistence/HeroFileDAO.java
|
||||
/home/icestar/Documents/2231-notes/swen-261-02/coursework/heroesRestAPIStarter/src/main/java/com/heroes/api/heroesapi/HeroesApiApplication.java
|
Loading…
Add table
Reference in a new issue