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);
+ }
+ }
+}
diff --git a/ass2-service/auth-client/pom.xml b/ass2-service/auth-client/pom.xml
new file mode 100644
index 0000000..6b26ecc
--- /dev/null
+++ b/ass2-service/auth-client/pom.xml
@@ -0,0 +1,72 @@
+
+
+
+ 4.0.0
+
+
+ at.ac.tuwien.infosys.dst
+ dst
+ 2021.1
+ ../..
+
+
+ ass2-service-auth-client
+
+ DST :: Assignment 2 :: Service :: Auth Client
+
+ jar
+
+
+
+ at.ac.tuwien.infosys.dst
+ ass2-service-api
+ ${project.version}
+
+
+
+ io.grpc
+ grpc-protobuf
+
+
+ io.grpc
+ grpc-stub
+
+
+ io.grpc
+ grpc-netty
+
+
+
+ at.ac.tuwien.infosys.dst
+ ass2-service-auth
+ ${project.version}
+ test
+
+
+ at.ac.tuwien.infosys.dst
+ ass1-jpa
+ ${project.version}
+ test-jar
+ test
+
+
+ at.ac.tuwien.infosys.dst
+ ass2-service-auth
+ ${project.version}
+ test-jar
+ test
+
+
+ org.springframework
+ spring-orm
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
diff --git a/ass2-service/auth-client/src/main/java/dst/ass2/service/auth/client/AuthenticationClientProperties.java b/ass2-service/auth-client/src/main/java/dst/ass2/service/auth/client/AuthenticationClientProperties.java
new file mode 100644
index 0000000..ad91c83
--- /dev/null
+++ b/ass2-service/auth-client/src/main/java/dst/ass2/service/auth/client/AuthenticationClientProperties.java
@@ -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;
+ }
+}
diff --git a/ass2-service/auth-client/src/main/java/dst/ass2/service/auth/client/IAuthenticationClient.java b/ass2-service/auth-client/src/main/java/dst/ass2/service/auth/client/IAuthenticationClient.java
new file mode 100644
index 0000000..dc38f0a
--- /dev/null
+++ b/ass2-service/auth-client/src/main/java/dst/ass2/service/auth/client/IAuthenticationClient.java
@@ -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();
+}
diff --git a/ass2-service/auth-client/src/main/java/dst/ass2/service/auth/client/impl/GrpcAuthenticationClient.java b/ass2-service/auth-client/src/main/java/dst/ass2/service/auth/client/impl/GrpcAuthenticationClient.java
new file mode 100644
index 0000000..3ae5082
--- /dev/null
+++ b/ass2-service/auth-client/src/main/java/dst/ass2/service/auth/client/impl/GrpcAuthenticationClient.java
@@ -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
+ }
+}
diff --git a/ass2-service/auth-client/src/test/java/dst/ass2/service/auth/client/AuthenticationClientTest.java b/ass2-service/auth-client/src/test/java/dst/ass2/service/auth/client/AuthenticationClientTest.java
new file mode 100644
index 0000000..1f618f2
--- /dev/null
+++ b/ass2-service/auth-client/src/test/java/dst/ass2/service/auth/client/AuthenticationClientTest.java
@@ -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);
+ }
+
+}
diff --git a/ass2-service/auth/pom.xml b/ass2-service/auth/pom.xml
new file mode 100644
index 0000000..9e36d64
--- /dev/null
+++ b/ass2-service/auth/pom.xml
@@ -0,0 +1,80 @@
+
+
+
+ 4.0.0
+
+
+ at.ac.tuwien.infosys.dst
+ dst
+ 2021.1
+ ../..
+
+
+ ass2-service-auth
+
+ DST :: Assignment 2 :: Service :: Auth Server
+
+ jar
+
+
+
+ at.ac.tuwien.infosys.dst
+ ass1-jpa
+ ${project.version}
+
+
+ at.ac.tuwien.infosys.dst
+ ass2-service-api
+ ${project.version}
+
+
+
+ io.grpc
+ grpc-protobuf
+
+
+ io.grpc
+ grpc-stub
+
+
+ io.grpc
+ grpc-netty
+
+
+
+ at.ac.tuwien.infosys.dst
+ ass1-jpa
+ ${project.version}
+ test-jar
+ test
+
+
+ org.springframework
+ spring-orm
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ kr.motd.maven
+ os-maven-plugin
+
+
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+
+
+
+
+
+
diff --git a/ass2-service/auth/src/main/java/dst/ass2/service/auth/ICachingAuthenticationService.java b/ass2-service/auth/src/main/java/dst/ass2/service/auth/ICachingAuthenticationService.java
new file mode 100644
index 0000000..c2c4362
--- /dev/null
+++ b/ass2-service/auth/src/main/java/dst/ass2/service/auth/ICachingAuthenticationService.java
@@ -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}
+ *
+ *
+ * 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.
+ *
+ */
+ @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();
+}
diff --git a/ass2-service/auth/src/main/java/dst/ass2/service/auth/grpc/GrpcServerProperties.java b/ass2-service/auth/src/main/java/dst/ass2/service/auth/grpc/GrpcServerProperties.java
new file mode 100644
index 0000000..badbfa2
--- /dev/null
+++ b/ass2-service/auth/src/main/java/dst/ass2/service/auth/grpc/GrpcServerProperties.java
@@ -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;
+ }
+}
diff --git a/ass2-service/auth/src/main/java/dst/ass2/service/auth/grpc/IGrpcServerRunner.java b/ass2-service/auth/src/main/java/dst/ass2/service/auth/grpc/IGrpcServerRunner.java
new file mode 100644
index 0000000..c847b9b
--- /dev/null
+++ b/ass2-service/auth/src/main/java/dst/ass2/service/auth/grpc/IGrpcServerRunner.java
@@ -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;
+}
diff --git a/ass2-service/auth/src/main/resources/logback.xml b/ass2-service/auth/src/main/resources/logback.xml
new file mode 100644
index 0000000..e16fad1
--- /dev/null
+++ b/ass2-service/auth/src/main/resources/logback.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} - %highlight(%5p) [%12.12thread] %cyan(%-40.40logger{39}): %m%n
+
+
+
+
+
+
+
+
diff --git a/ass2-service/auth/src/test/java/dst/ass2/service/auth/AuthenticationServiceApplication.java b/ass2-service/auth/src/test/java/dst/ass2/service/auth/AuthenticationServiceApplication.java
new file mode 100644
index 0000000..6c9772b
--- /dev/null
+++ b/ass2-service/auth/src/test/java/dst/ass2/service/auth/AuthenticationServiceApplication.java
@@ -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);
+ }
+
+}
diff --git a/ass2-service/auth/src/test/java/dst/ass2/service/auth/AuthenticationServiceApplicationConfig.java b/ass2-service/auth/src/test/java/dst/ass2/service/auth/AuthenticationServiceApplicationConfig.java
new file mode 100644
index 0000000..e39264a
--- /dev/null
+++ b/ass2-service/auth/src/test/java/dst/ass2/service/auth/AuthenticationServiceApplicationConfig.java
@@ -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;
+ }
+ }
+
+}
diff --git a/ass2-service/auth/src/test/java/dst/ass2/service/auth/SpringGrpcServerRunner.java b/ass2-service/auth/src/test/java/dst/ass2/service/auth/SpringGrpcServerRunner.java
new file mode 100644
index 0000000..2b77fe0
--- /dev/null
+++ b/ass2-service/auth/src/test/java/dst/ass2/service/auth/SpringGrpcServerRunner.java
@@ -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();
+ }
+}
diff --git a/ass2-service/auth/src/test/java/dst/ass2/service/auth/TestDataInserter.java b/ass2-service/auth/src/test/java/dst/ass2/service/auth/TestDataInserter.java
new file mode 100644
index 0000000..a602f49
--- /dev/null
+++ b/ass2-service/auth/src/test/java/dst/ass2/service/auth/TestDataInserter.java
@@ -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;
+ });
+ }
+
+}
diff --git a/ass2-service/auth/src/test/java/dst/ass2/service/auth/tests/AuthenticationServiceTest.java b/ass2-service/auth/src/test/java/dst/ass2/service/auth/tests/AuthenticationServiceTest.java
new file mode 100644
index 0000000..0e6905d
--- /dev/null
+++ b/ass2-service/auth/src/test/java/dst/ass2/service/auth/tests/AuthenticationServiceTest.java
@@ -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"));
+ }
+
+}
diff --git a/ass2-service/auth/src/test/java/dst/ass2/service/auth/tests/GrpcServerRunnerTest.java b/ass2-service/auth/src/test/java/dst/ass2/service/auth/tests/GrpcServerRunnerTest.java
new file mode 100644
index 0000000..9ec8298
--- /dev/null
+++ b/ass2-service/auth/src/test/java/dst/ass2/service/auth/tests/GrpcServerRunnerTest.java
@@ -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--;
+ }
+
+ }
+}
diff --git a/ass2-service/auth/src/test/resources/dst/ass2/service/auth/grpc.properties b/ass2-service/auth/src/test/resources/dst/ass2/service/auth/grpc.properties
new file mode 100644
index 0000000..9d203f4
--- /dev/null
+++ b/ass2-service/auth/src/test/resources/dst/ass2/service/auth/grpc.properties
@@ -0,0 +1 @@
+grpc.port=50051
\ No newline at end of file
diff --git a/ass2-service/facade/pom.xml b/ass2-service/facade/pom.xml
new file mode 100644
index 0000000..a5c8eb9
--- /dev/null
+++ b/ass2-service/facade/pom.xml
@@ -0,0 +1,53 @@
+
+
+
+ 4.0.0
+
+
+ at.ac.tuwien.infosys.dst
+ dst
+ 2021.1
+ ../..
+
+
+ ass2-service-facade
+
+ DST :: Assignment 2 :: Service :: Facade
+
+ jar
+
+
+
+ at.ac.tuwien.infosys.dst
+ ass2-service-api
+ ${project.version}
+
+
+ at.ac.tuwien.infosys.dst
+ ass2-service-auth-client
+ ${project.version}
+
+
+
+ org.glassfish.jersey.core
+ jersey-client
+
+
+ org.glassfish.jersey.ext
+ jersey-proxy-client
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jersey
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
\ No newline at end of file
diff --git a/ass2-service/facade/src/main/java/dst/ass2/service/facade/impl/.gitkeep b/ass2-service/facade/src/main/java/dst/ass2/service/facade/impl/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/ass2-service/facade/src/test/java/dst/ass2/service/facade/ServiceFacadeApplication.java b/ass2-service/facade/src/test/java/dst/ass2/service/facade/ServiceFacadeApplication.java
new file mode 100644
index 0000000..998d5f2
--- /dev/null
+++ b/ass2-service/facade/src/test/java/dst/ass2/service/facade/ServiceFacadeApplication.java
@@ -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);
+ }
+
+}
diff --git a/ass2-service/facade/src/test/java/dst/ass2/service/facade/ServiceFacadeApplicationConfig.java b/ass2-service/facade/src/test/java/dst/ass2/service/facade/ServiceFacadeApplicationConfig.java
new file mode 100644
index 0000000..7e44d87
--- /dev/null
+++ b/ass2-service/facade/src/test/java/dst/ass2/service/facade/ServiceFacadeApplicationConfig.java
@@ -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
+ }
+ }
+}
diff --git a/ass2-service/facade/src/test/java/dst/ass2/service/facade/test/AuthenticationResourceTest.java b/ass2-service/facade/src/test/java/dst/ass2/service/facade/test/AuthenticationResourceTest.java
new file mode 100644
index 0000000..5b41d6a
--- /dev/null
+++ b/ass2-service/facade/src/test/java/dst/ass2/service/facade/test/AuthenticationResourceTest.java
@@ -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 body = new LinkedMultiValueMap<>();
+ body.add("email", "junit@example.com");
+ body.add("password", "junit");
+
+ HttpEntity> request = new HttpEntity<>(body, headers);
+ ResponseEntity 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 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;
+ }
+
+}
diff --git a/ass2-service/facade/src/test/resources/application.properties b/ass2-service/facade/src/test/resources/application.properties
new file mode 100644
index 0000000..e4ba079
--- /dev/null
+++ b/ass2-service/facade/src/test/resources/application.properties
@@ -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/
diff --git a/ass2-service/facade/src/test/resources/logback.xml b/ass2-service/facade/src/test/resources/logback.xml
new file mode 100644
index 0000000..e16fad1
--- /dev/null
+++ b/ass2-service/facade/src/test/resources/logback.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} - %highlight(%5p) [%12.12thread] %cyan(%-40.40logger{39}): %m%n
+
+
+
+
+
+
+
+
diff --git a/ass2-service/trip/pom.xml b/ass2-service/trip/pom.xml
new file mode 100644
index 0000000..c7821f9
--- /dev/null
+++ b/ass2-service/trip/pom.xml
@@ -0,0 +1,62 @@
+
+
+
+ 4.0.0
+
+
+ at.ac.tuwien.infosys.dst
+ dst
+ 2021.1
+ ../..
+
+
+ ass2-service-trip
+
+ DST :: Assignment 2 :: Service :: Trip
+
+ jar
+
+
+
+ at.ac.tuwien.infosys.dst
+ ass1-jpa
+ ${project.version}
+
+
+ at.ac.tuwien.infosys.dst
+ ass2-service-api
+ ${project.version}
+
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+
+ at.ac.tuwien.infosys.dst
+ ass1-jpa
+ ${project.version}
+ test-jar
+ test
+
+
+ org.springframework
+ spring-orm
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-jersey
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
diff --git a/ass2-service/trip/src/main/java/dst/ass2/service/trip/impl/.gitkeep b/ass2-service/trip/src/main/java/dst/ass2/service/trip/impl/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/ass2-service/trip/src/test/java/dst/ass2/service/trip/MatchingService.java b/ass2-service/trip/src/test/java/dst/ass2/service/trip/MatchingService.java
new file mode 100644
index 0000000..207323f
--- /dev/null
+++ b/ass2-service/trip/src/test/java/dst/ass2/service/trip/MatchingService.java
@@ -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 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);
+ }
+ }
+}
diff --git a/ass2-service/trip/src/test/java/dst/ass2/service/trip/TestDataConfig.java b/ass2-service/trip/src/test/java/dst/ass2/service/trip/TestDataConfig.java
new file mode 100644
index 0000000..6fe9907
--- /dev/null
+++ b/ass2-service/trip/src/test/java/dst/ass2/service/trip/TestDataConfig.java
@@ -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 {
+
+ @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);
+ }
+}
diff --git a/ass2-service/trip/src/test/java/dst/ass2/service/trip/TestDataInserter.java b/ass2-service/trip/src/test/java/dst/ass2/service/trip/TestDataInserter.java
new file mode 100644
index 0000000..6227d5b
--- /dev/null
+++ b/ass2-service/trip/src/test/java/dst/ass2/service/trip/TestDataInserter.java
@@ -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;
+ });
+ }
+
+}
diff --git a/ass2-service/trip/src/test/java/dst/ass2/service/trip/TripApplication.java b/ass2-service/trip/src/test/java/dst/ass2/service/trip/TripApplication.java
new file mode 100644
index 0000000..21eaac2
--- /dev/null
+++ b/ass2-service/trip/src/test/java/dst/ass2/service/trip/TripApplication.java
@@ -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);
+ }
+}
diff --git a/ass2-service/trip/src/test/java/dst/ass2/service/trip/TripApplicationConfig.java b/ass2-service/trip/src/test/java/dst/ass2/service/trip/TripApplicationConfig.java
new file mode 100644
index 0000000..08bf63d
--- /dev/null
+++ b/ass2-service/trip/src/test/java/dst/ass2/service/trip/TripApplicationConfig.java
@@ -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;
+ }
+
+}
diff --git a/ass2-service/trip/src/test/java/dst/ass2/service/trip/tests/TripServiceResourceTest.java b/ass2-service/trip/src/test/java/dst/ass2/service/trip/tests/TripServiceResourceTest.java
new file mode 100644
index 0000000..25e17dc
--- /dev/null
+++ b/ass2-service/trip/src/test/java/dst/ass2/service/trip/tests/TripServiceResourceTest.java
@@ -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 map = getCreateMap(2134L, 2222L, 33L);
+ ResponseEntity 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 body = getCreateMap(3333L, testData.location1Id, testData.location2Id);
+ HttpEntity> request = new HttpEntity<>(body, headers);
+ ResponseEntity 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 body = getCreateMap(testData.rider1Id, testData.location1Id, testData.location2Id);
+ HttpEntity> request = new HttpEntity<>(body, headers);
+ ResponseEntity 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 body = getCreateMap(testData.rider1Id, testData.location1Id, testData.location2Id);
+ HttpEntity> request = new HttpEntity<>(body, headers);
+ ResponseEntity 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 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 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 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 body = body("locationId", testData.location5Id);
+ HttpEntity> request = new HttpEntity<>(body, headers);
+ ResponseEntity 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 body = body("locationId", testData.location5Id);
+ HttpEntity> request = new HttpEntity<>(body, headers);
+ ResponseEntity response = restTemplate.postForEntity(url, request, MoneyDTO.class);
+ assertThat(response.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
+
+ ResponseEntity 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 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 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 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 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 exchange = restTemplate.exchange(
+ url,
+ HttpMethod.DELETE,
+ null,
+ (Class) null,
+ new HashMap()
+ );
+
+ 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 response = getTrip(trip);
+ assertNotNull(response);
+ List 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 response = restTemplate.exchange(
+ url,
+ HttpMethod.DELETE,
+ null,
+ (Class) null,
+ new HashMap()
+ );
+
+ 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 exchange = restTemplate.exchange(
+ url,
+ HttpMethod.DELETE,
+ null,
+ (Class) null,
+ new HashMap()
+ );
+
+ 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 response = restTemplate.exchange(
+ url,
+ HttpMethod.DELETE,
+ null,
+ (Class) null,
+ new HashMap()
+ );
+
+ 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 exchange = restTemplate.exchange(
+ url,
+ HttpMethod.DELETE,
+ null,
+ (Class) null,
+ new HashMap()
+ );
+
+ 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 exchange = restTemplate.exchange(
+ url,
+ HttpMethod.PATCH,
+ null,
+ (Class) null,
+ new HashMap()
+ );
+
+ 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 exchange = restTemplate.exchange(
+ url,
+ HttpMethod.PATCH,
+ null,
+ (Class) null,
+ new HashMap()
+ );
+
+ 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 response = restTemplate.exchange(
+ url,
+ HttpMethod.PATCH,
+ null,
+ (Class) null,
+ new HashMap()
+ );
+
+ 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 exchange = restTemplate.exchange(
+ url,
+ HttpMethod.DELETE,
+ null,
+ (Class) null,
+ new HashMap()
+ );
+
+ assertThat(exchange.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
+ }
+
+ @Test
+ public void deleteTrip_withUnknownTrip_shouldReturnNotFoundError() {
+ Long trip = 1245L;
+ String url = url("/trips/" + trip);
+
+ ResponseEntity exchange = restTemplate.exchange(
+ url,
+ HttpMethod.DELETE,
+ null,
+ (Class) null,
+ new HashMap()
+ );
+
+ assertThat(exchange.getStatusCode(), is(HttpStatus.NOT_FOUND));
+ }
+
+ @Test
+ public void cancelTrip_withKnownKey_shouldReturnSuccessful() {
+ Long trip = testData.trip6Id;
+ String url = url("/trips/" + trip + "/cancel");
+
+ ResponseEntity exchange = restTemplate.exchange(
+ url,
+ HttpMethod.PATCH,
+ null,
+ (Class) null,
+ new HashMap()
+ );
+
+ assertThat(exchange.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
+ }
+
+ @Test
+ public void cancelTrip_withUnknownKey_shouldReturnNotFoundError() {
+ Long trip = 12545L;
+ String url = url("/trips/" + trip + "/cancel");
+
+ ResponseEntity exchange = restTemplate.exchange(
+ url,
+ HttpMethod.PATCH,
+ null,
+ (Class) null,
+ new HashMap()
+ );
+
+ 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 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 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 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 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 getCreateMap(Long riderId, Long pickupId, Long destinationId) {
+ MultiValueMap 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 body(String key, Object value) {
+ MultiValueMap map = new LinkedMultiValueMap<>();
+ map.add(key, String.valueOf(value));
+ return map;
+ }
+
+ private ResponseEntity getTrip(Long id) {
+ String url = url("/trips/" + id);
+ return restTemplate.getForEntity(url, TripDTO.class);
+ }
+
+}
diff --git a/ass2-service/trip/src/test/java/dst/ass2/service/trip/tests/TripServiceTest.java b/ass2-service/trip/src/test/java/dst/ass2/service/trip/tests/TripServiceTest.java
new file mode 100644
index 0000000..b2a5260
--- /dev/null
+++ b/ass2-service/trip/src/test/java/dst/ass2/service/trip/tests/TripServiceTest.java
@@ -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 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 longs = new LinkedList<>();
+ longs.add(testData.location2Id);
+ longs.add(testData.location3Id);
+ longs.add(testData.location4Id);
+ tripDTO.setStops(longs);
+ return tripDTO;
+ }
+}
diff --git a/ass2-service/trip/src/test/resources/application.properties b/ass2-service/trip/src/test/resources/application.properties
new file mode 100644
index 0000000..c68b46d
--- /dev/null
+++ b/ass2-service/trip/src/test/resources/application.properties
@@ -0,0 +1 @@
+server.port=8091
diff --git a/pom.xml b/pom.xml
index f742244..137e20a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,6 +43,13 @@
org.apache.maven.plugins
maven-surefire-plugin
${maven-surefire-plugin.version}
+
+
+ org.apache.maven.surefire
+ surefire-junit4
+ ${maven-surefire-plugin.version}
+
+
false
alphabetical
@@ -255,6 +262,131 @@
javassist
${javassist.version}
+
+
+ commons-io
+ commons-io
+ ${commons-io.version}
+
+
+ org.aspectj
+ aspectjrt
+ ${aspectj.version}
+
+
+ org.aspectj
+ aspectjweaver
+ ${aspectj.version}
+
+
+ org.springframework
+ spring-aop
+ ${spring.version}
+
+
+ org.apache.commons
+ commons-lang3
+ ${commons-lang3.version}
+
+
+ io.grpc
+ grpc-all
+ ${grpc.version}
+
+
+ io.grpc
+ grpc-netty
+ ${grpc.version}
+
+
+ io.grpc
+ grpc-protobuf
+ ${grpc.version}
+
+
+ io.grpc
+ grpc-stub
+ ${grpc.version}
+
+
+ io.grpc
+ grpc-testing
+ ${grpc.version}
+
+
+ org.springframework
+ spring-orm
+ ${spring.version}
+
+
+ org.springframework
+ spring-context
+ ${spring.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-jersey
+ ${spring-boot.version}
+
+
+
+ org.springframework
+ spring-context
+
+
+
+
+ org.glassfish.jersey.core
+ jersey-client
+ ${jersey.version}
+
+
+
+ org.glassfish.jersey.core
+ jersey-server
+ ${jersey.version}
+
+
+ org.glassfish.jersey.ext
+ jersey-proxy-client
+ ${jersey.version}
+
+
+ org.springframework.boot
+ spring-boot-starter
+ ${spring-boot.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ ${spring-boot.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ ${spring-boot.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ ${jackson.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ ${jackson.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+
+
+ org.apache.httpcomponents
+ httpclient
+ ${httpclient.version}
+
+
@@ -266,6 +398,13 @@
ass1-jpa
ass1-doc
ass1-kv
+ ass2-service/api
+ ass2-service/auth-client
+ ass2-service/auth
+ ass2-service/trip
+ ass2-service/facade
+ ass2-aop
+ ass2-ioc
@@ -290,6 +429,65 @@
+
+ ass2-service
+
+ ass1-jpa
+ ass2-service/api
+ ass2-service/auth-client
+ ass2-service/auth
+ ass2-service/trip
+ ass2-service/facade
+
+
+
+
+ ass2-aop
+
+ ass2-aop
+
+
+
+
+ ass2-ioc
+
+
+
+ maven-jar-plugin
+ 2.6
+
+
+
+ dst.ass2.ioc.lock.LockingInjectorAgent
+
+
+ dst-ioc-agent
+
+
+
+
+ jar
+
+ test-compile
+
+
+
+
+ maven-surefire-plugin
+
+ -javaagent:"${project.build.directory}/dst-ioc-agent.jar"
+
+ dst/ass2/ioc/**/*.java
+
+
+
+
+
+
+ ass2-ioc
+
+
+
@@ -321,6 +519,16 @@
1.4.200
3.12.8
3.0.0
+
+ 1.9.6
+ 5.3.4
+ 2.4.3
+ 2.32
+ 2.8.0
+ 1.35.0
+ 0.6.1
+ 1.7.0
+ 4.5.13