Skip to content
Open
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
@@ -0,0 +1,29 @@
package com.festivalapp.api;

import com.festivalapp.dto.PerformanceResponse;
import com.festivalapp.service.PerformanceService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/performances")
@RequiredArgsConstructor
public class PerformanceController {

private final PerformanceService performanceService;

@GetMapping
ResponseEntity<List<PerformanceResponse>> getPerformances() {
return ResponseEntity.ok(performanceService.getPerformances());
}

@GetMapping("/{performanceId}")
ResponseEntity<PerformanceResponse> getPerformance(@PathVariable Long performanceId) {
return ResponseEntity.ok(performanceService.getPerformance(performanceId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
"/api/booths/**",
"/api/timeline",
"/api/timeline/**",
"/api/performances",
"/api/performances/**",
"/api/map-locations",
"/api/map-locations/**")
.permitAll()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.festivalapp.domain.performance;

import java.time.LocalDateTime;

public record Performance(
Long id,
String title,
String artist,
LocalDateTime startsAt,
LocalDateTime endsAt,
String location,
String description,
int totalSeats,
int reservedSeats) {

public int remainingSeats() {
return Math.max(totalSeats - reservedSeats, 0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.festivalapp.dto;

import com.festivalapp.domain.performance.Performance;
import java.time.LocalDateTime;

public record PerformanceResponse(
Long id,
String title,
String artist,
LocalDateTime startsAt,
LocalDateTime endsAt,
String location,
String description,
int totalSeats,
int reservedSeats,
int remainingSeats) {

public static PerformanceResponse from(Performance performance) {
return new PerformanceResponse(
performance.id(),
performance.title(),
performance.artist(),
performance.startsAt(),
performance.endsAt(),
performance.location(),
performance.description(),
performance.totalSeats(),
performance.reservedSeats(),
performance.remainingSeats());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.festivalapp.repository.performance;

import com.festivalapp.domain.performance.Performance;
import com.festivalapp.repository.performance.datasource.PerformanceDataSource;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class PerformanceRepository {

private final PerformanceDataSource performanceDataSource;

public List<Performance> findAll() {
return performanceDataSource.findAll().stream()
.sorted(Comparator.comparing(Performance::startsAt))
.toList();
}

public Optional<Performance> findById(Long performanceId) {
return performanceDataSource.findAll().stream()
.filter(performance -> performance.id().equals(performanceId))
.findFirst();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.festivalapp.repository.performance.datasource;

import com.festivalapp.domain.performance.Performance;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.stereotype.Component;

@Component
public class InMemoryPerformanceDataSource implements PerformanceDataSource {

private final List<Performance> performances =
List.of(
performance(
1L,
"와우 스테이지 헤드라이너",
"헤드라이너 아티스트",
2026,
5,
13,
19,
0,
21,
0,
"대운동장 메인 스테이지",
"축제 첫날 밤을 여는 메인 스테이지 공연입니다.",
500,
372),
performance(
2L,
"동아리 밴드 쇼케이스",
"학생 밴드 연합",
2026,
5,
14,
18,
0,
19,
30,
"학생회관 야외무대",
"교내 밴드 동아리들이 준비한 라이브 쇼케이스입니다.",
220,
156),
performance(
3L,
"WOW DJ Festival",
"WOW DJ Crew",
2026,
5,
15,
20,
0,
23,
30,
"운동장 DJ 스테이지",
"축제 마지막 밤을 채우는 DJ 페스티벌 공연입니다.",
800,
615));

@Override
public List<Performance> findAll() {
return performances;
}

private static Performance performance(
Long id,
String title,
String artist,
int year,
int month,
int day,
int startHour,
int startMinute,
int endHour,
int endMinute,
String location,
String description,
int totalSeats,
int reservedSeats) {
return new Performance(
id,
title,
artist,
LocalDateTime.of(year, month, day, startHour, startMinute),
LocalDateTime.of(year, month, day, endHour, endMinute),
location,
description,
totalSeats,
reservedSeats);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.festivalapp.repository.performance.datasource;

import com.festivalapp.domain.performance.Performance;
import java.util.List;

public interface PerformanceDataSource {

List<Performance> findAll();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.festivalapp.service;

import com.festivalapp.dto.PerformanceResponse;
import com.festivalapp.repository.performance.PerformanceRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;

@Service
@RequiredArgsConstructor
public class PerformanceService {

private final PerformanceRepository performanceRepository;

public List<PerformanceResponse> getPerformances() {
return performanceRepository.findAll().stream()
.map(PerformanceResponse::from)
.toList();
}

public PerformanceResponse getPerformance(Long performanceId) {
return performanceRepository.findById(performanceId)
.map(PerformanceResponse::from)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "공연을 찾을 수 없습니다."));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.festivalapp.api;

import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.festivalapp.dto.PerformanceResponse;
import com.festivalapp.service.PerformanceService;
import java.time.LocalDateTime;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

class PerformanceControllerTest {

private final PerformanceService performanceService = mock(PerformanceService.class);
private MockMvc mockMvc;

@BeforeEach
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

중요한 부분은 아니나, 추후 테스트코드를 작성할 때는 @BeforeAll을 이용하여 리소스 낭비를 줄이는 방식으로 해도 좋을 거 같습니다!

void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(new PerformanceController(performanceService)).build();
}

@Test
void getPerformancesReturnsPerformanceSummaries() throws Exception {
given(performanceService.getPerformances()).willReturn(List.of(performanceResponse()));

mockMvc.perform(get("/api/performances"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].title").value("와우 스테이지 헤드라이너"))
.andExpect(jsonPath("$[0].remainingSeats").value(128));
}

@Test
void getPerformanceReturnsPerformanceDetail() throws Exception {
given(performanceService.getPerformance(1L)).willReturn(performanceResponse());

mockMvc.perform(get("/api/performances/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.title").value("와우 스테이지 헤드라이너"))
.andExpect(jsonPath("$.description").value("축제 첫날 밤을 여는 메인 스테이지 공연입니다."))
.andExpect(jsonPath("$.remainingSeats").value(128));
}

private PerformanceResponse performanceResponse() {
return new PerformanceResponse(
1L,
"와우 스테이지 헤드라이너",
"헤드라이너 아티스트",
LocalDateTime.of(2026, 5, 13, 19, 0),
LocalDateTime.of(2026, 5, 13, 21, 0),
"대운동장 메인 스테이지",
"축제 첫날 밤을 여는 메인 스테이지 공연입니다.",
500,
372,
128);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.festivalapp.repository.performance;

import static org.assertj.core.api.Assertions.assertThat;

import com.festivalapp.domain.performance.Performance;
import java.time.LocalDateTime;
import java.util.List;
import org.junit.jupiter.api.Test;

class PerformanceRepositoryTest {

@Test
void findAllReturnsPerformancesSortedByStartTime() {
Performance latePerformance =
performance(2L, "늦은 공연", LocalDateTime.of(2026, 5, 14, 20, 0));
Performance earlyPerformance =
performance(1L, "이른 공연", LocalDateTime.of(2026, 5, 14, 18, 0));
PerformanceRepository performanceRepository =
new PerformanceRepository(() -> List.of(latePerformance, earlyPerformance));

List<Performance> performances = performanceRepository.findAll();

assertThat(performances).containsExactly(earlyPerformance, latePerformance);
}

@Test
void findByIdReturnsMatchingPerformance() {
Performance performance =
performance(1L, "와우 스테이지 헤드라이너", LocalDateTime.of(2026, 5, 13, 19, 0));
PerformanceRepository performanceRepository = new PerformanceRepository(() -> List.of(performance));

assertThat(performanceRepository.findById(1L)).contains(performance);
assertThat(performanceRepository.findById(999L)).isEmpty();
}

private Performance performance(Long id, String title, LocalDateTime startsAt) {
return new Performance(
id,
title,
"아티스트",
startsAt,
startsAt.plusHours(2),
"메인무대",
"공연 설명입니다.",
100,
40);
}
}
Loading