Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor : 약속 개설 API 구현 #250

Merged
merged 10 commits into from
Aug 4, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@
public interface MateControllerSwagger {

@Operation(
summary = "모임 참여",
summary = "약속 참여",
requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = MateSaveRequest.class))),
responses = {
@ApiResponse(
responseCode = "201",
description = "모임 참여 성공",
description = "약속 참여 성공",
content = @Content(schema = @Schema(implementation = MeetingSaveResponse.class))
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,11 @@ public ResponseEntity<MeetingSaveResponseV1> saveV1(
@AuthMember Member member,
@Valid @RequestBody MeetingSaveRequestV1 meetingSaveRequestV1
) {
MeetingSaveResponseV1 meetingSaveResponseV1 = new MeetingSaveResponseV1(
1L,
"우테코 16조",
LocalDate.parse("2024-07-15"),
LocalTime.parse("14:00"),
"서울 송파구 올림픽로 35다길 42",
"37.515298",
"127.103113",
"초대코드"
);

MeetingSaveResponseV1 meetingSaveResponseV1 = meetingService.saveV1(meetingSaveRequestV1);
return ResponseEntity.status(HttpStatus.CREATED)
.body(meetingSaveResponseV1);
}

@GetMapping("/v1/meetings/{meetingId}")
public ResponseEntity<MeetingWithMatesResponse> findMeetingWithMates(
@AuthMember Member member,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.ody.notification.dto.response.NotiLogFindResponses;
import com.ody.swagger.annotation.ErrorCode400;
import com.ody.swagger.annotation.ErrorCode401;
import com.ody.swagger.annotation.ErrorCode404;
import com.ody.swagger.annotation.ErrorCode500;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand All @@ -29,6 +30,7 @@
public interface MeetingControllerSwagger {

@Operation(
deprecated = true,
summary = "약속 개설",
requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = MeetingSaveRequest.class))),
responses = {
Expand Down Expand Up @@ -103,20 +105,15 @@ ResponseEntity<MeetingSaveResponse> save(
summary = "초대코드 유효성 검사",
responses = {
@ApiResponse(responseCode = "200", description = "유효한 초대 코드"),
@ApiResponse(
responseCode = "404",
description = "유효하지 않은 초대코드",
content = @Content(schema = @Schema(implementation = ProblemDetail.class))
)
}
)
@ErrorCode400
@ErrorCode401
@ErrorCode404(description = "유효하지 않은 초대코드")
@ErrorCode500
ResponseEntity<Void> validateInviteCode(@Parameter(hidden = true) Member member, String inviteCode);

@Operation(
deprecated = true,
summary = "약속 개설",
requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = MeetingSaveRequestV1.class))),
responses = {
Expand Down Expand Up @@ -148,15 +145,11 @@ ResponseEntity<MeetingSaveResponseV1> saveV1(
responseCode = "400",
description = "클라이언트 입력 오류 또는 약속 시간 30분 전보다 이른 시간에 조회 시도 시",
content = @Content(schema = @Schema(implementation = ProblemDetail.class))
),
@ApiResponse(
responseCode = "404",
description = "존재하지 않는 모임이거나 해당 모임 참여자가 아닌 경우",
content = @Content(schema = @Schema(implementation = ProblemDetail.class))
)
}
)
@ErrorCode401
@ErrorCode404(description = "존재하지 않는 약속이거나 해당 약속 참여자가 아닌 경우")
@ErrorCode500
ResponseEntity<MateEtaResponses> findAllMateEtas(
@Parameter(hidden = true) Member member,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.ody.meeting.dto.request;

import com.ody.common.annotation.FutureOrPresentDateTime;
import com.ody.meeting.domain.Location;
import com.ody.meeting.domain.Meeting;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import java.time.LocalDate;
Expand Down Expand Up @@ -29,4 +31,8 @@ public record MeetingSaveRequestV1(
String targetLongitude
) {

public Meeting toMeeting(String inviteCode) {
Location target = new Location(targetAddress, targetLatitude, targetLongitude);
return new Meeting(name, date, time, target, inviteCode);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ody.meeting.dto.response;

import com.ody.meeting.domain.Meeting;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDate;
import java.time.LocalTime;
Expand Down Expand Up @@ -31,4 +32,16 @@ public record MeetingSaveResponseV1(
String inviteCode
) {

public static MeetingSaveResponseV1 from(Meeting meeting) {
return new MeetingSaveResponseV1(
meeting.getId(),
meeting.getName(),
meeting.getDate(),
meeting.getTime().withNano(0),
meeting.getTarget().getAddress(),
meeting.getTarget().getLatitude(),
meeting.getTarget().getLongitude(),
meeting.getInviteCode()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.ody.meeting.domain.Meeting;
import com.ody.member.domain.Member;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

Expand Down
10 changes: 10 additions & 0 deletions backend/src/main/java/com/ody/meeting/service/MeetingService.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
import com.ody.meeting.domain.Meeting;
import com.ody.meeting.dto.response.MateResponse;
import com.ody.meeting.dto.request.MeetingSaveRequest;
import com.ody.meeting.dto.request.MeetingSaveRequestV1;
import com.ody.meeting.dto.response.MeetingSaveResponse;
import com.ody.meeting.dto.response.MeetingSaveResponses;
import com.ody.meeting.dto.response.MeetingSaveResponseV1;
import com.ody.meeting.dto.response.MeetingWithMatesResponse;
import com.ody.meeting.repository.MeetingRepository;
import com.ody.member.domain.Member;
Expand Down Expand Up @@ -41,6 +43,14 @@ public MeetingSaveResponse saveAndSendNotifications(MeetingSaveRequest meetingSa
return mateService.saveAndSendNotifications(mateSaveRequest, meeting, member);
}

@Transactional
public MeetingSaveResponseV1 saveV1(MeetingSaveRequestV1 meetingSaveRequestV1) {
Meeting meeting = meetingRepository.save(meetingSaveRequestV1.toMeeting(DEFAULT_INVITE_CODE));
String encodedInviteCode = InviteCodeGenerator.encode(meeting.getId());
meeting.updateInviteCode(encodedInviteCode);
return MeetingSaveResponseV1.from(meeting);
}

public Meeting save(MeetingSaveRequest meetingSaveRequest) {
Meeting meeting = meetingRepository.save(meetingSaveRequest.toMeeting(DEFAULT_INVITE_CODE));
String encodedInviteCode = InviteCodeGenerator.encode(meeting.getId());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.ody.notification.repository;

import com.ody.notification.domain.Notification;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static long mapMinutes(OdsayResponse response) {
return ZERO_TIME;
}

if(response.code().isPresent()) {
if (response.code().isPresent()) {
checkOdsayException(response);
}

Expand All @@ -49,7 +49,7 @@ private static void checkOdsayException(OdsayResponse response) {

throw new OdyBadRequestException(
response.message()
.orElse(EMPTY_MESSAGE)
.orElse(EMPTY_MESSAGE)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ private OdsayResponse getOdsayResponse(Location origin, Location target) {
.uri(makeURI(origin, target))
.retrieve()
.body(OdsayResponse.class);
return Objects.requireNonNullElseGet(response, () -> {throw new OdyServerErrorException("서버 에러");});
return Objects.requireNonNullElseGet(response, () -> {
throw new OdyServerErrorException("서버 에러");
}
);
}

private URI makeURI(Location origin, Location target) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.ody.meeting.domain.Location;
import com.ody.meeting.domain.Meeting;
import com.ody.meeting.dto.request.MeetingSaveRequest;
import com.ody.meeting.dto.request.MeetingSaveRequestV1;
import com.ody.meeting.repository.MeetingRepository;
import com.ody.member.domain.DeviceToken;
import com.ody.member.domain.Member;
Expand Down Expand Up @@ -63,7 +64,32 @@ void save() {
.when()
.post("/meetings")
.then()
.statusCode(HttpStatus.CREATED.value());
.statusCode(201);
}

@DisplayName("모임 개설 성공 시, 201을 응답한다")
@Test
void saveV1() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v1 테스트까지 👍🏻
제가 까먹어버렸네요 바로 참여 V1 API 테스트 추가 하러 갑니다

String deviceToken = "Bearer device-token=testToken";
memberService.save(new DeviceToken(deviceToken));

MeetingSaveRequestV1 meetingRequest = new MeetingSaveRequestV1(
"우테코 16조",
LocalDate.now().plusDays(1),
LocalTime.now().plusHours(1),
"서울 송파구 올림픽로35다길 42",
"37.515298",
"127.103113"
);

RestAssured.given().log().all()
.contentType(ContentType.JSON)
.header(HttpHeaders.AUTHORIZATION, deviceToken)
.body(meetingRequest)
.when()
.post("/v1/meetings")
.then()
.statusCode(201);
}

@DisplayName("특정 멤버의 참여 모임 목록 조회에 성공하면 200응답 반환한다")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import com.ody.member.domain.Member;
import com.ody.member.repository.MemberRepository;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.ody.meeting.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;

import com.ody.common.BaseServiceTest;
import com.ody.common.Fixture;
import com.ody.meeting.domain.Meeting;
import com.ody.meeting.dto.request.MeetingSaveRequestV1;
import com.ody.meeting.dto.response.MeetingSaveResponseV1;
import com.ody.util.InviteCodeGenerator;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

class MeetingServiceTest extends BaseServiceTest {

@Autowired
MeetingService meetingService;

@DisplayName("약속 저장 및 초대 코드 갱신에 성공한다")
@Test
void saveV1Success() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[질문]
저장 시에 초대 코드가 인코딩된 코드로 갱신됐는지 확인하는 테스트는 의미가 있겠네요 👍🏻

단순한 궁금증인데 콜리는 "약속 저장 및 초대 코드 갱신에 성공"했다는 것을 검증하기 위해
어디까지 검증을 하는 것을 선호하시나요 ??
저는 개인적으로 모든 필드를 확인하면 필드별로 assertThat()을 작성해주는 리소스 발생과 스펙이 변경됐을 때 테스트 코드의 유지보수도 발생한다 생각해
위 상황이라면 response.id()와 디코딩된 초대코드가 일치한지 확인하는 것만으로도 충분한 검증이 되었다고 생각하는 편입니다
물론 모든 필드에 대한 검증도 장점이 있다고 생각해 단순히 콜리의 생각을 듣고 싶어요 😄

Copy link
Contributor Author

@coli-geonwoo coli-geonwoo Aug 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

약속 저장 및 초대 코드 갱신에 성공"했다는 것을 검증하기 위해 어디까지 검증을 하는 것을 선호하시나요 ??

마침 고민하고 있던 부분인데 카키가 좋은 화두를 던져줘서 생각을 정리할 계기가 된 것 같아요.
저는 테스트에서 주로 객체의 논리적 동등성(같다고 볼 수 있는 기준)을 검증하는 것 같아요.
그 관점에서 id가 같아도 모든 필드가 같지 않으면 같은 객체가 아니라 생각했습니다.

특히 save method에는 객체 저장이후에 update문이 들어가기 때문에
단순 id 검증만으로 논리적 동등성을 판단하기 힘든 부분이 있다고 생각했던 같아요!

만약 update문이 없었다면 저도 카키처럼 id 비교만으로 충분하다 생각합니다!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

콜리 의견에 동의해요. 테스트 유지보수가 힘들지만
모든 필드를 검증하지 않으면, 버그가 있을 때 찾기 어려운 것 같아요.

제 경험인데, 테스트 코드에서는 다 pass 돼서 문제가 없다고 생각했는데
실제 부트를 실행해서 확인해 봤을 때 에러가 있었고, H2 콘솔에서 데이터를 직접 확인해보고 버그 픽스를 할 수 있었어요.
당시 잘못된 값이 필드로 들어가고 있었거든요.

Meeting testMeeting = Fixture.ODY_MEETING1;
MeetingSaveRequestV1 request = new MeetingSaveRequestV1(
testMeeting.getName(),
testMeeting.getDate(),
testMeeting.getTime(),
testMeeting.getTarget().getAddress(),
testMeeting.getTarget().getLatitude(),
testMeeting.getTarget().getLongitude()
);

MeetingSaveResponseV1 response = meetingService.saveV1(request);

assertAll(
() -> assertThat(response.name()).isEqualTo(request.name()),
() -> assertThat(response.date()).isEqualTo(request.date()),
() -> assertThat(response.time()).isEqualTo(request.time()),
() -> assertThat(response.targetAddress()).isEqualTo(request.targetAddress()),
() -> assertThat(response.targetLatitude()).isEqualTo(request.targetLatitude()),
() -> assertThat(response.targetLongitude()).isEqualTo(request.targetLongitude()),
() -> assertThat(InviteCodeGenerator.decode(response.inviteCode())).isEqualTo(response.id())
);
}
}
Loading