Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion src/docs/asciidoc/grooming-test-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ include::{snippetsDir}/loadGroomingTests/1/response-fields.adoc[]
==== Request
include::{snippetsDir}/submitGroomingTests/1/http-request.adoc[]

==== Request Body Fields
include::{snippetsDir}/submitGroomingTests/1/request-fields.adoc[]

==== 성공 Response
include::{snippetsDir}/submitGroomingTests/1/http-response.adoc[]

Expand Down Expand Up @@ -49,4 +52,30 @@ include::{snippetsDir}/checkGroomingTestAvailability/3/http-response.adoc[]
include::{snippetsDir}/checkGroomingTestAvailability/1/response-fields.adoc[]

==== 실패 Response
include::{snippetsDir}/checkGroomingTestAvailability/4/http-response.adoc[]
include::{snippetsDir}/checkGroomingTestAvailability/4/http-response.adoc[]

---

=== **4. 그루밍 테스트 결과 저장**

그루밍 테스트 결과를 저장하는 api 입니다. (비로그인 그루밍 테스트 이후 회원가입 할 때)

==== Request
include::{snippetsDir}/saveGroomingTestResult/1/http-request.adoc[]

==== Request Body Fields
include::{snippetsDir}/saveGroomingTestResult/1/request-fields.adoc[]

==== 성공 Response
include::{snippetsDir}/saveGroomingTestResult/1/http-response.adoc[]

==== Response Body Fields
include::{snippetsDir}/saveGroomingTestResult/1/response-fields.adoc[]

==== 실패 Response
실패 1. 존재하지 않는 유저인 경우

include::{snippetsDir}/saveGroomingTestResult/2/http-response.adoc[]

실패 2. 존재하지 않는 그루밍 레벨인 경우
include::{snippetsDir}/saveGroomingTestResult/3/http-response.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.ftm.server.adapter.in.web.grooming.controller;

