Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
reynisson
2021-11-21 17:16:13 +01:00
33 changed files with 1012 additions and 8 deletions

View File

@@ -1,14 +1,20 @@
package ch.unisg.tapastasks;
import ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb.TaskRepository;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
@SpringBootApplication
@EnableMongoRepositories(basePackageClasses = TaskRepository.class)
public class TapasTasksApplication {
public static void main(String[] args) {
SpringApplication tapasTasksApp = new SpringApplication(TapasTasksApplication.class);
tapasTasksApp.run(args);
}
}

View File

@@ -0,0 +1,32 @@
package ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Data
@Document(collection = "tasks")
public class MongoTaskDocument {
@Id
public String taskId;
public String taskName;
public String taskType;
public String originalTaskUri;
public String taskStatus;
public String taskListName;
public MongoTaskDocument(String taskId, String taskName, String taskType,
String originalTaskUri,
String taskStatus, String taskListName) {
this.taskId = taskId;
this.taskName = taskName;
this.taskType = taskType;
this.originalTaskUri = originalTaskUri;
this.taskStatus = taskStatus;
this.taskListName = taskListName;
}
}

View File

@@ -0,0 +1,30 @@
package ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb;
import ch.unisg.tapastasks.tasks.domain.Task;
import ch.unisg.tapastasks.tasks.domain.TaskList;
import org.springframework.stereotype.Component;
@Component
class TaskMapper {
Task mapToDomainEntity(MongoTaskDocument task) {
return Task.withIdNameTypeOriginaluriStatus(
new Task.TaskId(task.taskId),
new Task.TaskName(task.taskName),
new Task.TaskType(task.taskType),
new Task.OriginalTaskUri(task.originalTaskUri),
new Task.TaskStatus(Task.Status.valueOf(task.taskStatus))
);
}
MongoTaskDocument mapToMongoDocument(Task task) {
return new MongoTaskDocument(
task.getTaskId().getValue(),
task.getTaskName().getValue(),
task.getTaskType().getValue(),
task.getOriginalTaskUri().getValue(),
task.getTaskStatus().getValue().toString(),
TaskList.getTapasTaskList().getTaskListName().getValue()
);
}
}

View File

@@ -0,0 +1,34 @@
package ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb;
import ch.unisg.tapastasks.tasks.application.port.out.AddTaskPort;
import ch.unisg.tapastasks.tasks.application.port.out.LoadTaskPort;
import ch.unisg.tapastasks.tasks.domain.Task;
import ch.unisg.tapastasks.tasks.domain.TaskList;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class TaskPersistenceAdapter implements
AddTaskPort,
LoadTaskPort {
@Autowired
private final TaskRepository taskRepository;
private final TaskMapper taskMapper;
@Override
public void addTask(Task task) {
MongoTaskDocument mongoTaskDocument = taskMapper.mapToMongoDocument(task);
taskRepository.save(mongoTaskDocument);
}
@Override
public Task loadTask(Task.TaskId taskId, TaskList.TaskListName taskListName) {
MongoTaskDocument mongoTaskDocument = taskRepository.findByTaskId(taskId.getValue(),taskListName.getValue());
Task task = taskMapper.mapToDomainEntity(mongoTaskDocument);
return task;
}
}

View File

@@ -0,0 +1,14 @@
package ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface TaskRepository extends MongoRepository<MongoTaskDocument,String> {
public MongoTaskDocument findByTaskId(String taskId, String taskListName);
public List<MongoTaskDocument> findByTaskListName(String taskListName);
}

View File

@@ -0,0 +1,9 @@
package ch.unisg.tapastasks.tasks.application.port.out;
import ch.unisg.tapastasks.tasks.domain.Task;
public interface AddTaskPort {
void addTask(Task task);
}

View File

@@ -0,0 +1,10 @@
package ch.unisg.tapastasks.tasks.application.port.out;
import ch.unisg.tapastasks.tasks.domain.Task;
import ch.unisg.tapastasks.tasks.domain.TaskList;
public interface LoadTaskPort {
Task loadTask(Task.TaskId taskId, TaskList.TaskListName taskListName);
}

