Add template for assignment 2
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>dst</artifactId>
|
||||
<version>2021.1</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>ass2-service-api</artifactId>
|
||||
|
||||
<name>DST :: Assignment 2 :: Service :: API</name>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-protobuf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-stub</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<extensions>
|
||||
<extension>
|
||||
<groupId>kr.motd.maven</groupId>
|
||||
<artifactId>os-maven-plugin</artifactId>
|
||||
</extension>
|
||||
</extensions>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.xolstice.maven.plugins</groupId>
|
||||
<artifactId>protobuf-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,24 @@
|
||||
package dst.ass2.service.api.auth;
|
||||
|
||||
public class AuthenticationException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public AuthenticationException() {
|
||||
}
|
||||
|
||||
public AuthenticationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AuthenticationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public AuthenticationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public AuthenticationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package dst.ass2.service.api.auth;
|
||||
|
||||
public interface IAuthenticationService {
|
||||
|
||||
/**
|
||||
* Attempts to authenticate the user with the given unique email address and the given password in plain text, by
|
||||
* checking the data against the records in the database. If the credentials are successfully authenticated, the
|
||||
* service generates a new authentication token which is stored (with the users email address) in-memory and then
|
||||
* returned.
|
||||
*
|
||||
* @param email the user email
|
||||
* @param password the password
|
||||
* @return a new authentication token
|
||||
* @throws NoSuchUserException if the given user was not found
|
||||
* @throws AuthenticationException if the credentials could not be authenticated
|
||||
*/
|
||||
String authenticate(String email, String password) throws NoSuchUserException, AuthenticationException;
|
||||
|
||||
/**
|
||||
* Changes the password of the given user in the database. Also updates the in-memory cache in a thread-safe way.
|
||||
*
|
||||
* @param email the user email
|
||||
* @param newPassword the new password in plain text.
|
||||
* @throws NoSuchUserException if the given user was not found
|
||||
*/
|
||||
void changePassword(String email, String newPassword) throws NoSuchUserException;
|
||||
|
||||
/**
|
||||
* Returns the user that is associated with this token. Returns null if the token does not exist.
|
||||
*
|
||||
* @param token an authentication token previously created via {@link #authenticate(String, String)}
|
||||
* @return the user's email address or null
|
||||
*/
|
||||
String getUser(String token);
|
||||
|
||||
/**
|
||||
* Checks whether the given token is valid (i.e., was issued by this service and has not been invalidated).
|
||||
*
|
||||
* @param token the token to validate
|
||||
* @return true if the token is valid, false otherwise
|
||||
*/
|
||||
boolean isValid(String token);
|
||||
|
||||
/**
|
||||
* Invalidates the given token, i.e., removes it from the cache. Returns false if the token did not exist.
|
||||
*
|
||||
* @param token the token to invalidate
|
||||
* @return true if the token existed and was successfully invalidated, false otherwise
|
||||
*/
|
||||
boolean invalidate(String token);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package dst.ass2.service.api.auth;
|
||||
|
||||
public class NoSuchUserException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public NoSuchUserException() {
|
||||
}
|
||||
|
||||
public NoSuchUserException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public NoSuchUserException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public NoSuchUserException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public NoSuchUserException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package dst.ass2.service.api.auth.rest;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import dst.ass2.service.api.auth.AuthenticationException;
|
||||
import dst.ass2.service.api.auth.NoSuchUserException;
|
||||
|
||||
/**
|
||||
* The IAuthenticationResource exposes parts of the {@code IAuthenticationService} as a RESTful interface.
|
||||
*/
|
||||
public interface IAuthenticationResource {
|
||||
|
||||
// TODO annotate the class and methods with the correct javax.ws.rs annotations
|
||||
|
||||
Response authenticate(String email, String password)
|
||||
throws NoSuchUserException, AuthenticationException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package dst.ass2.service.api.match;
|
||||
|
||||
import dst.ass2.service.api.trip.InvalidTripException;
|
||||
import dst.ass2.service.api.trip.MoneyDTO;
|
||||
import dst.ass2.service.api.trip.TripDTO;
|
||||
|
||||
/**
|
||||
* An implementation of this interface is provided to you by the application.
|
||||
*/
|
||||
public interface IMatchingService {
|
||||
|
||||
/**
|
||||
* The passed trip needs at least the following properties set: riderId, pickup, destination and fare
|
||||
*
|
||||
* @param trip the trip the MatchingService will calculate a fare for
|
||||
* @return the proposed fare for the given trip
|
||||
* @throws InvalidTripException in case the route cannot be calculated
|
||||
*/
|
||||
MoneyDTO calculateFare(TripDTO trip) throws InvalidTripException;
|
||||
|
||||
/**
|
||||
* Puts the trip into the queue for driver matching.
|
||||
* The trip with the specified ID needs at least the following properties set:
|
||||
* riderId, pickup, destination and fare
|
||||
*
|
||||
* @param tripId
|
||||
*/
|
||||
void queueTripForMatching(Long tripId);
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package dst.ass2.service.api.trip;
|
||||
|
||||
public class DriverNotAvailableException extends Exception {
|
||||
|
||||
public DriverNotAvailableException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DriverNotAvailableException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package dst.ass2.service.api.trip;
|
||||
|
||||
/**
|
||||
* Exception indicating that a resource that was trying to be accessed does not exist.
|
||||
*/
|
||||
public class EntityNotFoundException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public EntityNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public EntityNotFoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package dst.ass2.service.api.trip;
|
||||
|
||||
public interface ITripService {
|
||||
|
||||
/**
|
||||
* Creates and persists a Trip, sets the state to CREATED and calculates an initial fare estimation for
|
||||
* the route 'pickupId - destinationId'
|
||||
*
|
||||
* @param riderId the id of the rider, who is planning a trip
|
||||
* @param pickupId the id of the pickupId location
|
||||
* @param destinationId the id of the destinationId location
|
||||
* @return a TripDTO corresponding to the persisted Trip and includes the fare (null if route is invalid)
|
||||
* @throws EntityNotFoundException if the rider or one of the locations doesn't exist
|
||||
*/
|
||||
TripDTO create(Long riderId, Long pickupId, Long destinationId) throws EntityNotFoundException;
|
||||
|
||||
|
||||
/**
|
||||
* Confirms the given trip (i.e., sets the state of the corresponding trip to QUEUED and
|
||||
* puts it into the queue for matching), if possible (i.e., the trip is still in CREATED state)
|
||||
*
|
||||
* @param tripId the trip to confirm
|
||||
* @throws EntityNotFoundException if the trip cannot be found
|
||||
* @throws IllegalStateException in case the trip is not in state CREATED or the rider is null
|
||||
* @throws InvalidTripException in case the fare couldn't be estimated
|
||||
*/
|
||||
void confirm(Long tripId) throws EntityNotFoundException, IllegalStateException, InvalidTripException;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a match for the given trip and sets the trip's state to MATCHED, if possible (i.e., if the trips is
|
||||
* QUEUED). Rolls back transaction in case something goes wrong and re-queues the trip for a new match.
|
||||
* You can assume that the locations (pickup, destination and stops) haven't been deleted and will not be deleted
|
||||
* during the execution of this method.
|
||||
*
|
||||
* @param tripId the id of the trip the match will be created for
|
||||
* @param match the match, containing the driver, the vehicle and the fare
|
||||
* @throws EntityNotFoundException in case one of the following doesn't exist anymore: trip, driver or
|
||||
* vehicle
|
||||
* @throws DriverNotAvailableException in case the driver was assigned in the meantime to another customer
|
||||
* @throws IllegalStateException in case the rider of the trip is null or the trip is not in QUEUED state
|
||||
*/
|
||||
void match(Long tripId, MatchDTO match) throws EntityNotFoundException, DriverNotAvailableException, IllegalStateException;
|
||||
|
||||
|
||||
/**
|
||||
* Completes the trip (i. e., persists a TripInfo object and set the trips state to COMPLETED)
|
||||
*
|
||||
* @param tripId the id of the trip to complete
|
||||
* @param tripInfoDTO the tripInfo to persist
|
||||
* @throws EntityNotFoundException in case the trip doesn't exist
|
||||
*/
|
||||
void complete(Long tripId, TripInfoDTO tripInfoDTO) throws EntityNotFoundException;
|
||||
|
||||
|
||||
/**
|
||||
* Cancels the given trip (i.e., sets the state to cancelled)
|
||||
*
|
||||
* @param tripId the trip to cancel
|
||||
* @throws EntityNotFoundException in case the trip doesn't exist
|
||||
*/
|
||||
void cancel(Long tripId) throws EntityNotFoundException;
|
||||
|
||||
|
||||
/**
|
||||
* Adds the location as a stop if possible (i.e., if the referenced trip is still in the CREATED state and
|
||||
* the given location is not already in the list of stops.)
|
||||
* As a side effect, the list of the passed TripDTO is modified and the fare freshly estimated.
|
||||
* In case the the estimation fails, sets the fare to null.
|
||||
*
|
||||
* You can assume that the passed TripDTO and the Trip entity have the same values
|
||||
* @param trip the trip
|
||||
* @param locationId the location
|
||||
* @return true if the stop was added, otherwise false
|
||||
* @throws EntityNotFoundException in case the location or trip doesn't exist
|
||||
* @throws IllegalStateException in case the trip isn't longer in the CREATED state
|
||||
*/
|
||||
boolean addStop(TripDTO trip, Long locationId) throws EntityNotFoundException, IllegalStateException;
|
||||
|
||||
|
||||
/**
|
||||
* Removes the location from the stops only (i.e., if the referenced trip is still in the CREATED state).
|
||||
* As a side effect, the list of the passed TripDTO is modified and the fare freshly estimated.
|
||||
* In case the the estimation fails, sets the fare to null.
|
||||
*
|
||||
* You can assume that the passed TripDTO and the Trip entity have the same values
|
||||
* @param trip the trip
|
||||
* @param locationId the location to remove
|
||||
* @return true if the trip was removed, otherwise false (for example when the location wasn't added to the stops)
|
||||
* @throws EntityNotFoundException in case the location or trip doesn't exist
|
||||
* @throws IllegalStateException in case the trip isn't longer in the CREATED state
|
||||
*/
|
||||
boolean removeStop(TripDTO trip, Long locationId) throws EntityNotFoundException, IllegalStateException;
|
||||
|
||||
|
||||
/**
|
||||
* Removes the trip with the given id.
|
||||
*
|
||||
* @param tripId the id of the trip to remove
|
||||
* @throws EntityNotFoundException in case the trip doesn't exist
|
||||
*/
|
||||
void delete(Long tripId) throws EntityNotFoundException;
|
||||
|
||||
|
||||
/**
|
||||
* Finds the trip with the given id and returns it as DTO, including the latest fare estimation.
|
||||
*
|
||||
* @param tripId the id of the trip
|
||||
* @return if found the DTO, otherwise null
|
||||
*/
|
||||
TripDTO find(Long tripId);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package dst.ass2.service.api.trip;
|
||||
|
||||
public class InvalidTripException extends Exception {
|
||||
public InvalidTripException() {
|
||||
}
|
||||
|
||||
public InvalidTripException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package dst.ass2.service.api.trip;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class MatchDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long driverId;
|
||||
private Long vehicleId;
|
||||
|
||||
private MoneyDTO fare;
|
||||
|
||||
public Long getDriverId() {
|
||||
return driverId;
|
||||
}
|
||||
|
||||
public void setDriverId(Long driverId) {
|
||||
this.driverId = driverId;
|
||||
}
|
||||
|
||||
public Long getVehicleId() {
|
||||
return vehicleId;
|
||||
}
|
||||
|
||||
public void setVehicleId(Long vehicleId) {
|
||||
this.vehicleId = vehicleId;
|
||||
}
|
||||
|
||||
|
||||
public MoneyDTO getFare() {
|
||||
return fare;
|
||||
}
|
||||
|
||||
public void setFare(MoneyDTO fare) {
|
||||
this.fare = fare;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package dst.ass2.service.api.trip;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Objects;
|
||||
|
||||
public class MoneyDTO {
|
||||
|
||||
private String currency;
|
||||
private BigDecimal value;
|
||||
|
||||
public String getCurrency() {
|
||||
return currency;
|
||||
}
|
||||
|
||||
public void setCurrency(String currency) {
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
public BigDecimal getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(BigDecimal value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
MoneyDTO moneyDTO = (MoneyDTO) o;
|
||||
return Objects.equals(getCurrency(), moneyDTO.getCurrency()) &&
|
||||
Objects.equals(getValue(), moneyDTO.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getCurrency(), getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MoneyDTO{" +
|
||||
"currency='" + currency + '\'' +
|
||||
", value=" + value +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package dst.ass2.service.api.trip;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class TripDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private Long riderId;
|
||||
private Long pickupId;
|
||||
private Long destinationId;
|
||||
private List<Long> stops;
|
||||
private MoneyDTO fare;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getRiderId() {
|
||||
return riderId;
|
||||
}
|
||||
|
||||
public void setRiderId(Long riderId) {
|
||||
this.riderId = riderId;
|
||||
}
|
||||
|
||||
public Long getPickupId() {
|
||||
return pickupId;
|
||||
}
|
||||
|
||||
public void setPickupId(Long pickupId) {
|
||||
this.pickupId = pickupId;
|
||||
}
|
||||
|
||||
public Long getDestinationId() {
|
||||
return destinationId;
|
||||
}
|
||||
|
||||
public void setDestinationId(Long destinationId) {
|
||||
this.destinationId = destinationId;
|
||||
}
|
||||
|
||||
public List<Long> getStops() {
|
||||
if (stops == null) {
|
||||
stops = new LinkedList<>();
|
||||
}
|
||||
return stops;
|
||||
}
|
||||
|
||||
public void setStops(List<Long> stops) {
|
||||
this.stops = stops;
|
||||
}
|
||||
|
||||
public MoneyDTO getFare() {
|
||||
return fare;
|
||||
}
|
||||
|
||||
public void setFare(MoneyDTO fare) {
|
||||
this.fare = fare;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package dst.ass2.service.api.trip;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class TripInfoDTO {
|
||||
|
||||
private Double distance;
|
||||
private Date completed;
|
||||
private MoneyDTO fare;
|
||||
|
||||
public Double getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
public void setDistance(Double distance) {
|
||||
this.distance = distance;
|
||||
}
|
||||
|
||||
public Date getCompleted() {
|
||||
return completed;
|
||||
}
|
||||
|
||||
public void setCompleted(Date completed) {
|
||||
this.completed = completed;
|
||||
}
|
||||
|
||||
|
||||
public MoneyDTO getFare() {
|
||||
return fare;
|
||||
}
|
||||
|
||||
public void setFare(MoneyDTO fare) {
|
||||
this.fare = fare;
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package dst.ass2.service.api.trip.rest;
|
||||
|
||||
import dst.ass2.service.api.trip.*;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
|
||||
/**
|
||||
* This interface exposes the {@code ITripService} as a RESTful interface.
|
||||
*/
|
||||
public interface ITripServiceResource {
|
||||
|
||||
// TODO annotate the class and methods with the correct javax.ws.rs annotations
|
||||
|
||||
Response createTrip(Long riderId, Long pickupId, Long destinationId)
|
||||
throws EntityNotFoundException, InvalidTripException;
|
||||
|
||||
Response confirm(Long tripId) throws EntityNotFoundException, InvalidTripException;
|
||||
|
||||
Response getTrip(Long tripId) throws EntityNotFoundException;
|
||||
|
||||
Response deleteTrip(Long tripId) throws EntityNotFoundException;
|
||||
|
||||
Response addStop(Long tripId, Long locationId) throws InvalidTripException, EntityNotFoundException;
|
||||
|
||||
Response removeStop(Long tripId, Long locationId) throws InvalidTripException, EntityNotFoundException;
|
||||
|
||||
Response match(Long tripId, MatchDTO matchDTO) throws EntityNotFoundException, DriverNotAvailableException;
|
||||
|
||||
Response complete(Long tripId, TripInfoDTO tripInfoDTO) throws EntityNotFoundException;
|
||||
|
||||
Response cancel(Long tripId) throws EntityNotFoundException;
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
syntax = "proto3";
|
||||
|
||||
// TODO implement authentication service
|
||||
@@ -0,0 +1,66 @@
|
||||
package dst.ass2.proto.auth;
|
||||
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.ServiceDescriptor;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.hasItems;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class ProtoSpecificationTest {
|
||||
|
||||
private ClassLoader cl;
|
||||
public static final String SERVICE_NAME = "dst.ass2.service.api.auth.proto.AuthService";
|
||||
public static final String GRPC_CLASS_NAME = SERVICE_NAME + "Grpc";
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
cl = ProtoSpecificationTest.class.getClassLoader();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generatedClass_exists() throws Exception {
|
||||
try {
|
||||
cl.loadClass(GRPC_CLASS_NAME);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new AssertionError("Classpath did not contain expected gRPC service class", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generatedClass_hasMethods() throws Exception {
|
||||
assertThat(getMethodDescriptors().keySet(), hasItems(
|
||||
SERVICE_NAME + "/authenticate",
|
||||
SERVICE_NAME + "/validateToken"
|
||||
));
|
||||
}
|
||||
|
||||
private Map<String, MethodDescriptor> getMethodDescriptors() throws ClassNotFoundException {
|
||||
return getMethodDescriptors(getServiceDescriptor(cl.loadClass(GRPC_CLASS_NAME)));
|
||||
}
|
||||
|
||||
private Map<String, MethodDescriptor> getMethodDescriptors(ServiceDescriptor sd) {
|
||||
return sd.getMethods().stream()
|
||||
.collect(Collectors.toMap(
|
||||
MethodDescriptor::getFullMethodName,
|
||||
Function.identity()
|
||||
));
|
||||
}
|
||||
|
||||
private ServiceDescriptor getServiceDescriptor(Class<?> grpcClass) {
|
||||
try {
|
||||
Method getServiceDescriptor = grpcClass.getDeclaredMethod("getServiceDescriptor");
|
||||
getServiceDescriptor.setAccessible(true);
|
||||
return (ServiceDescriptor) getServiceDescriptor.invoke(null);
|
||||
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
|
||||
throw new RuntimeException("Error finding service descriptor in " + grpcClass.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>dst</artifactId>
|
||||
<version>2021.1</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>ass2-service-auth-client</artifactId>
|
||||
|
||||
<name>DST :: Assignment 2 :: Service :: Auth Client</name>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>ass2-service-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-protobuf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-stub</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-netty</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>ass2-service-auth</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>ass1-jpa</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>ass2-service-auth</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-orm</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package dst.ass2.service.auth.client;
|
||||
|
||||
/**
|
||||
* This class holds the host and port value used to connect to the gRPC server. The CDI context provides an instance
|
||||
* that you can inject into your implementation of {@link IAuthenticationClient}. The config values are loaded from the
|
||||
* application.properties file.
|
||||
*/
|
||||
public class AuthenticationClientProperties {
|
||||
|
||||
private String host;
|
||||
private int port;
|
||||
|
||||
public AuthenticationClientProperties() {
|
||||
|
||||
}
|
||||
|
||||
public AuthenticationClientProperties(String host, int port) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package dst.ass2.service.auth.client;
|
||||
|
||||
import dst.ass2.service.api.auth.AuthenticationException;
|
||||
import dst.ass2.service.api.auth.NoSuchUserException;
|
||||
|
||||
public interface IAuthenticationClient extends AutoCloseable {
|
||||
|
||||
String authenticate(String email, String password) throws NoSuchUserException, AuthenticationException;
|
||||
|
||||
boolean isTokenValid(String token);
|
||||
|
||||
/**
|
||||
* Shuts down any underlying resource required to maintain this client.
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package dst.ass2.service.auth.client.impl;
|
||||
|
||||
import dst.ass2.service.api.auth.AuthenticationException;
|
||||
import dst.ass2.service.api.auth.NoSuchUserException;
|
||||
import dst.ass2.service.auth.client.AuthenticationClientProperties;
|
||||
import dst.ass2.service.auth.client.IAuthenticationClient;
|
||||
|
||||
public class GrpcAuthenticationClient implements IAuthenticationClient {
|
||||
|
||||
// TODO make use of the generated grpc sources to implement a blocking client
|
||||
|
||||
public GrpcAuthenticationClient(AuthenticationClientProperties properties) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public String authenticate(String email, String password) throws NoSuchUserException, AuthenticationException {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTokenValid(String token) {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package dst.ass2.service.auth.client;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import dst.ass1.jpa.tests.TestData;
|
||||
import dst.ass2.service.api.auth.AuthenticationException;
|
||||
import dst.ass2.service.api.auth.NoSuchUserException;
|
||||
import dst.ass2.service.auth.AuthenticationServiceApplication;
|
||||
import dst.ass2.service.auth.client.impl.GrpcAuthenticationClient;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = AuthenticationServiceApplication.class)
|
||||
@Transactional
|
||||
@ActiveProfiles({"testdata", "grpc"})
|
||||
public class AuthenticationClientTest {
|
||||
|
||||
@Value("${grpc.port}")
|
||||
private int port;
|
||||
|
||||
private IAuthenticationClient client;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
AuthenticationClientProperties properties = new AuthenticationClientProperties("localhost", port);
|
||||
client = new GrpcAuthenticationClient(properties);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
client.close();
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchUserException.class)
|
||||
public void authenticate_invalidUser_throwsException() throws Exception {
|
||||
client.authenticate("nonexisting@example.com", "foo");
|
||||
}
|
||||
|
||||
@Test(expected = AuthenticationException.class)
|
||||
public void authenticate_invalidPassword_throwsException() throws Exception {
|
||||
client.authenticate(TestData.RIDER_1_EMAIL, "foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticate_existingUser_returnsToken() throws Exception {
|
||||
String token = client.authenticate(TestData.RIDER_1_EMAIL, TestData.RIDER_1_PW);
|
||||
assertNotNull(token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isTokenValid_invalidToken_returnsFalse() throws Exception {
|
||||
boolean valid = client.isTokenValid(UUID.randomUUID().toString()); // should be false in *almost* all cases ;-)
|
||||
assertFalse(valid);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isTokenValid_onCreatedToken_returnsTrue() throws Exception {
|
||||
String token = client.authenticate(TestData.RIDER_2_EMAIL, TestData.RIDER_2_PW);
|
||||
boolean valid = client.isTokenValid(token);
|
||||
assertTrue(valid);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>dst</artifactId>
|
||||
<version>2021.1</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>ass2-service-auth</artifactId>
|
||||
|
||||
<name>DST :: Assignment 2 :: Service :: Auth Server</name>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>ass1-jpa</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>ass2-service-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-protobuf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-stub</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-netty</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>ass1-jpa</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-orm</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<extensions>
|
||||
<extension>
|
||||
<groupId>kr.motd.maven</groupId>
|
||||
<artifactId>os-maven-plugin</artifactId>
|
||||
</extension>
|
||||
</extensions>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.xolstice.maven.plugins</groupId>
|
||||
<artifactId>protobuf-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package dst.ass2.service.auth;
|
||||
|
||||
import dst.ass2.service.api.auth.AuthenticationException;
|
||||
import dst.ass2.service.api.auth.IAuthenticationService;
|
||||
import dst.ass2.service.api.auth.NoSuchUserException;
|
||||
|
||||
public interface ICachingAuthenticationService extends IAuthenticationService {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* Instead of checking database records directly, the method first checks the cache for existing users. If the user
|
||||
* is not in the cache, then the service checks the database for the given email address, and updates the cache if
|
||||
* necessary.
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
String authenticate(String email, String password) throws NoSuchUserException, AuthenticationException;
|
||||
|
||||
/**
|
||||
* Loads user data from the database into memory.
|
||||
*/
|
||||
void loadData();
|
||||
|
||||
/**
|
||||
* Clears the data cached from the database.
|
||||
*/
|
||||
void clearCache();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package dst.ass2.service.auth.grpc;
|
||||
|
||||
/**
|
||||
* This class holds the port value used to bind the gRPC server. The CDI context provides an instance that you can
|
||||
* inject into your implementation of {@link IGrpcServerRunner}. The config values are loaded from the
|
||||
* grpc.properties.
|
||||
*/
|
||||
public class GrpcServerProperties {
|
||||
|
||||
private int port;
|
||||
|
||||
public GrpcServerProperties() {
|
||||
}
|
||||
|
||||
public GrpcServerProperties(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package dst.ass2.service.auth.grpc;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An implementation of this interface is expected by the application to start the grpc server. Inject get
|
||||
* {@link GrpcServerProperties} to access the configuration.
|
||||
*/
|
||||
public interface IGrpcServerRunner {
|
||||
/**
|
||||
* Starts the gRPC server.
|
||||
*
|
||||
* @throws IOException start error
|
||||
*/
|
||||
void run() throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} - %highlight(%5p) [%12.12thread] %cyan(%-40.40logger{39}): %m%n</pattern>
|
||||
</encoder>
|
||||
|
||||
</appender>
|
||||
|
||||
<root level="${log.level:-INFO}">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
</configuration>
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package dst.ass2.service.auth;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableTransactionManagement
|
||||
public class AuthenticationServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AuthenticationServiceApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package dst.ass2.service.auth;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalEntityManagerFactoryBean;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import dst.ass1.jpa.dao.IDAOFactory;
|
||||
import dst.ass1.jpa.dao.impl.DAOFactory;
|
||||
import dst.ass1.jpa.model.IModelFactory;
|
||||
import dst.ass1.jpa.model.impl.ModelFactory;
|
||||
import dst.ass1.jpa.tests.TestData;
|
||||
import dst.ass1.jpa.util.Constants;
|
||||
import dst.ass2.service.auth.grpc.GrpcServerProperties;
|
||||
|
||||
@SpringBootConfiguration
|
||||
@PropertySource("classpath:/dst/ass2/service/auth/grpc.properties")
|
||||
public class AuthenticationServiceApplicationConfig {
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager em;
|
||||
|
||||
@Bean
|
||||
public IModelFactory modelFactory() {
|
||||
return new ModelFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IDAOFactory daoFactory() {
|
||||
return new DAOFactory(em);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GrpcServerProperties grpcServerProperties(@Value("${grpc.port}") int port) {
|
||||
return new GrpcServerProperties(port);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Profile("grpc")
|
||||
public SpringGrpcServerRunner springGrpcServerRunner() {
|
||||
return new SpringGrpcServerRunner();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LocalEntityManagerFactoryBean entityManagerFactoryBean() {
|
||||
LocalEntityManagerFactoryBean bean = new LocalEntityManagerFactoryBean();
|
||||
bean.setPersistenceUnitName(Constants.JPA_PERSISTENCE_UNIT);
|
||||
// fixes collection proxy problem when using jersey
|
||||
bean.getJpaPropertyMap().put("hibernate.enable_lazy_load_no_trans", true);
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager transactionManager(LocalEntityManagerFactoryBean entityManagerFactoryBean) {
|
||||
JpaTransactionManager transactionManager = new JpaTransactionManager();
|
||||
transactionManager.setPersistenceUnitName(Constants.JPA_PERSISTENCE_UNIT);
|
||||
transactionManager.setEntityManagerFactory(entityManagerFactoryBean.getObject());
|
||||
return transactionManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Profile("testdata")
|
||||
public TestData testData() {
|
||||
return new TestData();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Profile("testdata")
|
||||
public TestDataInserter testDataInserter(TestData testData, IModelFactory modelFactory, PlatformTransactionManager transactionManager) {
|
||||
return new TestDataInserter(testData, modelFactory, transactionManager);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Profile("testdata")
|
||||
public AuthServiceDataInjector dataInjector(TestDataInserter testDataInserter) {
|
||||
return new AuthServiceDataInjector(em, testDataInserter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure data is in the database before the {@link ICachingAuthenticationService} is initialized.
|
||||
*/
|
||||
public static class AuthServiceDataInjector implements BeanPostProcessor {
|
||||
private boolean dataInjected = false;
|
||||
|
||||
private EntityManager em;
|
||||
private TestDataInserter testDataInserter;
|
||||
|
||||
public AuthServiceDataInjector(EntityManager em, TestDataInserter testDataInserter) {
|
||||
this.em = em;
|
||||
this.testDataInserter = testDataInserter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (!dataInjected && (bean instanceof ICachingAuthenticationService)) {
|
||||
testDataInserter.insertTestData(em);
|
||||
dataInjected = true;
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package dst.ass2.service.auth;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
||||
import dst.ass2.service.auth.grpc.IGrpcServerRunner;
|
||||
|
||||
/**
|
||||
* This class loads the {@link IGrpcServerRunner} from the application context and runs it after the application starts.
|
||||
*/
|
||||
public class SpringGrpcServerRunner implements CommandLineRunner, ApplicationContextAware {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SpringGrpcServerRunner.class);
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
LOG.info("Getting instance of GrpcServerRunner");
|
||||
IGrpcServerRunner bean = applicationContext.getBean(IGrpcServerRunner.class);
|
||||
LOG.info("Starting IGrpcServerRunner instance {}", bean);
|
||||
bean.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package dst.ass2.service.auth;
|
||||
|
||||
import dst.ass1.jpa.model.IModelFactory;
|
||||
import dst.ass1.jpa.tests.TestData;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
public class TestDataInserter {
|
||||
|
||||
private PlatformTransactionManager transactionManager;
|
||||
private IModelFactory modelFactory;
|
||||
private TestData testData;
|
||||
|
||||
public TestDataInserter(TestData testData, IModelFactory modelFactory, PlatformTransactionManager transactionManager) {
|
||||
this.testData = testData;
|
||||
this.modelFactory = modelFactory;
|
||||
this.transactionManager = transactionManager;
|
||||
}
|
||||
|
||||
public void insertTestData(EntityManager em) {
|
||||
TransactionTemplate tx = new TransactionTemplate(transactionManager);
|
||||
tx.execute(status -> {
|
||||
testData.insert(modelFactory, em);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package dst.ass2.service.auth.tests;
|
||||
|
||||
import dst.ass1.jpa.model.IModelFactory;
|
||||
import dst.ass1.jpa.model.IPreferences;
|
||||
import dst.ass1.jpa.model.IRider;
|
||||
import dst.ass1.jpa.tests.TestData;
|
||||
import dst.ass2.service.api.auth.AuthenticationException;
|
||||
import dst.ass2.service.api.auth.NoSuchUserException;
|
||||
import dst.ass2.service.auth.AuthenticationServiceApplication;
|
||||
import dst.ass2.service.auth.ICachingAuthenticationService;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.transaction.Transactional;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = AuthenticationServiceApplication.class)
|
||||
@Transactional
|
||||
@ActiveProfiles("testdata")
|
||||
public class AuthenticationServiceTest implements ApplicationContextAware {
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager em;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private IModelFactory modelFactory;
|
||||
private ICachingAuthenticationService authenticationService;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
|
||||
applicationContext = ctx;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
modelFactory = applicationContext.getBean(IModelFactory.class);
|
||||
authenticationService = applicationContext.getBean(ICachingAuthenticationService.class);
|
||||
|
||||
// reload the data before each test
|
||||
authenticationService.loadData();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticate_existingUser_createsTokenCorrectly() throws Exception {
|
||||
String token = authenticationService.authenticate(TestData.RIDER_1_EMAIL, TestData.RIDER_1_PW);
|
||||
assertNotNull(token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticate_existingUserNotInCache_createsTokenCorrectly() throws Exception {
|
||||
IRider p = modelFactory.createRider();
|
||||
|
||||
p.setEmail("non-cached@example.com");
|
||||
try {
|
||||
p.setPassword(MessageDigest.getInstance("SHA1").digest("somepw".getBytes()));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
p.setName("non-cached");
|
||||
p.setAccountNo("accountno");
|
||||
p.setBankCode("bankcode");
|
||||
p.setTel("+43158801");
|
||||
IPreferences prefs = modelFactory.createPreferences();
|
||||
p.setPreferences(prefs);
|
||||
|
||||
em.persist(prefs);
|
||||
em.persist(p);
|
||||
|
||||
String token = authenticationService.authenticate(p.getEmail(), "somepw");
|
||||
assertNotNull(token);
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchUserException.class)
|
||||
public void authenticate_invalidUser_throwsException() throws Exception {
|
||||
authenticationService.authenticate("nonexisting@example.com", "foo");
|
||||
}
|
||||
|
||||
@Test(expected = AuthenticationException.class)
|
||||
public void authenticate_invalidPassword_throwsException() throws Exception {
|
||||
authenticationService.authenticate(TestData.RIDER_1_EMAIL, "foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changePassword_existingUser_passwordChanged() throws Exception {
|
||||
authenticationService.changePassword(TestData.RIDER_1_EMAIL, "newPwd");
|
||||
assertNotNull(authenticationService.authenticate(TestData.RIDER_1_EMAIL, "newPwd"));
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchUserException.class)
|
||||
public void changePassword_nonExistingUser_throwsException() throws Exception {
|
||||
authenticationService.changePassword("nonexisting@example.com", "foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUser_existingToken_returnsUser() throws Exception {
|
||||
String token = authenticationService.authenticate(TestData.RIDER_1_EMAIL, TestData.RIDER_1_PW);
|
||||
assertEquals(TestData.RIDER_1_EMAIL, authenticationService.getUser(token));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUser_nonExistingToken_returnsNull() throws Exception {
|
||||
assertNull(authenticationService.getUser("invalidToken"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isValid_existingToken_returnsTrue() throws Exception {
|
||||
String token = authenticationService.authenticate(TestData.RIDER_1_EMAIL, TestData.RIDER_1_PW);
|
||||
assertTrue(authenticationService.isValid(token));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isValid_nonExistingToken_returnsFalse() throws Exception {
|
||||
assertFalse(authenticationService.isValid("invalidToken"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidate_validToken_tokenInvalidatedReturnsTrue() throws Exception {
|
||||
String token = authenticationService.authenticate(TestData.RIDER_1_EMAIL, TestData.RIDER_1_PW);
|
||||
assertTrue(authenticationService.invalidate(token));
|
||||
assertFalse(authenticationService.isValid(token));
|
||||
assertNull(authenticationService.getUser(token));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidate_invalidToken_returnsFalse() throws Exception {
|
||||
assertFalse(authenticationService.invalidate("invalidToken"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package dst.ass2.service.auth.tests;
|
||||
|
||||
import dst.ass2.service.auth.AuthenticationServiceApplication;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
@ActiveProfiles({"testdata", "grpc"})
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = AuthenticationServiceApplication.class)
|
||||
public class GrpcServerRunnerTest {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GrpcServerRunnerTest.class);
|
||||
|
||||
@Value("${grpc.port}")
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
public void canConnectToGrpcSocketAfterApplicationInitialization() throws Exception {
|
||||
int n = 4;
|
||||
|
||||
while (true) {
|
||||
LOG.info("Tyring to connect to TCP socket on localhost:{}", port);
|
||||
try (Socket socket = new Socket("localhost", port)) {
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
if (n == 0) {
|
||||
throw new AssertionError("Expected gRPC server to run on port " + port, e);
|
||||
} else {
|
||||
Thread.sleep(250);
|
||||
}
|
||||
}
|
||||
n--;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
grpc.port=50051
|
||||
@@ -0,0 +1,53 @@
|
||||
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>dst</artifactId>
|
||||
<version>2021.1</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>ass2-service-facade</artifactId>
|
||||
|
||||
<name>DST :: Assignment 2 :: Service :: Facade</name>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>ass2-service-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>ass2-service-auth-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.core</groupId>
|
||||
<artifactId>jersey-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.ext</groupId>
|
||||
<artifactId>jersey-proxy-client</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jersey</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package dst.ass2.service.facade;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class ServiceFacadeApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ServiceFacadeApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package dst.ass2.service.facade;
|
||||
|
||||
import dst.ass2.service.api.auth.AuthenticationException;
|
||||
import dst.ass2.service.api.auth.NoSuchUserException;
|
||||
import dst.ass2.service.auth.client.AuthenticationClientProperties;
|
||||
import dst.ass2.service.auth.client.IAuthenticationClient;
|
||||
import dst.ass2.service.auth.client.impl.GrpcAuthenticationClient;
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
@SpringBootConfiguration
|
||||
public class ServiceFacadeApplicationConfig {
|
||||
|
||||
@Bean
|
||||
public ResourceConfig jerseyConfig() {
|
||||
return new ResourceConfig()
|
||||
.packages("dst.ass2.service.facade");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public URI tripServiceURI(@Value("${tripservice.uri}") URI target) {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationClientProperties authenticationClientProperties(
|
||||
@Value("${auth.host}") String host,
|
||||
@Value("${auth.port}") int port) {
|
||||
return new AuthenticationClientProperties(host, port);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Profile("!AuthenticationResourceTest")
|
||||
// only use this when we're not running individual tests
|
||||
public IAuthenticationClient grpcAuthenticationClient(AuthenticationClientProperties authenticationClientProperties) {
|
||||
return new GrpcAuthenticationClient(authenticationClientProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Profile("AuthenticationResourceTest")
|
||||
public IAuthenticationClient mockAuthenticationClient() {
|
||||
return new MockAuthenticationClient();
|
||||
}
|
||||
|
||||
public static class MockAuthenticationClient implements IAuthenticationClient {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MockAuthenticationClient.class);
|
||||
|
||||
public static String TOKEN = "123e4567-e89b-12d3-a456-426655440000";
|
||||
|
||||
@Override
|
||||
public String authenticate(String email, String password) throws NoSuchUserException, AuthenticationException {
|
||||
LOG.info("Calling MockAuthenticationClient with {}, {}", email, password);
|
||||
|
||||
if (email.equals("junit@example.com")) {
|
||||
if (password.equals("junit")) {
|
||||
return TOKEN;
|
||||
}
|
||||
throw new AuthenticationException();
|
||||
}
|
||||
throw new NoSuchUserException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTokenValid(String t) {
|
||||
return TOKEN.equals(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package dst.ass2.service.facade.test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.allOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.BufferingClientHttpRequestFactory;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import dst.ass2.service.facade.ServiceFacadeApplication;
|
||||
import dst.ass2.service.facade.ServiceFacadeApplicationConfig;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = ServiceFacadeApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("AuthenticationResourceTest")
|
||||
public class AuthenticationResourceTest {
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
private RestTemplate restTemplate;
|
||||
private HttpHeaders headers;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
headers = new HttpHeaders();
|
||||
restTemplate = new RestTemplate();
|
||||
|
||||
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
|
||||
BufferingClientHttpRequestFactory bufferingClientHttpRequestFactory = new BufferingClientHttpRequestFactory(requestFactory);
|
||||
requestFactory.setOutputStreaming(false);
|
||||
restTemplate.setRequestFactory(bufferingClientHttpRequestFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticate_withValidUser_returnsOkAndToken() throws Exception {
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
String url = url("/auth/authenticate");
|
||||
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
|
||||
body.add("email", "junit@example.com");
|
||||
body.add("password", "junit");
|
||||
|
||||
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
|
||||
|
||||
assertThat(response.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||
assertNotNull(response.getBody());
|
||||
assertThat(response.getBody(), is(ServiceFacadeApplicationConfig.MockAuthenticationClient.TOKEN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticate_withInvalidUser_returnsAppropriateCode() throws Exception {
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
String url = url("/auth/authenticate");
|
||||
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
|
||||
body.add("email", "nonexisting@example.com");
|
||||
body.add("password", "wrong");
|
||||
|
||||
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||
HttpStatus status;
|
||||
|
||||
try {
|
||||
status = restTemplate.postForEntity(url, request, String.class).getStatusCode();
|
||||
} catch (HttpClientErrorException e) {
|
||||
status = e.getStatusCode();
|
||||
}
|
||||
|
||||
assertThat("Return an appropriate error code", status, allOf(
|
||||
not(HttpStatus.OK),
|
||||
not(HttpStatus.NOT_FOUND),
|
||||
not(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
));
|
||||
}
|
||||
|
||||
private String url(String uri) {
|
||||
return "http://localhost:" + port + uri;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
server.port=8090
|
||||
|
||||
# this is the gRPC host the client tries to connect to
|
||||
auth.host=localhost
|
||||
auth.port=50051
|
||||
|
||||
# this is the root of the remote resource that the facade accesses via its client
|
||||
tripservice.uri=http://localhost:8091/
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} - %highlight(%5p) [%12.12thread] %cyan(%-40.40logger{39}): %m%n</pattern>
|
||||
</encoder>
|
||||
|
||||
</appender>
|
||||
|
||||
<root level="${log.level:-INFO}">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,62 @@
|
||||
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>dst</artifactId>
|
||||
<version>2021.1</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>ass2-service-trip</artifactId>
|
||||
|
||||
<name>DST :: Assignment 2 :: Service :: Trip</name>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>ass1-jpa</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>ass2-service-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>ass1-jpa</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-orm</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jersey</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,73 @@
|
||||
package dst.ass2.service.trip;
|
||||
|
||||
import dst.ass1.jpa.dao.IDAOFactory;
|
||||
import dst.ass1.jpa.dao.IDriverDAO;
|
||||
import dst.ass1.jpa.model.IDriver;
|
||||
import dst.ass2.service.api.match.IMatchingService;
|
||||
import dst.ass2.service.api.trip.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.ManagedBean;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.inject.Inject;
|
||||
import javax.transaction.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@ManagedBean
|
||||
@Transactional
|
||||
public class MatchingService implements IMatchingService {
|
||||
|
||||
@Inject
|
||||
private ITripService tripService;
|
||||
|
||||
@Inject
|
||||
private IDAOFactory daoFactory;
|
||||
|
||||
private IDriverDAO driverDAO;
|
||||
|
||||
private int driverIndex;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MatchingService.class);
|
||||
private List<IDriver> all;
|
||||
|
||||
@PostConstruct
|
||||
public void setup() {
|
||||
driverDAO = daoFactory.createDriverDAO();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MoneyDTO calculateFare(TripDTO trip) throws InvalidTripException {
|
||||
LOG.info("Calculate fare for trip: {}", trip.getId());
|
||||
BigDecimal value = Math.random() > 0.5 ? BigDecimal.ONE : BigDecimal.TEN;
|
||||
MoneyDTO moneyDTO = new MoneyDTO();
|
||||
moneyDTO.setCurrency("EUR");
|
||||
moneyDTO.setValue(value);
|
||||
return moneyDTO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueTripForMatching(Long tripId) {
|
||||
if (all == null) {
|
||||
all = driverDAO.findAll();
|
||||
driverIndex = all.size() - 2;
|
||||
}
|
||||
TripDTO trip = tripService.find(tripId);
|
||||
try {
|
||||
LOG.info("Queue trip {} for matching", tripId);
|
||||
IDriver driver = all.get(driverIndex++);
|
||||
if (driverIndex >= all.size()) {
|
||||
driverIndex = 0;
|
||||
}
|
||||
|
||||
MatchDTO matchDTO = new MatchDTO();
|
||||
matchDTO.setDriverId(driver.getId());
|
||||
matchDTO.setVehicleId(driver.getVehicle().getId());
|
||||
matchDTO.setFare(trip.getFare());
|
||||
tripService.match(tripId, matchDTO);
|
||||
} catch (EntityNotFoundException | DriverNotAvailableException e) {
|
||||
//LOG.error("Error during matching", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package dst.ass2.service.trip;
|
||||
|
||||
import dst.ass1.jpa.model.IModelFactory;
|
||||
import dst.ass1.jpa.tests.TestData;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
@SpringBootConfiguration
|
||||
@Profile("testdata")
|
||||
public class TestDataConfig implements ApplicationListener<ApplicationReadyEvent> {
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager em;
|
||||
|
||||
@Bean
|
||||
public TestData testData() {
|
||||
return new TestData();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestDataInserter testDataInserter(TestData testData, IModelFactory modelFactory, PlatformTransactionManager transactionManager) {
|
||||
return new TestDataInserter(testData, modelFactory, transactionManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationReadyEvent event) {
|
||||
event.getApplicationContext()
|
||||
.getBean(TestDataInserter.class)
|
||||
.insertTestData(em);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package dst.ass2.service.trip;
|
||||
|
||||
import dst.ass1.jpa.model.IModelFactory;
|
||||
import dst.ass1.jpa.tests.TestData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
public class TestDataInserter {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TestDataInserter.class);
|
||||
|
||||
private PlatformTransactionManager transactionManager;
|
||||
private IModelFactory modelFactory;
|
||||
private TestData testData;
|
||||
|
||||
public TestDataInserter(TestData testData, IModelFactory modelFactory, PlatformTransactionManager transactionManager) {
|
||||
this.testData = testData;
|
||||
this.modelFactory = modelFactory;
|
||||
this.transactionManager = transactionManager;
|
||||
}
|
||||
|
||||
public void insertTestData(EntityManager em) {
|
||||
LOG.info("Inserting test data...");
|
||||
TransactionTemplate tx = new TransactionTemplate(transactionManager);
|
||||
tx.execute(status -> {
|
||||
testData.insert(modelFactory, em);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package dst.ass2.service.trip;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableTransactionManagement
|
||||
public class TripApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TripApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package dst.ass2.service.trip;
|
||||
|
||||
import dst.ass1.jpa.dao.IDAOFactory;
|
||||
import dst.ass1.jpa.dao.impl.DAOFactory;
|
||||
import dst.ass1.jpa.model.IModelFactory;
|
||||
import dst.ass1.jpa.model.impl.ModelFactory;
|
||||
import dst.ass1.jpa.util.Constants;
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalEntityManagerFactoryBean;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
@SpringBootConfiguration
|
||||
public class TripApplicationConfig {
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager em;
|
||||
|
||||
@Bean
|
||||
public ResourceConfig jerseyConfig() {
|
||||
return new ResourceConfig()
|
||||
.packages("dst.ass2.service.trip");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IModelFactory modelFactory() {
|
||||
return new ModelFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IDAOFactory daoFactory() {
|
||||
return new DAOFactory(em);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LocalEntityManagerFactoryBean entityManagerFactoryBean() {
|
||||
LocalEntityManagerFactoryBean bean = new LocalEntityManagerFactoryBean();
|
||||
bean.setPersistenceUnitName(Constants.JPA_PERSISTENCE_UNIT);
|
||||
// fixes collection proxy problem when using jersey
|
||||
bean.getJpaPropertyMap().put("hibernate.enable_lazy_load_no_trans", true);
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager transactionManager(LocalEntityManagerFactoryBean entityManagerFactoryBean) {
|
||||
JpaTransactionManager transactionManager = new JpaTransactionManager();
|
||||
transactionManager.setPersistenceUnitName(Constants.JPA_PERSISTENCE_UNIT);
|
||||
transactionManager.setEntityManagerFactory(entityManagerFactoryBean.getObject());
|
||||
return transactionManager;
|
||||
}
|
||||
|
||||
}
|
||||
+616
@@ -0,0 +1,616 @@
|
||||
package dst.ass2.service.trip.tests;
|
||||
|
||||
import dst.ass1.jpa.tests.TestData;
|
||||
import dst.ass2.service.api.match.IMatchingService;
|
||||
import dst.ass2.service.api.trip.*;
|
||||
import dst.ass2.service.trip.TripApplication;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.allOf;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.core.IsNull.notNullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = TripApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("testdata")
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
public class TripServiceResourceTest {
|
||||
|
||||
@Autowired
|
||||
private TestData testData;
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@MockBean
|
||||
private IMatchingService matchingService;
|
||||
|
||||
private TestRestTemplate restTemplate;
|
||||
private HttpHeaders headers;
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
|
||||
return new RestTemplate(requestFactory);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.headers = new HttpHeaders();
|
||||
this.restTemplate = new TestRestTemplate();
|
||||
}
|
||||
|
||||
private String url(String uri) {
|
||||
return "http://localhost:" + port + uri;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createTrip_withWrongHttpMethod_returnsError() {
|
||||
String url = url("/trips");
|
||||
MultiValueMap<String, String> map = getCreateMap(2134L, 2222L, 33L);
|
||||
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class, map);
|
||||
|
||||
assertThat("Response was: " + response, response.getStatusCode().series(), is(HttpStatus.Series.CLIENT_ERROR));
|
||||
assertThat(response.getStatusCode(), not(HttpStatus.NOT_FOUND));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createTrip_withUnknownRider_returnsNotFoundError() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
String url = url("/trips");
|
||||
MultiValueMap<String, String> body = getCreateMap(3333L, testData.location1Id, testData.location2Id);
|
||||
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
|
||||
|
||||
assertThat("Response was: " + response, response.getStatusCode(), is(HttpStatus.NOT_FOUND));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void createTrip_withValidKeys_returnsTripId() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
String url = url("/trips");
|
||||
MultiValueMap<String, String> body = getCreateMap(testData.rider1Id, testData.location1Id, testData.location2Id);
|
||||
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
|
||||
|
||||
assertThat(response.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||
assertThat(response.getBody(), notNullValue());
|
||||
|
||||
try {
|
||||
Long.parseLong(response.getBody());
|
||||
} catch (NumberFormatException e) {
|
||||
throw new AssertionError("Response body of " + url + " should be a Long value (the trip ID)", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createTrip_withInvalidTrip_andThenGetTrip_hasNoFare() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenThrow(new InvalidTripException());
|
||||
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
String url = url("/trips");
|
||||
MultiValueMap<String, String> body = getCreateMap(testData.rider1Id, testData.location1Id, testData.location2Id);
|
||||
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
|
||||
|
||||
assertThat(response.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||
assertThat(response.getBody(), notNullValue());
|
||||
|
||||
try {
|
||||
long id = Long.parseLong(response.getBody());
|
||||
ResponseEntity<TripDTO> trip = getTrip(id);
|
||||
assertThat(trip.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||
assertNull(trip.getBody().getFare());
|
||||
} catch (NumberFormatException e) {
|
||||
throw new AssertionError("Response body of " + url + " should be a Long value (the trip ID)", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void findTrip_withUnknownKey_returnsNotFoundError() {
|
||||
String url = url("/trips/" + (Long) 1338L);
|
||||
ResponseEntity<String> trip = restTemplate.getForEntity(url, String.class);
|
||||
assertThat(trip.getStatusCode(), is(HttpStatus.NOT_FOUND));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findTrip_onCreatedTrip_returnsJsonEntity() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
Long pickup = testData.location5Id;
|
||||
Long rider = testData.rider4Id;
|
||||
Long destination = testData.location1Id;
|
||||
Long trip = testData.trip6Id;
|
||||
String url = url("/trips/" + trip);
|
||||
|
||||
ResponseEntity<TripDTO> response = restTemplate.getForEntity(url, TripDTO.class);
|
||||
TripDTO tripDTO = response.getBody();
|
||||
|
||||
assertThat(response.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||
assertNotNull(tripDTO);
|
||||
assertEquals(trip, tripDTO.getId());
|
||||
assertEquals(destination, tripDTO.getDestinationId());
|
||||
assertEquals(pickup, tripDTO.getPickupId());
|
||||
assertEquals(rider, tripDTO.getRiderId());
|
||||
assertEquals(3, tripDTO.getStops().size());
|
||||
assertEquals(getTen(), tripDTO.getFare());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addStop_onCreatedTrip_returnsOkAndCalculatedFare() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
String url = url("/trips/" + testData.trip6Id + "/stops");
|
||||
MultiValueMap<String, String> body = body("locationId", testData.location5Id);
|
||||
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<MoneyDTO> response = restTemplate.postForEntity(url, request, MoneyDTO.class);
|
||||
MoneyDTO moneyDTO = response.getBody();
|
||||
|
||||
assertThat(response.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||
assertNotNull(moneyDTO);
|
||||
assertEquals(getTen(), moneyDTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addStop_andThenGetTrip_containsStopInList() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
|
||||
String url = url("/trips/" + testData.trip6Id + "/stops");
|
||||
MultiValueMap<String, String> body = body("locationId", testData.location5Id);
|
||||
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<MoneyDTO> response = restTemplate.postForEntity(url, request, MoneyDTO.class);
|
||||
assertThat(response.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||
|
||||
ResponseEntity<TripDTO> tripResponse = getTrip(testData.trip6Id);
|
||||
assertThat(tripResponse.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||
assertThat(tripResponse.getBody().getStops(), hasItem(testData.location5Id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addStop_onQueuedTrip_returnsAppropriateError() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
|
||||
String url = url("/trips/" + testData.trip10Id + "/stops");
|
||||
MultiValueMap<String, String> body = body("locationId", testData.location5Id);
|
||||
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<?> response = restTemplate.postForEntity(url, request, String.class);
|
||||
|
||||
assertThat("Make use of an appropriate HTTP status code", response.getStatusCode(), allOf(
|
||||
CoreMatchers.is(not(HttpStatus.OK)),
|
||||
CoreMatchers.is(not(HttpStatus.NOT_FOUND)),
|
||||
CoreMatchers.is(not(HttpStatus.INTERNAL_SERVER_ERROR))
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addStop_withNonExistingLocation_returnsNotFoundError() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
|
||||
Long trip = testData.trip6Id;
|
||||
|
||||
String url = url("/trips/" + trip + "/stops");
|
||||
MultiValueMap<String, String> body = body("locationId", 1337L);
|
||||
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<?> response = restTemplate.postForEntity(url, request, String.class);
|
||||
assertThat(response.getStatusCode(), is(HttpStatus.NOT_FOUND));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addStop_withLocationAlreadyInStopList_returnsAppropriateError() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
|
||||
Long trip = testData.trip6Id;
|
||||
|
||||
String url = url("/trips/" + trip + "/stops");
|
||||
MultiValueMap<String, String> body = body("locationId", testData.location2Id);
|
||||
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<?> response = restTemplate.postForEntity(url, request, String.class);
|
||||
assertThat("Make use of an appropriate HTTP status code", response.getStatusCode(), allOf(
|
||||
CoreMatchers.is(not(HttpStatus.OK)),
|
||||
CoreMatchers.is(not(HttpStatus.NOT_FOUND)),
|
||||
CoreMatchers.is(not(HttpStatus.INTERNAL_SERVER_ERROR))
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addStop_withInvalidTrip_returnsOk() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenThrow(new InvalidTripException());
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
|
||||
Long trip = testData.trip6Id;
|
||||
|
||||
String url = url("/trips/" + trip + "/stops");
|
||||
MultiValueMap<String, String> body = body("locationId", testData.location5Id);
|
||||
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<?> response = restTemplate.postForEntity(url, request, String.class);
|
||||
assertThat(response.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeStop_onCreatedTrip_returnOk() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
Long trip = testData.trip6Id;
|
||||
Long location = testData.location2Id;
|
||||
String url = url("/trips/" + trip + "/stops/" + location);
|
||||
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.DELETE,
|
||||
null,
|
||||
(Class<String>) null,
|
||||
new HashMap<String, String>()
|
||||
);
|
||||
|
||||
assertThat(exchange.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeStop_andThenGetTrip_doesntContainStopInList() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
|
||||
Long trip = testData.trip6Id;
|
||||
Long location = testData.location2Id;
|
||||
String url = url("/trips/" + trip + "/stops/" + location);
|
||||
|
||||
restTemplate.delete(url, new HashMap<>());
|
||||
ResponseEntity<TripDTO> response = getTrip(trip);
|
||||
assertNotNull(response);
|
||||
List<Long> ids = response.getBody().getStops();
|
||||
assertFalse(ids.contains(location));
|
||||
assertTrue(ids.contains(testData.location3Id));
|
||||
assertTrue(ids.contains(testData.location4Id));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void removeStop_onQueuedTrip_returnsAppropriateError() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
Long trip = testData.trip10Id;
|
||||
Long location = testData.location4Id;
|
||||
String url = url("/trips/" + trip + "/stops/" + location);
|
||||
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.DELETE,
|
||||
null,
|
||||
(Class<String>) null,
|
||||
new HashMap<String, String>()
|
||||
);
|
||||
|
||||
assertThat("Make use of an appropriate HTTP status code", response.getStatusCode(), allOf(
|
||||
CoreMatchers.is(not(HttpStatus.OK)),
|
||||
CoreMatchers.is(not(HttpStatus.NOT_FOUND)),
|
||||
CoreMatchers.is(not(HttpStatus.INTERNAL_SERVER_ERROR))
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeStop_withNonExistingLocation_returnsNotFoundError() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
Long trip = testData.trip6Id;
|
||||
Long location = 1234L;
|
||||
String url = url("/trips/" + trip + "/stops/" + location);
|
||||
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.DELETE,
|
||||
null,
|
||||
(Class<String>) null,
|
||||
new HashMap<String, String>()
|
||||
);
|
||||
|
||||
assertThat(exchange.getStatusCode(), is(HttpStatus.NOT_FOUND));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void removeStop_WithLocationNotInStopsList_returnsAppropriateError() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
Long trip = testData.trip6Id;
|
||||
Long location = testData.location5Id;
|
||||
String url = url("/trips/" + trip + "/stops/" + location);
|
||||
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.DELETE,
|
||||
null,
|
||||
(Class<String>) null,
|
||||
new HashMap<String, String>()
|
||||
);
|
||||
|
||||
assertThat("Make use of an appropriate HTTP status code", response.getStatusCode(), allOf(
|
||||
CoreMatchers.is(not(HttpStatus.OK)),
|
||||
CoreMatchers.is(not(HttpStatus.NOT_FOUND)),
|
||||
CoreMatchers.is(not(HttpStatus.INTERNAL_SERVER_ERROR))
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeStop_withInvalidTrip_returnsOk() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenThrow(new InvalidTripException());
|
||||
Long trip = testData.trip6Id;
|
||||
Long location = testData.location2Id;
|
||||
String url = url("/trips/" + trip + "/stops/" + location);
|
||||
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.DELETE,
|
||||
null,
|
||||
(Class<String>) null,
|
||||
new HashMap<String, String>()
|
||||
);
|
||||
|
||||
assertThat(exchange.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void confirmTrip_withKnownTrip_shouldReturnSuccessful() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
|
||||
Long trip = testData.trip6Id;
|
||||
String url = url("/trips/" + trip + "/confirm");
|
||||
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.PATCH,
|
||||
null,
|
||||
(Class<String>) null,
|
||||
new HashMap<String, String>()
|
||||
);
|
||||
|
||||
assertThat(exchange.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void confirmTrip_withUnknownTrip_shouldReturnNotFoundError() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
|
||||
Long trip = 1244L;
|
||||
String url = url("/trips/" + trip + "/confirm");
|
||||
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.PATCH,
|
||||
null,
|
||||
(Class<String>) null,
|
||||
new HashMap<String, String>()
|
||||
);
|
||||
|
||||
assertThat(exchange.getStatusCode(), is(HttpStatus.NOT_FOUND));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void confirmTrip_withInvalidTrip_shouldReturnAppropriateError() throws InvalidTripException {
|
||||
when(matchingService.calculateFare(any())).thenThrow(new InvalidTripException());
|
||||
|
||||
Long trip = testData.trip6Id;
|
||||
String url = url("/trips/" + trip + "/confirm");
|
||||
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.PATCH,
|
||||
null,
|
||||
(Class<String>) null,
|
||||
new HashMap<String, String>()
|
||||
);
|
||||
|
||||
assertThat("Make use of an appropriate HTTP status code", response.getStatusCode(), allOf(
|
||||
CoreMatchers.is(not(HttpStatus.OK)),
|
||||
CoreMatchers.is(not(HttpStatus.NOT_FOUND)),
|
||||
CoreMatchers.is(not(HttpStatus.INTERNAL_SERVER_ERROR))
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteTrip_withKnownTrip_shouldReturnSuccessful() {
|
||||
Long trip = testData.trip6Id;
|
||||
String url = url("/trips/" + trip);
|
||||
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.DELETE,
|
||||
null,
|
||||
(Class<String>) null,
|
||||
new HashMap<String, String>()
|
||||
);
|
||||
|
||||
assertThat(exchange.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteTrip_withUnknownTrip_shouldReturnNotFoundError() {
|
||||
Long trip = 1245L;
|
||||
String url = url("/trips/" + trip);
|
||||
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.DELETE,
|
||||
null,
|
||||
(Class<String>) null,
|
||||
new HashMap<String, String>()
|
||||
);
|
||||
|
||||
assertThat(exchange.getStatusCode(), is(HttpStatus.NOT_FOUND));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cancelTrip_withKnownKey_shouldReturnSuccessful() {
|
||||
Long trip = testData.trip6Id;
|
||||
String url = url("/trips/" + trip + "/cancel");
|
||||
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.PATCH,
|
||||
null,
|
||||
(Class<String>) null,
|
||||
new HashMap<String, String>()
|
||||
);
|
||||
|
||||
assertThat(exchange.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cancelTrip_withUnknownKey_shouldReturnNotFoundError() {
|
||||
Long trip = 12545L;
|
||||
String url = url("/trips/" + trip + "/cancel");
|
||||
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.PATCH,
|
||||
null,
|
||||
(Class<String>) null,
|
||||
new HashMap<String, String>()
|
||||
);
|
||||
|
||||
assertThat(exchange.getStatusCode(), is(HttpStatus.NOT_FOUND));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completeTrip_withKnownTrip_shouldReturnSuccessful() {
|
||||
Long trip = testData.trip9Id;
|
||||
TripInfoDTO tripInfoDTO = new TripInfoDTO();
|
||||
tripInfoDTO.setCompleted(new Date());
|
||||
tripInfoDTO.setDistance(100.0);
|
||||
tripInfoDTO.setFare(getTen());
|
||||
String url = url("/trips/" + trip + "/complete");
|
||||
|
||||
ResponseEntity<String> exchange = restTemplate.postForEntity(
|
||||
url,
|
||||
tripInfoDTO,
|
||||
String.class
|
||||
);
|
||||
|
||||
assertThat(exchange.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completeTrip_withUnknownTrip_shouldReturnNotFoundError() {
|
||||
Long trip = 12545L;
|
||||
TripInfoDTO tripInfoDTO = new TripInfoDTO();
|
||||
tripInfoDTO.setCompleted(new Date());
|
||||
tripInfoDTO.setDistance(100.0);
|
||||
tripInfoDTO.setFare(getTen());
|
||||
String url = url("/trips/" + trip + "/complete");
|
||||
|
||||
ResponseEntity<String> exchange = restTemplate.postForEntity(
|
||||
url,
|
||||
tripInfoDTO,
|
||||
String.class
|
||||
);
|
||||
|
||||
assertThat(exchange.getStatusCode(), is(HttpStatus.NOT_FOUND));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchTrip_withKnownTrip_shouldReturnSuccessful() {
|
||||
Long trip = testData.trip10Id;
|
||||
Long driver = testData.driver4Id + 1L;
|
||||
Long vehicle = testData.vehicle1Id;
|
||||
String url = url("/trips/" + trip + "/match");
|
||||
|
||||
MatchDTO matchDTO = new MatchDTO();
|
||||
matchDTO.setVehicleId(vehicle);
|
||||
matchDTO.setDriverId(driver);
|
||||
matchDTO.setFare(getTen());
|
||||
|
||||
ResponseEntity<String> exchange = restTemplate.postForEntity(
|
||||
url,
|
||||
matchDTO,
|
||||
String.class
|
||||
);
|
||||
|
||||
assertThat(exchange.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchTrip_withUnavailableDriver_shouldReturnAppropriateError() {
|
||||
Long trip = testData.trip10Id;
|
||||
Long driver = testData.driver4Id;
|
||||
Long vehicle = testData.vehicle1Id;
|
||||
String url = url("/trips/" + trip + "/match");
|
||||
|
||||
MatchDTO matchDTO = new MatchDTO();
|
||||
matchDTO.setVehicleId(vehicle);
|
||||
matchDTO.setDriverId(driver);
|
||||
matchDTO.setFare(getTen());
|
||||
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(
|
||||
url,
|
||||
matchDTO,
|
||||
String.class
|
||||
);
|
||||
|
||||
assertThat("Make use of an appropriate HTTP status code", response.getStatusCode(), allOf(
|
||||
CoreMatchers.is(not(HttpStatus.OK)),
|
||||
CoreMatchers.is(not(HttpStatus.NOT_FOUND)),
|
||||
CoreMatchers.is(not(HttpStatus.INTERNAL_SERVER_ERROR))
|
||||
));
|
||||
}
|
||||
|
||||
private MultiValueMap<String, String> getCreateMap(Long riderId, Long pickupId, Long destinationId) {
|
||||
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
|
||||
map.add("riderId", riderId + "");
|
||||
map.add("pickupId", pickupId + "");
|
||||
map.add("destinationId", destinationId + "");
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
private MoneyDTO getTen() {
|
||||
MoneyDTO moneyDTO = new MoneyDTO();
|
||||
moneyDTO.setCurrency("EUR");
|
||||
moneyDTO.setValue(BigDecimal.TEN);
|
||||
return moneyDTO;
|
||||
}
|
||||
|
||||
private MultiValueMap<String, String> body(String key, Object value) {
|
||||
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
|
||||
map.add(key, String.valueOf(value));
|
||||
return map;
|
||||
}
|
||||
|
||||
private ResponseEntity<TripDTO> getTrip(Long id) {
|
||||
String url = url("/trips/" + id);
|
||||
return restTemplate.getForEntity(url, TripDTO.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
package dst.ass2.service.trip.tests;
|
||||
|
||||
import dst.ass1.jpa.dao.IDAOFactory;
|
||||
import dst.ass1.jpa.dao.ITripDAO;
|
||||
import dst.ass1.jpa.model.*;
|
||||
import dst.ass1.jpa.tests.TestData;
|
||||
import dst.ass2.service.api.match.IMatchingService;
|
||||
import dst.ass2.service.api.trip.*;
|
||||
import dst.ass2.service.trip.TripApplication;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = TripApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("testdata")
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
public class TripServiceTest implements ApplicationContextAware {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TripServiceTest.class);
|
||||
|
||||
private ApplicationContext ctx;
|
||||
|
||||
@MockBean
|
||||
private IMatchingService matchingService;
|
||||
|
||||
private ITripService tripService;
|
||||
private IDAOFactory daoFactory;
|
||||
private TestData testData;
|
||||
private ITripDAO tripDAO;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
LOG.info("Test resolving beans from application context");
|
||||
daoFactory = ctx.getBean(IDAOFactory.class);
|
||||
tripService = ctx.getBean(ITripService.class);
|
||||
testData = ctx.getBean(TestData.class);
|
||||
tripDAO = daoFactory.createTripDAO();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateWithValidArguments_persistsTrip_and_returnsTripDTO() throws Exception {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getOne());
|
||||
|
||||
TripDTO tripDTO = tripService.create(testData.rider1Id, testData.location1Id, testData.location2Id);
|
||||
|
||||
verify(matchingService, times(1)).calculateFare(any());
|
||||
|
||||
assertNotNull(tripDTO);
|
||||
assertNotNull(tripDTO.getId());
|
||||
assertNotNull(tripDTO.getFare());
|
||||
|
||||
assertEquals(testData.rider1Id, tripDTO.getRiderId());
|
||||
assertEquals(testData.location1Id, tripDTO.getPickupId());
|
||||
assertEquals(testData.location2Id, tripDTO.getDestinationId());
|
||||
assertEquals(getOne(), tripDTO.getFare());
|
||||
|
||||
ITrip trip = tripDAO.findById(tripDTO.getId());
|
||||
assertNotNull(trip);
|
||||
|
||||
assertEquals(testData.location1Id, trip.getPickup().getId());
|
||||
assertEquals(testData.location2Id, trip.getDestination().getId());
|
||||
assertEquals(testData.rider1Id, trip.getRider().getId());
|
||||
assertEquals(TripState.CREATED, trip.getState());
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = EntityNotFoundException.class)
|
||||
public void testCreateWithInvalidRider_throwsException() throws Exception {
|
||||
tripService.create(1337L, testData.location1Id, testData.location2Id);
|
||||
}
|
||||
|
||||
@Test(expected = EntityNotFoundException.class)
|
||||
public void testCreateWithInvalidPickup_throwsException() throws Exception {
|
||||
tripService.create(testData.rider1Id, 1444L, testData.location2Id);
|
||||
}
|
||||
|
||||
@Test(expected = EntityNotFoundException.class)
|
||||
public void testCreateWithInvalidDestination_throwsException() throws Exception {
|
||||
tripService.create(testData.rider1Id, testData.location1Id, 1337L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateWithInvalidTrip_setsFareToNull() throws Exception {
|
||||
when(matchingService.calculateFare(any())).thenThrow(new InvalidTripException());
|
||||
|
||||
TripDTO tripDTO = tripService.create(testData.rider1Id, testData.location1Id, testData.location2Id);
|
||||
assertNotNull(tripDTO);
|
||||
assertNotNull(tripDTO.getId());
|
||||
|
||||
ITrip trip = tripDAO.findById(tripDTO.getId());
|
||||
assertNotNull(trip);
|
||||
assertNull(tripDTO.getFare());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfirmWithValidTrip_isQueued() throws Exception {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
|
||||
tripService.confirm(testData.trip6Id);
|
||||
ITrip confirmed = tripDAO.findById(testData.trip6Id);
|
||||
assertEquals(TripState.QUEUED, confirmed.getState());
|
||||
verify(matchingService, times(1)).queueTripForMatching(any());
|
||||
}
|
||||
|
||||
@Test(expected = EntityNotFoundException.class)
|
||||
public void testConfirmWithUnknownTrip_throwsException() throws Exception {
|
||||
tripService.confirm(1337L);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testConfirmQueuedTrip_throwsException() throws Exception {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getOne());
|
||||
tripService.confirm(testData.trip10Id);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidTripException.class)
|
||||
public void testConfirmWithInvalidTrip_throwsException() throws Exception {
|
||||
when(matchingService.calculateFare(any())).thenThrow(new InvalidTripException());
|
||||
tripService.confirm(testData.trip6Id);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatch_matchCreated_stateMatched() throws Exception {
|
||||
Long tripId = testData.trip10Id;
|
||||
|
||||
MatchDTO matchDTO = new MatchDTO();
|
||||
matchDTO.setFare(getOne());
|
||||
matchDTO.setDriverId(testData.driver4Id + 1);
|
||||
matchDTO.setVehicleId(testData.vehicle1Id);
|
||||
|
||||
tripService.match(tripId, matchDTO);
|
||||
|
||||
ITrip updated = tripDAO.findById(tripId);
|
||||
assertNotNull(updated);
|
||||
assertEquals(TripState.MATCHED, updated.getState());
|
||||
|
||||
IMatch match = updated.getMatch();
|
||||
assertNotNull(match);
|
||||
assertEquals(matchDTO.getDriverId(), match.getDriver().getId());
|
||||
assertEquals(matchDTO.getVehicleId(), match.getVehicle().getId());
|
||||
|
||||
IMoney fare = match.getFare();
|
||||
assertEquals(getOne().getCurrency(), fare.getCurrency());
|
||||
assertEquals(getOne().getValue().compareTo(fare.getValue()), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchWithUnknownTrip_throwsException_and_requeueTrip() throws Exception {
|
||||
long tripId = 1337L;
|
||||
|
||||
MatchDTO matchDTO = new MatchDTO();
|
||||
matchDTO.setFare(getOne());
|
||||
matchDTO.setDriverId(testData.driver4Id + 1);
|
||||
matchDTO.setVehicleId(testData.vehicle1Id);
|
||||
|
||||
try {
|
||||
tripService.match(tripId, matchDTO);
|
||||
} catch (EntityNotFoundException ex) {
|
||||
verify(matchingService, times(1)).queueTripForMatching(any());
|
||||
return;
|
||||
}
|
||||
|
||||
fail();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatch_driverAlreadyAssigned_throwsException_and_requeueTrip() throws Exception {
|
||||
Long tripId = testData.trip10Id;
|
||||
|
||||
MatchDTO matchDTO = new MatchDTO();
|
||||
matchDTO.setFare(getTen());
|
||||
matchDTO.setDriverId(testData.driver4Id);
|
||||
matchDTO.setVehicleId(testData.vehicle1Id);
|
||||
|
||||
try {
|
||||
tripService.match(tripId, matchDTO);
|
||||
} catch (DriverNotAvailableException ex) {
|
||||
verify(matchingService, times(1)).queueTripForMatching(any());
|
||||
return;
|
||||
}
|
||||
|
||||
fail();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompleteWithValidTripInfo_shouldPersistTripInfo_and_setTripCompleted() throws Exception {
|
||||
Long tripId = testData.trip9Id;
|
||||
TripInfoDTO tripInfoDTO = new TripInfoDTO();
|
||||
tripInfoDTO.setCompleted(new Date());
|
||||
tripInfoDTO.setFare(getTen());
|
||||
tripInfoDTO.setDistance(2.0);
|
||||
|
||||
tripService.complete(tripId, tripInfoDTO);
|
||||
|
||||
ITrip updated = tripDAO.findById(tripId);
|
||||
assertNotNull(updated);
|
||||
assertEquals(TripState.COMPLETED, updated.getState());
|
||||
|
||||
ITripInfo tripInfo = updated.getTripInfo();
|
||||
assertNotNull(tripInfo);
|
||||
|
||||
assertEquals(tripInfoDTO.getCompleted(), tripInfo.getCompleted());
|
||||
assertEquals(2.0, tripInfo.getDistance(), 0);
|
||||
assertEquals(getTen().getCurrency(), tripInfo.getTotal().getCurrency());
|
||||
assertEquals(0, getTen().getValue().compareTo(tripInfo.getTotal().getValue()));
|
||||
}
|
||||
|
||||
@Test(expected = EntityNotFoundException.class)
|
||||
public void testCompleteWithUnknownTrip_throwsException() throws Exception {
|
||||
long tripId = 1337L;
|
||||
TripInfoDTO tripInfoDTO = new TripInfoDTO();
|
||||
tripInfoDTO.setCompleted(new Date());
|
||||
tripInfoDTO.setFare(getTen());
|
||||
tripInfoDTO.setDistance(2.0);
|
||||
|
||||
tripService.complete(tripId, tripInfoDTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelWithValidTrip_shouldCancelTrip() throws Exception {
|
||||
tripService.cancel(testData.trip6Id);
|
||||
|
||||
ITrip updated = tripDAO.findById(testData.trip6Id);
|
||||
assertNotNull(updated);
|
||||
assertEquals(TripState.CANCELLED, updated.getState());
|
||||
}
|
||||
|
||||
@Test(expected = EntityNotFoundException.class)
|
||||
public void testCancelWithUnkownTrip_throwsException() throws Exception {
|
||||
tripService.cancel(1337L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddStop_shouldReturnTrue() throws Exception {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
|
||||
TripDTO tripDTO = getTrip6DTO();
|
||||
tripDTO.setFare(getTen());
|
||||
|
||||
boolean added = tripService.addStop(tripDTO, testData.location1Id);
|
||||
assertTrue(added);
|
||||
assertEquals(getTen(), tripDTO.getFare());
|
||||
verify(matchingService, times(1)).calculateFare(any());
|
||||
}
|
||||
|
||||
@Test(expected = EntityNotFoundException.class)
|
||||
public void testAddStopUnknownLocation_throwsException() throws Exception {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
|
||||
TripDTO tripDTO = getTrip6DTO();
|
||||
tripDTO.setFare(getTen());
|
||||
|
||||
tripService.addStop(tripDTO, 1344L);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testAddStopInvalidTripState_throwsException() throws Exception {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
|
||||
ITrip trip = tripDAO.findById(testData.trip1Id);
|
||||
TripDTO tripDTO = new TripDTO();
|
||||
tripDTO.setId(trip.getId());
|
||||
tripDTO.setPickupId(trip.getPickup().getId());
|
||||
tripDTO.setDestinationId(trip.getDestination().getId());
|
||||
tripDTO.setRiderId(trip.getRider().getId());
|
||||
tripDTO.setStops(trip.getStops().stream().map(ILocation::getId).collect(Collectors.toList()));
|
||||
tripDTO.setFare(getTen());
|
||||
|
||||
tripService.addStop(tripDTO, testData.location2Id);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddStopInvalidTrip_setsFareToNull() throws Exception {
|
||||
when(matchingService.calculateFare(any())).thenThrow(new InvalidTripException());
|
||||
|
||||
TripDTO tripDTO = getTrip6DTO();
|
||||
tripDTO.setFare(getOne());
|
||||
|
||||
boolean added = tripService.addStop(tripDTO, testData.location1Id);
|
||||
assertTrue(added);
|
||||
assertNull(tripDTO.getFare());
|
||||
assertEquals(4, tripDTO.getStops().size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAddStop_shouldReturnFalse() throws Exception {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
|
||||
TripDTO tripDTO = getTrip6DTO();
|
||||
tripDTO.setFare(getOne());
|
||||
|
||||
boolean added = tripService.addStop(tripDTO, testData.location2Id);
|
||||
assertFalse(added);
|
||||
|
||||
//fare should not be updated if the location hasn't been added
|
||||
assertEquals(getOne(), tripDTO.getFare());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveStop_shouldReturnTrue() throws Exception {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getOne());
|
||||
|
||||
TripDTO tripDTO = getTrip6DTO();
|
||||
tripDTO.setFare(getTen());
|
||||
|
||||
boolean removed = tripService.removeStop(tripDTO, testData.location2Id);
|
||||
assertTrue(removed);
|
||||
assertEquals(getOne(), tripDTO.getFare());
|
||||
assertEquals(2, tripDTO.getStops().size());
|
||||
ITrip updated = tripDAO.findById(testData.trip6Id);
|
||||
assertEquals(2, updated.getStops().size());
|
||||
verify(matchingService, times(1)).calculateFare(any());
|
||||
}
|
||||
|
||||
private MoneyDTO getZero() {
|
||||
MoneyDTO moneyDTO = new MoneyDTO();
|
||||
moneyDTO.setCurrency("EUR");
|
||||
moneyDTO.setValue(BigDecimal.ZERO);
|
||||
return moneyDTO;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveStop_shouldReturnFalse() throws Exception {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getZero());
|
||||
TripDTO tripDTO = getTrip6DTO();
|
||||
tripDTO.setFare(getOne());
|
||||
|
||||
boolean removed = tripService.removeStop(tripDTO, testData.location1Id);
|
||||
assertFalse(removed);
|
||||
|
||||
//fare should not be updated if the location hasn't been removed
|
||||
assertEquals(getOne(), tripDTO.getFare());
|
||||
|
||||
assertEquals(3, tripDTO.getStops().size());
|
||||
ITrip updated = tripDAO.findById(testData.trip6Id);
|
||||
assertEquals(3, updated.getStops().size());
|
||||
}
|
||||
|
||||
@Test(expected = EntityNotFoundException.class)
|
||||
public void testRemoveStopUnknownLocation_throwsException() throws Exception {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getOne());
|
||||
|
||||
TripDTO tripDTO = getTrip6DTO();
|
||||
tripDTO.setFare(getTen());
|
||||
|
||||
tripService.removeStop(tripDTO, 1344L);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testRemoveStopInvalidTripState_throwsException() throws Exception {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getOne());
|
||||
|
||||
ITrip trip = tripDAO.findById(testData.trip10Id);
|
||||
TripDTO tripDTO = new TripDTO();
|
||||
tripDTO.setId(trip.getId());
|
||||
tripDTO.setPickupId(trip.getPickup().getId());
|
||||
tripDTO.setDestinationId(trip.getDestination().getId());
|
||||
tripDTO.setRiderId(trip.getRider().getId());
|
||||
List<Long> stopIds = trip.getStops().stream().map(ILocation::getId).collect(Collectors.toList());
|
||||
tripDTO.setStops(stopIds);
|
||||
tripDTO.setFare(getTen());
|
||||
|
||||
tripService.removeStop(tripDTO, testData.location4Id);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveStopInvalidTrip_setsFareToNull() throws Exception {
|
||||
when(matchingService.calculateFare(any())).thenThrow(new InvalidTripException());
|
||||
|
||||
TripDTO tripDTO = getTrip6DTO();
|
||||
tripDTO.setFare(getOne());
|
||||
|
||||
boolean removed = tripService.removeStop(tripDTO, testData.location4Id);
|
||||
assertTrue(removed);
|
||||
assertNull(tripDTO.getFare());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteValidTrip_shouldSucceed() throws Exception {
|
||||
tripService.delete(testData.trip6Id);
|
||||
ITrip deleted = tripDAO.findById(testData.trip6Id);
|
||||
assertNull(deleted);
|
||||
}
|
||||
|
||||
@Test(expected = EntityNotFoundException.class)
|
||||
public void testDeleteUnknownTrip_throwsException() throws Exception {
|
||||
tripService.delete(1111L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindTrip_shouldSucceed() throws Exception {
|
||||
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||
|
||||
TripDTO tripDTO = tripService.find(testData.trip9Id);
|
||||
assertNotNull(tripDTO);
|
||||
assertEquals(testData.trip9Id, tripDTO.getId());
|
||||
assertEquals(testData.rider1Id, tripDTO.getRiderId());
|
||||
assertEquals(testData.location2Id, tripDTO.getPickupId());
|
||||
assertEquals(testData.location5Id, tripDTO.getDestinationId());
|
||||
assertEquals(getTen(), tripDTO.getFare());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindTrip_shouldReturnNull() {
|
||||
TripDTO tripDTO = tripService.find(1337L);
|
||||
assertNull(tripDTO);
|
||||
}
|
||||
|
||||
private MoneyDTO getOne() {
|
||||
MoneyDTO moneyDTO = new MoneyDTO();
|
||||
moneyDTO.setCurrency("EUR");
|
||||
moneyDTO.setValue(BigDecimal.ONE);
|
||||
return moneyDTO;
|
||||
}
|
||||
|
||||
private MoneyDTO getTen() {
|
||||
MoneyDTO moneyDTO = new MoneyDTO();
|
||||
moneyDTO.setValue(BigDecimal.TEN);
|
||||
moneyDTO.setCurrency("EUR");
|
||||
return moneyDTO;
|
||||
}
|
||||
|
||||
|
||||
private TripDTO getTrip6DTO() {
|
||||
TripDTO tripDTO = new TripDTO();
|
||||
tripDTO.setId(testData.trip6Id);
|
||||
tripDTO.setPickupId(testData.location5Id);
|
||||
tripDTO.setDestinationId(testData.location1Id);
|
||||
tripDTO.setRiderId(testData.rider4Id);
|
||||
LinkedList<Long> longs = new LinkedList<>();
|
||||
longs.add(testData.location2Id);
|
||||
longs.add(testData.location3Id);
|
||||
longs.add(testData.location4Id);
|
||||
tripDTO.setStops(longs);
|
||||
return tripDTO;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
server.port=8091
|
||||
Reference in New Issue
Block a user