Skip to content

Commit

Permalink
feat: 보행 훈련 기록 조회 API에 정확도 평균 정보 추가 완료 (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
limehee authored Jan 2, 2025
1 parent aed5ef5 commit 648959e
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 143 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.time.LocalDate;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -30,25 +29,26 @@ public class RecordController {
@SuccessApiResponse(data = "deviceTag", dataType = String.class, dataDescription = "사용자의 디바이스 식별자")
@PreAuthorize("hasRole('USER')")
@PostMapping("/api/v1/records")
public ApiResponse<String> record(
@Valid @RequestBody RecordRequestDto requestDto
public ApiResponse<String> recordTrainingData(
@Valid @RequestBody RecordRequestDto requestDto
) {
String deviceTag = recordService.record(requestDto);
String deviceTag = recordService.recordTrainingData(requestDto);
return ApiResponse.success(deviceTag);
}

@Operation(summary = "[U] 내 보행 훈련 기록 조회", description = "ROLE_USER 이상의 권한이 필요함<br>"
+ "startDate와 endDate는 yyyy-MM-dd 형식으로 입력해야 함<br>"
+ "startDate 이전의 가장 최신 데이터와 startDate부터 endDate까지의 데이터를 가져옴<br>"
+ "데이터가 없을 경우 startDate 이전 날짜, 정확도 0으로 설정하여 반환")
+ "데이터가 없을 경우 startDate 이전 날짜, 정확도 0으로 설정하여 반환"
+ "정확도 평균은 소수점 첫째 자리에서 반올림된 값으로 반환")
@PreAuthorize("hasRole('USER')")
@GetMapping("/api/v1/records")
public ApiResponse<List<RecordResponseDto>> getRecords(
@RequestParam(name = "startDate") LocalDate startDate,
@RequestParam(name = "endDate") LocalDate endDate
public ApiResponse<RecordResponseDto> getRecords(
@RequestParam(name = "startDate") LocalDate startDate,
@RequestParam(name = "endDate") LocalDate endDate
) {
List<RecordResponseDto> records = recordService.getRecordsByDateRange(startDate, endDate);
return ApiResponse.success(records);
RecordResponseDto recordsByDateRange = recordService.getRecordsByDateRange(startDate, endDate);
return ApiResponse.success(recordsByDateRange);
}

@Operation(summary = "[U] 내 보행 훈련 기록 통계", description = "ROLE_USER 이상의 권한이 필요함")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.stempo.dto.request.RecordRequestDto;
import com.stempo.dto.response.RecordItemDto;
import com.stempo.dto.response.RecordResponseDto;
import com.stempo.dto.response.RecordStatisticsResponseDto;
import com.stempo.service.RecordService;
Expand All @@ -28,7 +29,7 @@
@WebMvcTest(controllers = RecordController.class)
@ContextConfiguration(classes = TestApplication.class)
@ActiveProfiles("test")
public class RecordControllerTest {
class RecordControllerTest {

@Autowired
private MockMvc mockMvc;
Expand All @@ -50,17 +51,17 @@ public class RecordControllerTest {

String expectedDeviceTag = "device123";

when(recordService.record(any(RecordRequestDto.class)))
.thenReturn(expectedDeviceTag);
when(recordService.recordTrainingData(any(RecordRequestDto.class)))
.thenReturn(expectedDeviceTag);

// when
mockMvc.perform(post("/api/v1/records")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestDto)))
// then
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.data").value(expectedDeviceTag));
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestDto)))
// then
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.data").value(expectedDeviceTag));
}

@Test
Expand All @@ -74,12 +75,12 @@ public class RecordControllerTest {

// when
mockMvc.perform(post("/api/v1/records")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestDto)))
// then
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.success").value(false))
.andExpect(jsonPath("$.data").isEmpty());
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestDto)))
// then
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.success").value(false))
.andExpect(jsonPath("$.data").isEmpty());
}

@Test
Expand All @@ -92,10 +93,10 @@ public class RecordControllerTest {

// when
mockMvc.perform(post("/api/v1/records")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestDto)))
// then
.andExpect(status().isUnauthorized());
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestDto)))
// then
.andExpect(status().isUnauthorized());
}

