Skip to content

Commit

Permalink
Merge pull request #6 from dnd-side-project/feat/#5
Browse files Browse the repository at this point in the history
[FEAT-5] 지도 전체조회 기능 추가
  • Loading branch information
youngreal authored Jul 31, 2024
2 parents 9ede20e + 971c144 commit af6bd6e
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 0 deletions.
21 changes: 21 additions & 0 deletions src/main/java/com/dnd/dndtravel/map/controller/MapController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.dnd.dndtravel.map.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.dnd.dndtravel.map.service.MapService;
import com.dnd.dndtravel.map.service.dto.response.RegionResponse;

import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
public class MapController {

private final MapService mapService;

@GetMapping("/maps")
public RegionResponse map() {
return mapService.allRegions();
}
}
39 changes: 39 additions & 0 deletions src/main/java/com/dnd/dndtravel/map/domain/Region.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.dnd.dndtravel.map.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Region {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name; // 지역 이름

@Enumerated(EnumType.STRING)
private VisitOpacity visitOpacity; // 방문 횟수(색의 opacity)

public static Region of(String name, VisitOpacity visitOpacity) {
return new Region(name, visitOpacity);
}

public boolean isVisited() {
return visitOpacity.isNotZero();
}

private Region(String name, VisitOpacity visitOpacity) {
this.name = name;
this.visitOpacity = visitOpacity;
}
}
19 changes: 19 additions & 0 deletions src/main/java/com/dnd/dndtravel/map/domain/VisitOpacity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.dnd.dndtravel.map.domain;

public enum VisitOpacity {
ZERO(0), ONE(1), TWO(2), THREE(3);

private final int value;

VisitOpacity(int value) {
this.value = value;
}

public boolean isNotZero() {
return this != ZERO;
}

public int toInt() {
return this.value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.dnd.dndtravel.map.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.dnd.dndtravel.map.domain.Region;

public interface MapRepository extends JpaRepository<Region, Long> {
}
34 changes: 34 additions & 0 deletions src/main/java/com/dnd/dndtravel/map/service/MapService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.dnd.dndtravel.map.service;

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.dnd.dndtravel.map.domain.Region;
import com.dnd.dndtravel.map.repository.MapRepository;
import com.dnd.dndtravel.map.service.dto.RegionDto;
import com.dnd.dndtravel.map.service.dto.response.RegionResponse;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class MapService {

private final MapRepository mapRepository;

@Transactional(readOnly = true)
public RegionResponse allRegions() {
List<Region> all = mapRepository.findAll();

List<RegionDto> regions = all.stream()
.map(RegionDto::from)
.toList();
int visitCount = (int)all.stream()
.filter(Region::isVisited)
.count();

return new RegionResponse(regions, visitCount);
}
}
13 changes: 13 additions & 0 deletions src/main/java/com/dnd/dndtravel/map/service/dto/RegionDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.dnd.dndtravel.map.service.dto;

import com.dnd.dndtravel.map.domain.Region;

public record RegionDto(
String name,
int opacity
) {

public static RegionDto from(Region region) {
return new RegionDto(region.getName(), region.getVisitOpacity().toInt());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.dnd.dndtravel.map.service.dto.response;

import java.util.List;

import com.dnd.dndtravel.map.service.dto.RegionDto;

public record RegionResponse(
List<RegionDto> regions,
int visitCount,
int totalCount
) {

private static final int TOTAL_COUNT = 16; // 전체 지역구의 개수, 변경가능성이 낮아 16이라는 상수로 고정

public RegionResponse(List<RegionDto> regions, int visitCount) {
this(regions, visitCount, TOTAL_COUNT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.dnd.dndtravel.map.controller;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
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 com.dnd.dndtravel.map.service.MapService;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
@WebMvcTest(MapController.class)
class MapControllerTest {

@MockBean
private MapService mapService;

@Autowired
private MockMvc mockMvc;

@Test
void 전체_지역정보를_조회한다() throws Exception {
// when & then
mockMvc.perform(get("/map/all"))
.andExpect(status().isOk());
}

}
75 changes: 75 additions & 0 deletions src/test/java/com/dnd/dndtravel/map/service/MapServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.dnd.dndtravel.map.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;

import java.util.List;
import java.util.stream.Stream;

import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import com.dnd.dndtravel.map.domain.VisitOpacity;
import com.dnd.dndtravel.map.domain.Region;
import com.dnd.dndtravel.map.repository.MapRepository;
import com.dnd.dndtravel.map.service.dto.response.RegionResponse;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
@ExtendWith(MockitoExtension.class)
class MapServiceTest {

@InjectMocks
private MapService sut;

@Mock
private MapRepository mapRepository;

@ParameterizedTest
@MethodSource("provideRegionsForTesting")
void 전체_지역정보를_조회한다(List<Region> regions, int expectedVisitCount, int expectedRegionCount) {
// given
given(mapRepository.findAll()).willReturn(regions);

// when
RegionResponse actual = sut.allRegions();

// then
assertThat(actual.regions().size()).isEqualTo(expectedRegionCount);
assertThat(actual.visitCount()).isEqualTo(expectedVisitCount);
}

private static Stream<Arguments> provideRegionsForTesting() {
return Stream.of(
// 빈 리스트
Arguments.of(List.of(), 0, 0),

// 모든 지역이 방문되지 않은 경우
Arguments.of(List.of(
Region.of("서울특별시", VisitOpacity.ZERO),
Region.of("부산", VisitOpacity.ZERO),
Region.of("충청도", VisitOpacity.ZERO)
), 0, 3),

// 모든 지역이 방문된 경우
Arguments.of(List.of(
Region.of("서울특별시", VisitOpacity.ONE),
Region.of("부산", VisitOpacity.ONE),
Region.of("충청도", VisitOpacity.ONE)
), 3, 3),

// 일부 지역만 방문된 경우
Arguments.of(List.of(
Region.of("서울특별시", VisitOpacity.ONE),
Region.of("부산", VisitOpacity.ZERO),
Region.of("충청도", VisitOpacity.ONE)
), 2, 3)
);
}
}

0 comments on commit af6bd6e

Please sign in to comment.