import com.ftm.server.adapter.in.web.grooming.dto.request.SaveGroomingTestResultRequest;
import com.ftm.server.application.command.grooming.SaveGroomingTestResultCommand;
import com.ftm.server.application.port.in.grooming.SaveGroomingTestResultUseCase;
import com.ftm.server.common.response.ApiResponse;
import com.ftm.server.common.response.enums.SuccessResponseCode;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class SaveGroomingTestResultController {

private final SaveGroomingTestResultUseCase saveGroomingTestResultUseCase;

@PostMapping("/api/grooming/tests")
public ResponseEntity<ApiResponse<Void>> saveGroomingTestResult(
@RequestBody SaveGroomingTestResultRequest request) {
saveGroomingTestResultUseCase.execute(SaveGroomingTestResultCommand.from(request));

return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(SuccessResponseCode.CREATED));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.ftm.server.adapter.in.web.grooming.dto.request;

import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class SaveGroomingTestResultRequest {

private Long userId;
private Long groomingLevelId;
private Integer totalScore;
private List<GroomingTestResult> results;

@Getter
@AllArgsConstructor
public static class GroomingTestResult {

private Long questionId;
private List<Long> answerIds;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.ftm.server.application.command.grooming;

import com.ftm.server.adapter.in.web.grooming.dto.request.SaveGroomingTestResultRequest;
import java.util.List;
import lombok.Getter;

@Getter
public class SaveGroomingTestResultCommand {

private final Long userId;
private final Long groomingLevelId;
private final Integer totalScore;
private final List<GroomingTestResult> results;

@Getter
public static class GroomingTestResult {
private final Long questionId;
private final List<Long> answerIds;

private GroomingTestResult(Long questionId, List<Long> answerIds) {
this.questionId = questionId;
this.answerIds = answerIds;
}

public static GroomingTestResult of(Long questionId, List<Long> answerIds) {
return new GroomingTestResult(questionId, answerIds);
}
}

private SaveGroomingTestResultCommand(SaveGroomingTestResultRequest request) {
this.userId = request.getUserId();
this.groomingLevelId = request.getGroomingLevelId();
this.totalScore = request.getTotalScore();
this.results =
request.getResults().stream()
.map(
result ->
GroomingTestResult.of(
result.getQuestionId(), result.getAnswerIds()))
.toList();
}

public static SaveGroomingTestResultCommand from(SaveGroomingTestResultRequest request) {
return new SaveGroomingTestResultCommand(request);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ftm.server.application.port.in.grooming;

import com.ftm.server.application.command.grooming.SaveGroomingTestResultCommand;
import com.ftm.server.common.annotation.UseCase;

@UseCase
public interface SaveGroomingTestResultUseCase {

void execute(SaveGroomingTestResultCommand command);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.ftm.server.application.service.grooming;

import com.ftm.server.application.command.grooming.SaveGroomingTestResultCommand;
import com.ftm.server.application.port.in.grooming.SaveGroomingTestResultUseCase;
import com.ftm.server.application.port.out.persistence.grooming.LoadUserForGroomingPort;
import com.ftm.server.application.port.out.persistence.grooming.SaveGroomingTestResultPort;
import com.ftm.server.application.port.out.persistence.grooming.UpdateUserForGroomingPort;
import com.ftm.server.application.query.FindByIdQuery;
import com.ftm.server.common.exception.CustomException;
import com.ftm.server.domain.entity.GroomingTestResult;
import com.ftm.server.domain.entity.User;
import java.time.LocalDateTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class SaveGroomingTestResultService implements SaveGroomingTestResultUseCase {

private final SaveGroomingTestResultPort saveGroomingTestResultPort;
private final UpdateUserForGroomingPort updateUserForGroomingPort;
private final LoadUserForGroomingPort loadUserForGroomingPort;

@Override
@Transactional
public void execute(SaveGroomingTestResultCommand command) {
User user =
loadUserForGroomingPort
.loadUser(FindByIdQuery.of(command.getUserId()))
.orElseThrow(() -> CustomException.USER_NOT_FOUND);

// 그루밍 테스트 결과 저장
saveUserGroomingTestResult(command);

// 유저 그루밍 정보 업데이트
updateUserGroomingInfo(user, command);
}

private void saveUserGroomingTestResult(SaveGroomingTestResultCommand command) {
// 그루밍 테스트 결과 등록
LocalDateTime testedAt = LocalDateTime.now();
List<GroomingTestResult> groomingTestResults =
command.getResults().stream()
.flatMap(
result ->
result.getAnswerIds().stream()
.map(
answerId ->
GroomingTestResult.create(
command.getUserId(),
result.getQuestionId(),
answerId,
testedAt)))
.toList();
saveGroomingTestResultPort.saveGroomingTestResults(groomingTestResults);
}

private void updateUserGroomingInfo(User user, SaveGroomingTestResultCommand command) {
user.updateGroomingInfo(command.getTotalScore(), command.getGroomingLevelId());

updateUserForGroomingPort.updateUserGroomingStatus(user);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ public class SecurityConfig {
"/api/auth/login/**",
"/api/users",
"/api/users/social",
"/api/grooming/tests/submission"
"/api/grooming/tests/submission",
"/api/grooming/tests"
};

private static final String[] ANONYMOUS_MATCHERS = {"/docs/**"};
Expand Down
168 changes: 168 additions & 0 deletions src/test/java/com/ftm/server/grooming/SaveGroomingTestResultTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package com.ftm.server.grooming;

import static com.epages.restdocs.apispec.ResourceDocumentation.resource;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.payload.JsonFieldType.*;
import static org.springframework.restdocs.payload.JsonFieldType.NUMBER;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import com.epages.restdocs.apispec.ResourceSnippetParameters;
import com.ftm.server.BaseTest;
import com.ftm.server.adapter.in.web.grooming.dto.request.SaveGroomingTestResultRequest;
import com.ftm.server.application.command.user.GeneralUserCreationCommand;
import com.ftm.server.application.port.out.persistence.auth.LoadUserForAuthPort;
import com.ftm.server.application.port.out.persistence.user.SaveUserImagePort;
import com.ftm.server.application.port.out.persistence.user.SaveUserPort;
import com.ftm.server.application.port.out.security.SecurityAuthenticationPort;
import com.ftm.server.application.query.FindByEmailQuery;
import com.ftm.server.common.response.enums.ErrorResponseCode;
import com.ftm.server.domain.entity.User;
import com.ftm.server.domain.entity.UserImage;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.transaction.annotation.Transactional;

public class SaveGroomingTestResultTest extends BaseTest {

@Autowired private SecurityAuthenticationPort securityAuthenticationPort;
@Autowired private SaveUserPort saveUserPort;
@Autowired private SaveUserImagePort saveUserImagePort;
@Autowired private LoadUserForAuthPort loadUserForAuthPort;

private final List<FieldDescriptor> requestFieldSaveGroomingTestResult =
List.of(
fieldWithPath("userId").type(NUMBER).description("유저 ID"),
fieldWithPath("groomingLevelId").type(NUMBER).description("그루밍 레벨 ID"),
fieldWithPath("totalScore").type(NUMBER).description("그루밍 테스트 총 점수"),
fieldWithPath("results[]").type(ARRAY).optional().description("그루밍 테스트 결과 목록"),
fieldWithPath("results[].questionId").type(NUMBER).description("질문 ID"),
fieldWithPath("results[].answerIds[]").type(ARRAY).description("선택한 답변 ID 목록"));

private final List<FieldDescriptor> responseFieldSaveGroomingTestResult =
List.of(
fieldWithPath("status").type(NUMBER).description("응답 상태"),
fieldWithPath("code").type(STRING).description("상태 코드"),
fieldWithPath("message").type(STRING).description("메시지"),
fieldWithPath("data").type(OBJECT).optional().description("응답 데이터"));

private ResultActions getResultActions(SaveGroomingTestResultRequest request) throws Exception {
return mockMvc.perform(
RestDocumentationRequestBuilders.post("/api/grooming/tests")
.contentType(APPLICATION_JSON_VALUE)
.content(mapper.writeValueAsString(request)));
}

private RestDocumentationResultHandler getDocument(Integer identifier) {
return document(
"saveGroomingTestResult/" + identifier,
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint(), getModifiedHeader()),
requestFields(requestFieldSaveGroomingTestResult),
responseFields(responseFieldSaveGroomingTestResult),
resource(
ResourceSnippetParameters.builder()
.tag("그루밍 테스트")
.summary("그루밍 테스트 결과 저장 api")
.description("그루밍 테스트 결과 저장 api 입니다.")
.responseFields(responseFieldSaveGroomingTestResult)
.build()));
}

@BeforeEach
void setUp() {
GeneralUserCreationCommand command =
new GeneralUserCreationCommand(
"test@gmail.com",
securityAuthenticationPort.passwordEncode("test1234!"),
"test",
null,
null);
User testUser = saveUserPort.saveUser(User.createGeneralUser(command));
saveUserImagePort.saveUserDefaultImage(UserImage.createUserImage(testUser.getId()));
}

@Test
@Transactional
void 그루밍_테스트_결과_저장_성공() throws Exception {
// given
User user =
loadUserForAuthPort.loadUserByEmail(FindByEmailQuery.of("test@gmail.com")).get();
List<SaveGroomingTestResultRequest.GroomingTestResult> results =
List.of(
new SaveGroomingTestResultRequest.GroomingTestResult(
1L, List.of(1L, 2L, 3L)));
SaveGroomingTestResultRequest request =
new SaveGroomingTestResultRequest(user.getId(), 1L, 10, results);

// when
ResultActions resultActions = getResultActions(request);

// then
resultActions.andExpect(status().isCreated()).andDo(print());

// documentation
resultActions.andDo(getDocument(1));
}

@Test
@Transactional
void 그루밍_테스트_결과_저장_실패1() throws Exception {
// given
List<SaveGroomingTestResultRequest.GroomingTestResult> results = new ArrayList<>();
SaveGroomingTestResultRequest request =
new SaveGroomingTestResultRequest(100L, 1L, 10, results);

// when
ResultActions resultActions = getResultActions(request);

// then
resultActions
.andExpect(status().is(ErrorResponseCode.USER_NOT_FOUND.getHttpStatus().value()))
.andExpect(jsonPath("code").value(ErrorResponseCode.USER_NOT_FOUND.getCode()))
.andDo(print());

// documentation
resultActions.andDo(getDocument(2));
}

@Test
@Transactional
void 그루밍_테스트_결과_저장_실패2() throws Exception {
// given
User user =
loadUserForAuthPort.loadUserByEmail(FindByEmailQuery.of("test@gmail.com")).get();
List<SaveGroomingTestResultRequest.GroomingTestResult> results = new ArrayList<>();
SaveGroomingTestResultRequest request =
new SaveGroomingTestResultRequest(user.getId(), 100L, 10, results);

// when
ResultActions resultActions = getResultActions(request);

// then
resultActions
.andExpect(
status().is(
ErrorResponseCode.GROOMING_LEVEL_NOT_FOUND
.getHttpStatus()
.value()))
.andExpect(
jsonPath("code")
.value(ErrorResponseCode.GROOMING_LEVEL_NOT_FOUND.getCode()))
.andDo(print());

// documentation
resultActions.andDo(getDocument(3));
}
}
Loading