@Test
Expand All @@ -105,84 +106,91 @@ public class RecordControllerTest {
LocalDate startDate = LocalDate.of(2024, 10, 1);
LocalDate endDate = LocalDate.of(2024, 10, 31);

List<RecordResponseDto> expectedRecords = List.of(
RecordResponseDto.builder()
.accuracy(95.5)
.duration(30)
.steps(5000)
.date(LocalDate.of(2024, 10, 15))
.build(),
RecordResponseDto.builder()
.accuracy(90.0)
.duration(25)
.steps(4500)
.date(LocalDate.of(2024, 10, 20))
.build()
List<RecordItemDto> recordItems = List.of(
RecordItemDto.builder()
.accuracy(95.5)
.duration(30)
.steps(5000)
.date(LocalDate.of(2024, 10, 15))
.build(),
RecordItemDto.builder()
.accuracy(90.0)
.duration(25)
.steps(4500)
.date(LocalDate.of(2024, 10, 20))
.build()
);

int accuracyAverage = 93;

RecordResponseDto responseDto = RecordResponseDto.builder()
.accuracyAverage(accuracyAverage)
.records(recordItems)
.build();

when(recordService.getRecordsByDateRange(startDate, endDate))
.thenReturn(expectedRecords);
.thenReturn(responseDto);

// when
mockMvc.perform(get("/api/v1/records")
.param("startDate", "2024-10-01")
.param("endDate", "2024-10-31")
.contentType(MediaType.APPLICATION_JSON))
// then
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.data").isArray())
.andExpect(jsonPath("$.data[0].accuracy").value(95.5))
.andExpect(jsonPath("$.data[0].duration").value(30))
.andExpect(jsonPath("$.data[0].steps").value(5000))
.andExpect(jsonPath("$.data[0].date").value("2024-10-15"))
.andExpect(jsonPath("$.data[1].accuracy").value(90.0))
.andExpect(jsonPath("$.data[1].duration").value(25))
.andExpect(jsonPath("$.data[1].steps").value(4500))
.andExpect(jsonPath("$.data[1].date").value("2024-10-20"));
.param("startDate", "2024-10-01")
.param("endDate", "2024-10-31")
.contentType(MediaType.APPLICATION_JSON))
// then
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.data.accuracyAverage").value(93))
.andExpect(jsonPath("$.data.records[0].accuracy").value(95.5))
.andExpect(jsonPath("$.data.records[0].duration").value(30))
.andExpect(jsonPath("$.data.records[0].steps").value(5000))
.andExpect(jsonPath("$.data.records[0].date").value("2024-10-15"))
.andExpect(jsonPath("$.data.records[1].accuracy").value(90.0))
.andExpect(jsonPath("$.data.records[1].duration").value(25))
.andExpect(jsonPath("$.data.records[1].steps").value(4500))
.andExpect(jsonPath("$.data.records[1].date").value("2024-10-20"));
}

@Test
void 인증되지_않은_사용자가_보행_훈련_기록을_조회시_권한에러가_발생한다() throws Exception {
// when
mockMvc.perform(get("/api/v1/records")
.param("startDate", "2024-10-01")
.param("endDate", "2024-10-31")
.contentType(MediaType.APPLICATION_JSON))
// then
.andExpect(status().isUnauthorized());
.param("startDate", "2024-10-01")
.param("endDate", "2024-10-31")
.contentType(MediaType.APPLICATION_JSON))
// then
.andExpect(status().isUnauthorized());
}

