This commit is contained in:
gushakov
2021-12-13 08:54:20 +01:00
parent 6d774ee1e9
commit f58cc3157d
6 changed files with 128 additions and 11 deletions

View File

@@ -10,3 +10,5 @@ Here are some references and links which were used for this application:
- Interesting question also involving academic domain
2. [Transactional, roll back transaction manually](https://stackoverflow.com/a/23502214)
3. [Baeldung, Jackson ignore null fields](https://www.baeldung.com/jackson-ignore-null-fields)

View File

@@ -6,4 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
public interface CourseEntityRepository extends JpaRepository<CourseEntity, Integer> {
boolean existsCourseEntityByTitleLike(String title);
}

View File

@@ -0,0 +1,16 @@
package com.github.cleanddd.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder;
import lombok.Data;
// omit null fields from: https://www.baeldung.com/jackson-ignore-null-fields
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CreateCourseResponse {
Boolean existsAlready;
Integer courseId;
}

View File

@@ -0,0 +1,15 @@
package com.github.cleanddd.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CreateStudentResponse {
Boolean existsAlready;
Integer studentId;
}

View File

@@ -1,5 +1,7 @@
package com.github.cleanddd.usecase;
import com.github.cleanddd.dto.CreateCourseResponse;
import com.github.cleanddd.dto.CreateStudentResponse;
import com.github.cleanddd.model.Course;
import com.github.cleanddd.model.EnrollResult;
import com.github.cleanddd.model.Student;
@@ -25,13 +27,16 @@ public class EnrollStudentUseCase implements EnrollStudentInputPort {
public void createCourse(String title) {
if (persistenceOps.courseExistsWithTitle(title)) {
restPresenter.presentOk(Map.of("exists", "already"));
restPresenter.presentOk(CreateCourseResponse.builder().existsAlready(true));
} else {
final Integer courseId = persistenceOps.persist(Course.builder()
.title(title)
.build());
restPresenter.presentOk(Map.of("courseId", courseId));
restPresenter.presentOk(CreateCourseResponse.builder()
.existsAlready(false)
.courseId(courseId)
.build());
}
}
@@ -39,15 +44,16 @@ public class EnrollStudentUseCase implements EnrollStudentInputPort {
@Transactional
public void createStudent(String fullName) {
if (persistenceOps.studentExistsWithFullName(fullName)){
restPresenter.presentOk(Map.of("exists", "already"));
}
else {
if (persistenceOps.studentExistsWithFullName(fullName)) {
restPresenter.presentOk(CreateStudentResponse.builder().existsAlready(true).build());
} else {
final Integer studentId = persistenceOps.persist(Student.builder()
.fullName(fullName)
.build());
restPresenter.presentOk(Map.of("studentId", studentId));
restPresenter.presentOk(CreateStudentResponse.builder().existsAlready(false)
.studentId(studentId)
.build());
}
}
@@ -71,9 +77,7 @@ public class EnrollStudentUseCase implements EnrollStudentInputPort {
}
// present the result of enrollment
restPresenter.presentOk(Map.of("studentId", studentId,
"newEnrollment", enrollResult.isCourseAdded(),
"coursesIds", enrollResult.getStudent().getCoursesIds()));
restPresenter.presentOk(enrollResult);
} catch (Exception e) {
restPresenter.presentError(e);
}

View File

@@ -0,0 +1,79 @@
package com.github.cleanddd.usecase;
import com.github.cleanddd.model.Course;
import com.github.cleanddd.model.EnrollResult;
import com.github.cleanddd.model.Student;
import com.github.cleanddd.port.PersistenceOperationsOutputPort;
import com.github.cleanddd.port.RestPresenterOutputPort;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Set;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
public class EnrollStudentUseCaseTest {
@Mock
private RestPresenterOutputPort presenter;
@Mock
private PersistenceOperationsOutputPort persistenceOps;
@BeforeEach
void setUp() {
// return a mock course when asked to obtain one
lenient().when(persistenceOps.obtainCourseById(anyInt()))
.thenAnswer(invocation -> Course.builder()
.id(invocation.getArgument(0))
.title("Software architecture 101")
.build());
// return a mock student enrolled in some courses when asked to obtain one
lenient().when(persistenceOps.obtainStudentById(anyInt()))
.thenAnswer(invocation -> Student.builder()
.id(invocation.getArgument(0))
.fullName("Brad Pitt")
.coursesIds(Set.of(1, 2, 3))
.build());
// just return the ID a course or a student when asked to
// persist it
lenient().when(persistenceOps.persist(any(Course.class)))
.thenAnswer(invocation -> ((Course) invocation.getArgument(0)).getId());
lenient().when(persistenceOps.persist(any(Student.class)))
.thenAnswer(invocation -> ((Student) invocation.getArgument(0)).getId());
}
@Test
void testEnroll_StudentCanEnrollInCourseSheIsNotYetEnrolledIn() {
final EnrollStudentUseCase useCase = new EnrollStudentUseCase(presenter, persistenceOps);
// enroll student in a new course
useCase.enroll(4, 1);
assertNoError();
// verify the result of enrollment
final ArgumentCaptor<EnrollResult> resultArg = ArgumentCaptor.forClass(EnrollResult.class);
verify(presenter, times(1))
.presentOk(resultArg.capture());
final EnrollResult enrollResult = resultArg.getValue();
Assertions.assertThat(enrollResult.isCourseAdded()).isTrue();
Assertions.assertThat(enrollResult.getStudent().getCoursesIds())
.containsOnly(1,2,3,4);
}
private void assertNoError() {
verify(presenter, times(0)).presentError(any(Throwable.class));
}
}