View File

@@ -0,0 +1,11 @@
package ch.unisg.tapastasks.tasks.application.port.out;
import ch.unisg.tapastasks.tasks.domain.TaskList;
public interface TaskListLock {
void lockTaskList(TaskList.TaskListName taskListName);
void releaseTaskList(TaskList.TaskListName taskListName);
}

View File

@@ -2,26 +2,35 @@ package ch.unisg.tapastasks.tasks.application.service;
import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListCommand;
import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListUseCase;
import ch.unisg.tapastasks.tasks.application.port.out.AddTaskPort;
import ch.unisg.tapastasks.tasks.application.port.out.NewTaskAddedEventPort;
import ch.unisg.tapastasks.tasks.application.port.out.TaskListLock;
import ch.unisg.tapastasks.tasks.domain.Task;
import ch.unisg.tapastasks.tasks.domain.NewTaskAddedEvent;
import ch.unisg.tapastasks.tasks.domain.TaskList;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
@RequiredArgsConstructor
@Component
@Transactional
@Service("AddNewTaskToTaskList")
public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase {
private final NewTaskAddedEventPort newTaskAddedEventPort;
private final AddTaskPort addTaskToRepositoryPort;
private final TaskListLock taskListLock;
@Override
public Task addNewTaskToTaskList(AddNewTaskToTaskListCommand command) {
TaskList taskList = TaskList.getTapasTaskList();
taskListLock.lockTaskList(taskList.getTaskListName());
Task newTask;
if (command.getOriginalTaskUri().isPresent() && command.getInputData().isPresent()) {
@@ -37,6 +46,9 @@ public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase
newTask = taskList.addNewTaskWithNameAndType(command.getTaskName(), command.getTaskType());
}
addTaskToRepositoryPort.addTask(newTask);
taskListLock.releaseTaskList(taskList.getTaskListName());
//Here we are using the application service to emit the domain event to the outside of the bounded context.
//This event should be considered as a light-weight "integration event" to communicate with other services.
//Domain events are usually rather "fat". In our implementation we simplify at this point. In general, it is

View File

@@ -0,0 +1,18 @@
package ch.unisg.tapastasks.tasks.application.service;
import ch.unisg.tapastasks.tasks.application.port.out.TaskListLock;
import ch.unisg.tapastasks.tasks.domain.TaskList;
import org.springframework.stereotype.Component;
@Component
public class NoOpTaskListLock implements TaskListLock {
@Override
public void lockTaskList(TaskList.TaskListName taskListName) {
//do nothing
}
@Override
public void releaseTaskList(TaskList.TaskListName taskListName) {
//do nothing
}
}

View File

@@ -2,10 +2,12 @@ package ch.unisg.tapastasks.tasks.application.service;
import ch.unisg.tapastasks.tasks.application.port.in.RetrieveTaskFromTaskListQuery;
import ch.unisg.tapastasks.tasks.application.port.in.RetrieveTaskFromTaskListUseCase;
import ch.unisg.tapastasks.tasks.application.port.out.LoadTaskPort;
import ch.unisg.tapastasks.tasks.domain.Task;
import ch.unisg.tapastasks.tasks.domain.TaskList;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.Optional;
@@ -13,10 +15,19 @@ import java.util.Optional;
@RequiredArgsConstructor
@Component
@Transactional
@Service("RetrieveTaskFromTaskList")
public class RetrieveTaskFromTaskListService implements RetrieveTaskFromTaskListUseCase {
private final LoadTaskPort loadTaskFromRepositoryPort;
@Override
public Optional<Task> retrieveTaskFromTaskList(RetrieveTaskFromTaskListQuery query) {
TaskList taskList = TaskList.getTapasTaskList();
return taskList.retrieveTaskById(query.getTaskId());
Optional<Task> task = taskList.retrieveTaskById(query.getTaskId());
Optional<Task> taskFromRepo = Optional.ofNullable(loadTaskFromRepositoryPort.loadTask(query.getTaskId(), taskList.getTaskListName()));
return taskFromRepo;
}
}

View File

@@ -80,11 +80,22 @@ public class Task {
this.outputData = null;
}
public Task(TaskId taskId, TaskName taskName, TaskType taskType, OriginalTaskUri taskUri,
TaskStatus taskStatus) {
this.taskId = taskId;
this.taskName = taskName;
this.taskType = taskType;
this.originalTaskUri = taskUri;
this.taskStatus = taskStatus;
this.inputData = null;
this.outputData = null;
}
protected static Task createTaskWithNameAndType(TaskName name, TaskType type) {
return new Task(name, type);
}
protected static Task createTaskWithNameAndTypeAndOriginalTaskUri(TaskName name, TaskType type,
public static Task createTaskWithNameAndTypeAndOriginalTaskUri(TaskName name, TaskType type,
OriginalTaskUri originalTaskUri) {
return new Task(name, type, originalTaskUri);
}
@@ -99,6 +110,13 @@ public class Task {
return new Task(name, type, originalTaskUri, inputData);
}
public static Task withIdNameTypeOriginaluriStatus(TaskId taskId, TaskName taskName,
TaskType taskType,
OriginalTaskUri originalTaskUri,
TaskStatus taskStatus) {
return new Task(taskId, taskName, taskType, originalTaskUri, taskStatus);
}
@Value
public static class TaskId {
String value;

View File

@@ -1,3 +1,40 @@
server.port=8081
spring.data.mongodb.uri=mongodb://127.0.0.1:27017
#spring.data.mongodb.uri=mongodb://root:8nP7s0a@mongodb:27017/
spring.data.mongodb.database=tapas-tasks
baseuri=https://tapas-tasks.86-119-34-23.nip.io/
roster.uri=http://127.0.0.1:8082
spring.profiles.active=chaos-monkey
chaos.monkey.enabled=true
management.endpoint.chaosmonkey.enabled=true
management.endpoint.chaosmonkeyjmx.enabled=true
# include specific endpoints
management.endpoints.web.exposure.include=health,info,chaosmonkey
chaos.monkey.watcher.controller=true
chaos.monkey.watcher.restController=true
chaos.monkey.watcher.service=true
chaos.monkey.watcher.repository=true
chaos.monkey.watcher.component=true
#Chaos Monkey configs taken from here: https://www.baeldung.com/spring-boot-chaos-monkey
#Latency Assault
#chaos.monkey.assaults.latencyActive=true
#chaos.monkey.assaults.latencyRangeStart=3000
#chaos.monkey.assaults.latencyRangeEnd=15000
#Exception Assault
#chaos.monkey.assaults.latencyActive=false
#chaos.monkey.assaults.exceptionsActive=true
#chaos.monkey.assaults.killApplicationActive=false
#AppKiller Assault
#chaos.monkey.assaults.latencyActive=false
#chaos.monkey.assaults.exceptionsActive=false
#chaos.monkey.assaults.killApplicationActive=true
#Chaos Monkey assaults via REST to endpoint /actuator/chaosmonkey/assaults/
#https://softwarehut.com/blog/tech/chaos-monkey
#https://codecentric.github.io/chaos-monkey-spring-boot/latest/

View File

@@ -0,0 +1,76 @@
package ch.unisg.tapastasks;
import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonRepresentation;
import ch.unisg.tapastasks.tasks.application.port.out.AddTaskPort;
import ch.unisg.tapastasks.tasks.domain.Task;
import ch.unisg.tapastasks.tasks.domain.TaskList;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import static org.assertj.core.api.BDDAssertions.*;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class AddNewTaskToTaskListSystemTest {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private AddTaskPort addTaskPort;
@Test
void addNewTaskToTaskList() throws JSONException {
Task.TaskName taskName = new Task.TaskName("system-integration-test-task");
Task.TaskType taskType = new Task.TaskType("system-integration-test-type");
Task.OriginalTaskUri originalTaskUri = new Task.OriginalTaskUri("example.org");
ResponseEntity response = whenAddNewTaskToEmptyList(taskName, taskType, originalTaskUri);
JSONObject responseJson = new JSONObject(response.getBody().toString());
String respTaskId = responseJson.getString("taskId");
String respTaskName = responseJson.getString("taskName");
String respTaskType = responseJson.getString("taskType");
then(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
then(respTaskId).isNotEmpty();
then(respTaskName).isEqualTo(taskName.getValue());
then(respTaskType).isEqualTo(taskType.getValue());
then(TaskList.getTapasTaskList().getListOfTasks().getValue()).hasSize(1);
}
private ResponseEntity whenAddNewTaskToEmptyList(
Task.TaskName taskName,
Task.TaskType taskType,
Task.OriginalTaskUri originalTaskUri) throws JSONException {
TaskList.getTapasTaskList().getListOfTasks().getValue().clear();
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", TaskJsonRepresentation.MEDIA_TYPE);
String jsonPayLoad = new JSONObject()
.put("taskName", taskName.getValue() )
.put("taskType", taskType.getValue())
.put("originalTaskUri",originalTaskUri.getValue())
.toString();
HttpEntity<String> request = new HttpEntity<>(jsonPayLoad,headers);
return restTemplate.exchange(
"/tasks/",
HttpMethod.POST,
request,
Object.class
);
}
}

View File

@@ -0,0 +1,24 @@
package ch.unisg.tapastasks;
import ch.unisg.tapastasks.archunit.HexagonalArchitecture;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
class DependencyRuleTests {
@Test
void testPackageDependencies() {
noClasses()
.that()
.resideInAPackage("ch.unisg.tapastasks.tasks.domain..")
.should()
.dependOnClassesThat()
.resideInAnyPackage("ch.unisg.tapastasks.tasks.application..")
.check(new ClassFileImporter()
.importPackages("ch.unisg.tapastasks.tasks.."));
}
}

View File

@@ -1,13 +1,18 @@
package ch.unisg.tapastasks;
import ch.unisg.tapastasks.tasks.application.port.out.AddTaskPort;
import ch.unisg.tapastasks.tasks.application.port.out.NewTaskAddedEventPort;
import ch.unisg.tapastasks.tasks.application.port.out.TaskListLock;
import ch.unisg.tapastasks.tasks.application.service.AddNewTaskToTaskListService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class TapasTasksApplicationTests {
@Test
@Test
void contextLoads() {
}
}

View File

@@ -0,0 +1,62 @@
package ch.unisg.tapastasks.archunit;
import com.tngtech.archunit.core.domain.JavaClasses;
import java.util.ArrayList;
import java.util.List;
public class Adapters extends ArchitectureElement {
private final HexagonalArchitecture parentContext;
private List<String> incomingAdapterPackages = new ArrayList<>();
private List<String> outgoingAdapterPackages = new ArrayList<>();
Adapters(HexagonalArchitecture parentContext, String basePackage) {
super(basePackage);
this.parentContext = parentContext;
}
public Adapters outgoing(String packageName) {
this.incomingAdapterPackages.add(fullQualifiedPackage(packageName));
return this;
}
public Adapters incoming(String packageName) {
this.outgoingAdapterPackages.add(fullQualifiedPackage(packageName));
return this;
}
List<String> allAdapterPackages() {
List<String> allAdapters = new ArrayList<>();
allAdapters.addAll(incomingAdapterPackages);
allAdapters.addAll(outgoingAdapterPackages);
return allAdapters;
}
public HexagonalArchitecture and() {
return parentContext;
}
String getBasePackage() {
return basePackage;
}
void dontDependOnEachOther(JavaClasses classes) {
List<String> allAdapters = allAdapterPackages();
for (String adapter1 : allAdapters) {
for (String adapter2 : allAdapters) {
if (!adapter1.equals(adapter2)) {
denyDependency(adapter1, adapter2, classes);
}
}
}
}
void doesNotDependOn(String packageName, JavaClasses classes) {
denyDependency(this.basePackage, packageName, classes);
}
void doesNotContainEmptyPackages() {
denyEmptyPackages(allAdapterPackages());
}
}

View File

@@ -0,0 +1,59 @@
package ch.unisg.tapastasks.archunit;
import com.tngtech.archunit.core.domain.JavaClasses;
import java.util.ArrayList;
import java.util.List;
public class ApplicationLayer extends ArchitectureElement {
private final HexagonalArchitecture parentContext;
private List<String> incomingPortsPackages = new ArrayList<>();
private List<String> outgoingPortsPackages = new ArrayList<>();
private List<String> servicePackages = new ArrayList<>();
public ApplicationLayer(String basePackage, HexagonalArchitecture parentContext) {
super(basePackage);
this.parentContext = parentContext;
}
public ApplicationLayer incomingPorts(String packageName) {
this.incomingPortsPackages.add(fullQualifiedPackage(packageName));
return this;
}
public ApplicationLayer outgoingPorts(String packageName) {
this.outgoingPortsPackages.add(fullQualifiedPackage(packageName));
return this;
}
public ApplicationLayer services(String packageName) {
this.servicePackages.add(fullQualifiedPackage(packageName));
return this;
}
public HexagonalArchitecture and() {
return parentContext;
}
public void doesNotDependOn(String packageName, JavaClasses classes) {
denyDependency(this.basePackage, packageName, classes);
}
public void incomingAndOutgoingPortsDoNotDependOnEachOther(JavaClasses classes) {
denyAnyDependency(this.incomingPortsPackages, this.outgoingPortsPackages, classes);
denyAnyDependency(this.outgoingPortsPackages, this.incomingPortsPackages, classes);
}
private List<String> allPackages() {
List<String> allPackages = new ArrayList<>();
allPackages.addAll(incomingPortsPackages);
allPackages.addAll(outgoingPortsPackages);
allPackages.addAll(servicePackages);
return allPackages;
}
void doesNotContainEmptyPackages() {
denyEmptyPackages(allPackages());
}
}

View File

@@ -0,0 +1,74 @@
package ch.unisg.tapastasks.archunit;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import java.util.List;
import static com.tngtech.archunit.base.DescribedPredicate.*;
import static com.tngtech.archunit.base.DescribedPredicate.greaterThanOrEqualTo;
import static com.tngtech.archunit.lang.conditions.ArchConditions.*;
import static com.tngtech.archunit.lang.conditions.ArchConditions.containNumberOfElements;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
abstract class ArchitectureElement {
final String basePackage;
public ArchitectureElement(String basePackage) {
this.basePackage = basePackage;
}
String fullQualifiedPackage(String relativePackage) {
return this.basePackage + "." + relativePackage;
}
static void denyDependency(String fromPackageName, String toPackageName, JavaClasses classes) {
noClasses()
.that()
.resideInAPackage("ch.unisg.tapastasks.tasks.domain..")
.should()
.dependOnClassesThat()
.resideInAnyPackage("ch.unisg.tapastasks.tasks.application..")
.check(classes);
}
static void denyAnyDependency(
List<String> fromPackages, List<String> toPackages, JavaClasses classes) {
for (String fromPackage : fromPackages) {
for (String toPackage : toPackages) {
noClasses()
.that()
.resideInAPackage(matchAllClassesInPackage(fromPackage))
.should()
.dependOnClassesThat()
.resideInAnyPackage(matchAllClassesInPackage(toPackage))
.check(classes);
}
}
}
static String matchAllClassesInPackage(String packageName) {
return packageName + "..";
}
void denyEmptyPackage(String packageName) {
classes()
.that()
.resideInAPackage(matchAllClassesInPackage(packageName))
.should(containNumberOfElements(greaterThanOrEqualTo(1)))
.check(classesInPackage(packageName));
}
private JavaClasses classesInPackage(String packageName) {
return new ClassFileImporter().importPackages(packageName);
}
void denyEmptyPackages(List<String> packages) {
for (String packageName : packages) {
denyEmptyPackage(packageName);
}
}
}

View File

@@ -0,0 +1,61 @@
package ch.unisg.tapastasks.archunit;
import com.tngtech.archunit.core.domain.JavaClasses;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class HexagonalArchitecture extends ArchitectureElement {
private Adapters adapters;
private ApplicationLayer applicationLayer;
private String configurationPackage;
private List<String> domainPackages = new ArrayList<>();
public static HexagonalArchitecture boundedContext(String basePackage) {
return new HexagonalArchitecture(basePackage);
}
public HexagonalArchitecture(String basePackage) {
super(basePackage);
}
public Adapters withAdaptersLayer(String adaptersPackage) {
this.adapters = new Adapters(this, fullQualifiedPackage(adaptersPackage));
return this.adapters;
}
public HexagonalArchitecture withDomainLayer(String domainPackage) {
this.domainPackages.add(fullQualifiedPackage(domainPackage));
return this;
}
public ApplicationLayer withApplicationLayer(String applicationPackage) {
this.applicationLayer = new ApplicationLayer(fullQualifiedPackage(applicationPackage), this);
return this.applicationLayer;
}
public HexagonalArchitecture withConfiguration(String packageName) {
this.configurationPackage = fullQualifiedPackage(packageName);
return this;
}
private void domainDoesNotDependOnOtherPackages(JavaClasses classes) {
denyAnyDependency(
this.domainPackages, Collections.singletonList(adapters.basePackage), classes);
denyAnyDependency(
this.domainPackages, Collections.singletonList(applicationLayer.basePackage), classes);
}
public void check(JavaClasses classes) {
this.adapters.doesNotContainEmptyPackages();
this.adapters.dontDependOnEachOther(classes);
this.adapters.doesNotDependOn(this.configurationPackage, classes);
this.applicationLayer.doesNotContainEmptyPackages();
this.applicationLayer.doesNotDependOn(this.adapters.getBasePackage(), classes);
this.applicationLayer.doesNotDependOn(this.configurationPackage, classes);
this.applicationLayer.incomingAndOutgoingPortsDoNotDependOnEachOther(classes);
this.domainDoesNotDependOnOtherPackages(classes);
}
}

View File

@@ -0,0 +1,68 @@
package ch.unisg.tapastasks.tasks.adapter.in.web;
import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonRepresentation;
import ch.unisg.tapastasks.tasks.adapter.in.web.AddNewTaskToTaskListWebController;
import ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb.TaskRepository;
import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListCommand;
import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListUseCase;
import ch.unisg.tapastasks.tasks.domain.Task;
import org.json.JSONObject;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Optional;
import static org.mockito.BDDMockito.eq;
import static org.mockito.BDDMockito.then;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(controllers = AddNewTaskToTaskListWebController.class)
public class AddNewTaskToTaskListWebControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private AddNewTaskToTaskListUseCase addNewTaskToTaskListUseCase;
@MockBean
TaskRepository taskRepository;
@Disabled
@Test
void testAddNewTaskToTaskList() throws Exception {
String taskName = "test-request";
String taskType = "test-request-type";
String originalTaskUri = "example.org";
String jsonPayLoad = new JSONObject()
.put("taskName", taskName )
.put("taskType", taskType)
.put("originalTaskUri",originalTaskUri)
.toString();
//This raises a NullPointerException since it tries to build the HTTP response with attributes from
//the domain object (created task), which is mocked --> we need System Tests here!
//See the buckpal example from the lecture for a working integration test for testing the web controller
mockMvc.perform(post("/tasks/")
.contentType(TaskJsonRepresentation.MEDIA_TYPE)
.content(jsonPayLoad))
.andExpect(status().isCreated());
then(addNewTaskToTaskListUseCase).should()
.addNewTaskToTaskList(eq(new AddNewTaskToTaskListCommand(
new Task.TaskName(taskName), new Task.TaskType(taskType),
Optional.of(new Task.OriginalTaskUri(originalTaskUri))
)));
}
}

View File

@@ -0,0 +1,77 @@
package ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb;
import ch.unisg.tapastasks.tasks.domain.Task;
import ch.unisg.tapastasks.tasks.domain.TaskList;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.AutoConfigureDataMongo;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@AutoConfigureDataMongo
@Import({TaskPersistenceAdapter.class, TaskMapper.class})
public class TaskPersistenceAdapterTest {
@Autowired
private TaskRepository taskRepository;
@Autowired
private TaskPersistenceAdapter adapterUnderTest;
@Test
void addsNewTask() {
String testTaskId = UUID.randomUUID().toString();
String testTaskName = "adds-persistence-task-name";
String testTaskType = "adds-persistence-task-type";
String testTaskOuri = "adds-persistence-test-task-ouri";
String testTaskStatus = Task.Status.OPEN.toString();
String testTaskListName = "tapas-tasks-tutors";
Task testTask = new Task(
new Task.TaskId(testTaskId),
new Task.TaskName(testTaskName),
new Task.TaskType(testTaskType),
new Task.OriginalTaskUri(testTaskOuri),
new Task.TaskStatus(Task.Status.valueOf(testTaskStatus))
);
adapterUnderTest.addTask(testTask);
MongoTaskDocument retrievedDoc = taskRepository.findByTaskId(testTaskId,testTaskListName);
assertThat(retrievedDoc.taskId).isEqualTo(testTaskId);
assertThat(retrievedDoc.taskName).isEqualTo(testTaskName);
assertThat(retrievedDoc.taskListName).isEqualTo(testTaskListName);
}
@Test
void retrievesTask() {
String testTaskId = UUID.randomUUID().toString();
String testTaskName = "reads-persistence-task-name";
String testTaskType = "reads-persistence-task-type";
String testTaskOuri = "reads-persistence-test-task-ouri";
String testTaskStatus = Task.Status.OPEN.toString();
String testTaskListName = "tapas-tasks-tutors";
MongoTaskDocument mongoTask = new MongoTaskDocument(testTaskId, testTaskName, testTaskType, testTaskOuri,
testTaskStatus, testTaskListName);
taskRepository.insert(mongoTask);
Task retrievedTask = adapterUnderTest.loadTask(new Task.TaskId(testTaskId),
new TaskList.TaskListName(testTaskListName));
assertThat(retrievedTask.getTaskName().getValue()).isEqualTo(testTaskName);
assertThat(retrievedTask.getTaskId().getValue()).isEqualTo(testTaskId);
assertThat(retrievedTask.getTaskStatus().getValue()).isEqualTo(Task.Status.valueOf(testTaskStatus));
}
}

View File

@@ -0,0 +1,63 @@
package ch.unisg.tapastasks.tasks.application.service;
import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListCommand;
import ch.unisg.tapastasks.tasks.application.port.out.AddTaskPort;
import ch.unisg.tapastasks.tasks.application.port.out.NewTaskAddedEventPort;
import ch.unisg.tapastasks.tasks.application.port.out.TaskListLock;
import ch.unisg.tapastasks.tasks.domain.NewTaskAddedEvent;
import ch.unisg.tapastasks.tasks.domain.Task;
import ch.unisg.tapastasks.tasks.domain.TaskList;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.Optional;
import static org.mockito.BDDMockito.*;
import static org.assertj.core.api.Assertions.*;
public class AddNewTaskToTaskListServiceTest {
private final AddTaskPort addTaskPort = Mockito.mock(AddTaskPort.class);
private final TaskListLock taskListLock = Mockito.mock(TaskListLock.class);
private final NewTaskAddedEventPort newTaskAddedEventPort = Mockito.mock(NewTaskAddedEventPort.class);
private final AddNewTaskToTaskListService addNewTaskToTaskListService = new AddNewTaskToTaskListService(
newTaskAddedEventPort, addTaskPort, taskListLock);
@Test
void addingSucceeds() {
Task newTask = givenATaskWithNameAndTypeAndURI(new Task.TaskName("test-task"),
new Task.TaskType("test-type"), Optional.of(new Task.OriginalTaskUri("example.org")));
TaskList taskList = givenAnEmptyTaskList(TaskList.getTapasTaskList());
AddNewTaskToTaskListCommand addNewTaskToTaskListCommand = new AddNewTaskToTaskListCommand(newTask.getTaskName(),
newTask.getTaskType(), Optional.ofNullable(newTask.getOriginalTaskUri()));
Task addedTask = addNewTaskToTaskListService.addNewTaskToTaskList(addNewTaskToTaskListCommand);
assertThat(addedTask).isNotNull();
assertThat(taskList.getListOfTasks().getValue()).hasSize(1);
then(taskListLock).should().lockTaskList(eq(TaskList.getTapasTaskList().getTaskListName()));
then(newTaskAddedEventPort).should(times(1))
.publishNewTaskAddedEvent(any(NewTaskAddedEvent.class));
}
private TaskList givenAnEmptyTaskList(TaskList taskList) {
taskList.getListOfTasks().getValue().clear();
return taskList;
}
private Task givenATaskWithNameAndTypeAndURI(Task.TaskName taskName, Task.TaskType taskType,
Optional<Task.OriginalTaskUri> originalTaskUri) {
Task task = Mockito.mock(Task.class);
given(task.getTaskName()).willReturn(taskName);
given(task.getTaskType()).willReturn(taskType);
given(task.getOriginalTaskUri()).willReturn(originalTaskUri.get());
return task;
}
}

View File

@@ -0,0 +1,49 @@
package ch.unisg.tapastasks.tasks.domain;
import org.junit.jupiter.api.Test;
import java.util.Optional;
import static org.assertj.core.api.Assertions.*;
public class TaskListTest {
@Test
void addNewTaskToTaskListSuccess() {
TaskList taskList = TaskList.getTapasTaskList();
taskList.getListOfTasks().getValue().clear();
Task newTask = taskList.addNewTaskWithNameAndType(new Task.TaskName("My-Test-Task"),
new Task.TaskType("My-Test-Type"));
assertThat(newTask.getTaskName().getValue()).isEqualTo("My-Test-Task");
assertThat(taskList.getListOfTasks().getValue()).hasSize(1);
assertThat(taskList.getListOfTasks().getValue().get(0)).isEqualTo(newTask);
}
@Test
void retrieveTaskSuccess() {
TaskList taskList = TaskList.getTapasTaskList();
Task newTask = taskList.addNewTaskWithNameAndType(new Task.TaskName("My-Test-Task2"),
new Task.TaskType("My-Test-Type2"));
Task retrievedTask = taskList.retrieveTaskById(newTask.getTaskId()).get();
assertThat(retrievedTask).isEqualTo(newTask);
}
@Test
void retrieveTaskFailure() {
TaskList taskList = TaskList.getTapasTaskList();
Task newTask = taskList.addNewTaskWithNameAndType(new Task.TaskName("My-Test-Task3"),
new Task.TaskType("My-Test-Type3"));
Task.TaskId fakeId = new Task.TaskId("fake-id");
Optional<Task> retrievedTask = taskList.retrieveTaskById(fakeId);
assertThat(retrievedTask.isPresent()).isFalse();
}
}

View File

@@ -0,0 +1,2 @@
spring.data.mongodb.uri=mongodb://127.0.0.1:27017
spring.data.mongodb.database=tapas-tasks