@Test
@WithMockUser(roles = "USER")
void 정상적으로_보행_훈련_기록_통계를_조회한다() throws Exception {
// given
RecordStatisticsResponseDto expectedStatistics = RecordStatisticsResponseDto.builder()
.todayWalkTrainingCount(10)
.weeklyWalkTrainingCount(50)
.consecutiveWalkTrainingDays(5)
.build();
.todayWalkTrainingCount(10)
.weeklyWalkTrainingCount(50)
.consecutiveWalkTrainingDays(5)
.build();

when(recordService.getRecordStatistics())
.thenReturn(expectedStatistics);
.thenReturn(expectedStatistics);

// when
mockMvc.perform(get("/api/v1/records/statistics")
.contentType(MediaType.APPLICATION_JSON))
// then
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.data.todayWalkTrainingCount").value(10))
.andExpect(jsonPath("$.data.weeklyWalkTrainingCount").value(50))
.andExpect(jsonPath("$.data.consecutiveWalkTrainingDays").value(5));
.contentType(MediaType.APPLICATION_JSON))
// then
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.data.todayWalkTrainingCount").value(10))
.andExpect(jsonPath("$.data.weeklyWalkTrainingCount").value(50))
.andExpect(jsonPath("$.data.consecutiveWalkTrainingDays").value(5));
}

@Test
void 인증되지_않은_사용자가_보행_훈련_기록_통계를_조회시_권한에러가_발생한다() throws Exception {
// when
mockMvc.perform(get("/api/v1/records/statistics")
.contentType(MediaType.APPLICATION_JSON))
// then
.andExpect(status().isUnauthorized());
.contentType(MediaType.APPLICATION_JSON))
// then
.andExpect(status().isUnauthorized());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.stempo.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDate;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class RecordItemDto {

@Schema(description = "정확도", example = "0.0")
private Double accuracy;

@Schema(description = "재활 운동 시간(초)", example = "0")
private Integer duration;

@Schema(description = "걸음 수", example = "0")
private Integer steps;

@Schema(description = "날짜", example = "2024-01-01")
private LocalDate date;
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
package com.stempo.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDate;
import java.util.List;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class RecordResponseDto {

@Schema(description = "정확도", example = "0.0")
private Double accuracy;
@Schema(description = "정확도 평균", example = "0")
private Integer accuracyAverage;

@Schema(description = "재활 운동 시간(초)", example = "0")
private Integer duration;

@Schema(description = "걸음 수", example = "0")
private Integer steps;

@Schema(description = "날짜", example = "2024-01-01")
private LocalDate date;
@Schema(description = "보행 훈련 기록", example = """
[
{
"accuracy": 0.0,
"duration": 0,
"steps": 0,
"date": "2025-01-01"
}
]
""")
private List<RecordItemDto> records;
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
package com.stempo.mapper;

import com.stempo.dto.response.RecordItemDto;
import com.stempo.dto.response.RecordResponseDto;
import com.stempo.dto.response.RecordStatisticsResponseDto;
import java.time.LocalDate;
import java.util.List;
import org.springframework.stereotype.Component;

@Component
public class RecordDtoMapper {

public RecordResponseDto toDto(Double accuracy, Integer duration, Integer steps, LocalDate date) {
public RecordResponseDto toDto(int accuracyAverage, List<RecordItemDto> records) {
return RecordResponseDto.builder()
.accuracy(accuracy)
.duration(duration)
.steps(steps)
.date(date)
.build();
.accuracyAverage(accuracyAverage)
.records(records)
.build();
}

public RecordItemDto toDto(Double accuracy, Integer duration, Integer steps, LocalDate date) {
return RecordItemDto.builder()
.accuracy(accuracy)
.duration(duration)
.steps(steps)
.date(date)
.build();
}

public RecordStatisticsResponseDto toDto(int todayWalkTrainingCount, int weeklyWalkTrainingCount,
int consecutiveWalkTrainingDays) {
int consecutiveWalkTrainingDays) {
return RecordStatisticsResponseDto.builder()
.todayWalkTrainingCount(todayWalkTrainingCount)
.weeklyWalkTrainingCount(weeklyWalkTrainingCount)
.consecutiveWalkTrainingDays(consecutiveWalkTrainingDays)
.build();
.todayWalkTrainingCount(todayWalkTrainingCount)
.weeklyWalkTrainingCount(weeklyWalkTrainingCount)
.consecutiveWalkTrainingDays(consecutiveWalkTrainingDays)
.build();
}
}
Loading

0 comments on commit 648959e

Please sign in to comment.