diff --git a/README.md b/README.md
index 4bd7a0b20..856c9024b 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,73 @@
-# 2023-festa-go
+# 페스타고, 대학 축제를 더욱 즐겁게!
+> 대학 축제 줄서기 및 축제 정보 제공 서비스 "페스타고"
+
+![](https://github.com/woowacourse-teams/2023-festa-go/assets/71129059/55f0d73b-c032-4c15-9cdf-40a560af948f)
+
+다들 즐거워 하는 대학 축제이지만, 한 가지 걱정되는 점이 있습니다. 바로 ‘줄 서기’입니다. 티켓팅과 예매를 위한 지루한 줄서기 과정은 축제의 재미를 반감시키는 요인입니다.
+
+페스타고는 이러한 문제를 해결하여, 우리 모두가 대학 축제를 더 편리하게 즐기기 위해 만들어졌습니다.
+
+페스타고를 통해 티켓을 예매하기 위해 불편한 줄 서기 과정을 거칠 필요 없이 온라인으로 티켓을 예매하고, 복잡한 절차 없이 스마트폰의 QR 코드만으로 입장을 할 수 있습니다.
+
+
+
+
+
+
+**▷ 📲 다운로드 |** [PlayStore](https://play.google.com/store/apps/details?id=com.festago.festago)
+
+**▷ 📝 팀블로그 |** [Festago 팀블로그](https://festago.github.io/)
+
+**▷ 📧 연락처 |** team.festago@gmail.com
+
+## Android
+
+
프로젝트 아키텍처
+
+
+
+
+
기술 스택
+
+
+
+## Backend
+
+
백엔드 인프라 아키텍처
+
+
+
+
+
+
기술 스택
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## 🎉 축제 스태프를 소개합니다
+
+|BackEnd|BackEnd|BackEnd|BackEnd|Android|Android|Android|
+|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
+|![](https://avatars.githubusercontent.com/u/103228463?v=4&size=100)|![](https://avatars.githubusercontent.com/u/116627736?v=4&size=100)|![](https://avatars.githubusercontent.com/u/71129059?v=4&size=100)|![](https://avatars.githubusercontent.com/u/100915276?v=4&size=100)|![](https://avatars.githubusercontent.com/u/108349655?v=4&size=100)|![](https://avatars.githubusercontent.com/u/67777523?v=4&size=100)|![](https://avatars.githubusercontent.com/u/37167652?v=4&size=100)|
+|[푸우](https://github.com/BGuga)|[글렌](https://github.com/seokjin8678)|[애쉬](https://github.com/xxeol2)|[오리](https://github.com/carsago)|[베르](https://github.com/SeongHoonC)|[해시](https://github.com/EmilyCh0)|[아크](https://github.com/re4rk)|
+
+
+
+## ⛔️ 공연 관람시 주의사항
+> 페스타고 팀의 그라운드 룰을 소개합니다.
+
+
diff --git a/android/festago/app/build.gradle.kts b/android/festago/app/build.gradle.kts
index c92b8aae2..2c171794c 100644
--- a/android/festago/app/build.gradle.kts
+++ b/android/festago/app/build.gradle.kts
@@ -20,8 +20,8 @@ android {
applicationId = "com.festago.festago"
minSdk = 28
targetSdk = 34
- versionCode = 3
- versionName = "1.0.1"
+ versionCode = 7
+ versionName = "1.2.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/repository/FestivalDefaultRepository.kt b/android/festago/app/src/main/java/com/festago/festago/data/repository/FestivalDefaultRepository.kt
index 12326dd03..d9d999223 100644
--- a/android/festago/app/src/main/java/com/festago/festago/data/repository/FestivalDefaultRepository.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/data/repository/FestivalDefaultRepository.kt
@@ -4,6 +4,7 @@ import com.festago.festago.data.service.FestivalRetrofitService
import com.festago.festago.data.util.onSuccessOrCatch
import com.festago.festago.data.util.runCatchingResponse
import com.festago.festago.model.Festival
+import com.festago.festago.model.FestivalFilter
import com.festago.festago.model.Reservation
import com.festago.festago.repository.FestivalRepository
import javax.inject.Inject
@@ -11,9 +12,10 @@ import javax.inject.Inject
class FestivalDefaultRepository @Inject constructor(
private val festivalRetrofitService: FestivalRetrofitService,
) : FestivalRepository {
- override suspend fun loadFestivals(): Result> =
- runCatchingResponse { festivalRetrofitService.getFestivals() }
- .onSuccessOrCatch { it.toDomain() }
+ override suspend fun loadFestivals(festivalFilter: FestivalFilter): Result> =
+ runCatchingResponse {
+ festivalRetrofitService.getFestivals(festivalFilter.name)
+ }.onSuccessOrCatch { it.toDomain() }
override suspend fun loadFestivalDetail(festivalId: Long): Result =
runCatchingResponse { festivalRetrofitService.getFestivalDetail(festivalId) }
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/repository/TicketDefaultRepository.kt b/android/festago/app/src/main/java/com/festago/festago/data/repository/TicketDefaultRepository.kt
index 33ac652e5..43d9197aa 100644
--- a/android/festago/app/src/main/java/com/festago/festago/data/repository/TicketDefaultRepository.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/data/repository/TicketDefaultRepository.kt
@@ -4,6 +4,7 @@ import com.festago.festago.data.dto.ReservedTicketRequest
import com.festago.festago.data.service.TicketRetrofitService
import com.festago.festago.data.util.onSuccessOrCatch
import com.festago.festago.data.util.runCatchingResponse
+import com.festago.festago.model.ErrorCode
import com.festago.festago.model.ReservedTicket
import com.festago.festago.model.Ticket
import com.festago.festago.model.TicketCode
@@ -31,9 +32,16 @@ class TicketDefaultRepository @Inject constructor(
.onSuccessOrCatch { it.toDomain() }
override suspend fun reserveTicket(ticketId: Int): Result =
- runCatchingResponse {
- ticketRetrofitService.postReserveTicket(
- ReservedTicketRequest(ticketId),
- )
- }.onSuccessOrCatch { it.toDomain() }
+ runCatchingResponse { ticketRetrofitService.postReserveTicket(ReservedTicketRequest(ticketId)) }
+ .onSuccessOrCatch { it.toDomain() }
+ .onFailure { throwable ->
+ val message = throwable.message ?: "ERROR_UNKNOWN"
+ val error: Throwable = when {
+ "NEED_STUDENT_VERIFICATION" in message -> ErrorCode.NEED_STUDENT_VERIFICATION()
+ "RESERVE_TICKET_OVER_AMOUNT" in message -> ErrorCode.RESERVE_TICKET_OVER_AMOUNT()
+ "TICKET_SOLD_OUT" in message -> ErrorCode.TICKET_SOLD_OUT()
+ else -> throwable
+ }
+ return Result.failure(error)
+ }
}
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/service/FestivalRetrofitService.kt b/android/festago/app/src/main/java/com/festago/festago/data/service/FestivalRetrofitService.kt
index 0626fb95c..551442889 100644
--- a/android/festago/app/src/main/java/com/festago/festago/data/service/FestivalRetrofitService.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/data/service/FestivalRetrofitService.kt
@@ -5,10 +5,13 @@ import com.festago.festago.data.dto.ReservationFestivalResponse
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Path
+import retrofit2.http.Query
interface FestivalRetrofitService {
@GET("/festivals")
- suspend fun getFestivals(): Response
+ suspend fun getFestivals(
+ @Query("festivalFilter") festivalFilter: String,
+ ): Response
@GET("/festivals/{festivalId}")
suspend fun getFestivalDetail(
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeActivity.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeActivity.kt
index 9b7fe0a8a..d4932ed32 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeActivity.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeActivity.kt
@@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
+import androidx.activity.addCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
@@ -37,6 +38,7 @@ class HomeActivity : AppCompatActivity() {
initView()
initObserve()
initResultLauncher()
+ initBackPressedDispatcher()
}
private fun initResultLauncher() {
@@ -129,6 +131,22 @@ class HomeActivity : AppCompatActivity() {
resultLauncher.launch(SignInActivity.getIntent(this))
}
+ private fun initBackPressedDispatcher() {
+ var backPressedTime = START_BACK_PRESSED_TIME
+ onBackPressedDispatcher.addCallback {
+ if ((System.currentTimeMillis() - backPressedTime) > FINISH_BACK_PRESSED_TIME) {
+ backPressedTime = System.currentTimeMillis()
+ Toast.makeText(
+ this@HomeActivity,
+ getString(R.string.home_back_pressed),
+ Toast.LENGTH_SHORT,
+ ).show()
+ } else {
+ finish()
+ }
+ }
+ }
+
private inline fun changeFragment() {
val tag = T::class.java.name
val fragmentTransaction = supportFragmentManager.beginTransaction()
@@ -150,6 +168,8 @@ class HomeActivity : AppCompatActivity() {
}
companion object {
+ private const val START_BACK_PRESSED_TIME = 0L
+ private const val FINISH_BACK_PRESSED_TIME = 3000L
fun getIntent(context: Context): Intent {
return Intent(context, HomeActivity::class.java)
}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListFragment.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListFragment.kt
index 04e701e44..f4e2e8ba2 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListFragment.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListFragment.kt
@@ -9,6 +9,7 @@ import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.GridLayoutManager
import com.festago.festago.R
import com.festago.festago.databinding.FragmentFestivalListBinding
+import com.festago.festago.model.FestivalFilter
import com.festago.festago.presentation.ui.home.ticketlist.TicketListFragment
import com.festago.festago.presentation.ui.ticketreserve.TicketReserveActivity
import com.festago.festago.presentation.util.repeatOnStarted
@@ -59,6 +60,15 @@ class FestivalListFragment : Fragment(R.layout.fragment_festival_list) {
adapter = FestivalListAdapter()
binding.rvFestivalList.adapter = adapter
+ initFestivalListSpanSize()
+ initRefresh()
+ initFestivalFilters()
+ if (vm.uiState.value is FestivalListUiState.Loading) {
+ loadFestivalsBy(binding.cgFilterOption.checkedChipId)
+ }
+ }
+
+ private fun initFestivalListSpanSize() {
binding.rvFestivalList.layoutManager.apply {
if (this is GridLayoutManager) {
val spanSize = (resources.displayMetrics.widthPixels.dp / 160)
@@ -69,15 +79,29 @@ class FestivalListFragment : Fragment(R.layout.fragment_festival_list) {
}
}
}
+ }
- vm.loadFestivals()
-
+ private fun initRefresh() {
binding.srlFestivalList.setOnRefreshListener {
- vm.loadFestivals()
+ loadFestivalsBy(binding.cgFilterOption.checkedChipId)
binding.srlFestivalList.isRefreshing = false
}
}
+ private fun initFestivalFilters() {
+ binding.cgFilterOption.setOnCheckedStateChangeListener { group, _ ->
+ loadFestivalsBy(checkedChipId = group.checkedChipId)
+ }
+ }
+
+ private fun loadFestivalsBy(checkedChipId: Int) {
+ when (checkedChipId) {
+ R.id.chipProgress -> vm.loadFestivals(FestivalFilter.PROGRESS)
+ R.id.chipPlanned -> vm.loadFestivals(FestivalFilter.PLANNED)
+ R.id.chipEnd -> vm.loadFestivals(FestivalFilter.END)
+ }
+ }
+
private fun updateUi(uiState: FestivalListUiState) {
when (uiState) {
is FestivalListUiState.Loading,
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListUiState.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListUiState.kt
index eda06b3a9..8ba2e7774 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListUiState.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListUiState.kt
@@ -4,7 +4,7 @@ sealed interface FestivalListUiState {
object Loading : FestivalListUiState
data class Success(
- val festivals: List
+ val festivals: List,
) : FestivalListUiState {
val hasFestival get() = festivals.isNotEmpty()
}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModel.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModel.kt
index 78d86dc88..f69d601dd 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModel.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModel.kt
@@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.festago.festago.analytics.AnalyticsHelper
import com.festago.festago.analytics.logNetworkFailure
+import com.festago.festago.model.FestivalFilter
import com.festago.festago.presentation.ui.home.festivallist.FestivalListEvent.ShowTicketReserve
import com.festago.festago.repository.FestivalRepository
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -28,9 +29,9 @@ class FestivalListViewModel @Inject constructor(
private val _event = MutableSharedFlow()
val event: SharedFlow = _event.asSharedFlow()
- fun loadFestivals() {
+ fun loadFestivals(festivalFilter: FestivalFilter) {
viewModelScope.launch {
- festivalRepository.loadFestivals()
+ festivalRepository.loadFestivals(festivalFilter)
.onSuccess {
_uiState.value = FestivalListUiState.Success(
festivals = it.map { festival ->
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationActivity.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationActivity.kt
index 349ec05d4..54004bff5 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationActivity.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationActivity.kt
@@ -10,6 +10,7 @@ import com.festago.festago.databinding.ActivityStudentVerificationBinding
import com.festago.festago.presentation.ui.customview.OkDialogFragment
import com.festago.festago.presentation.ui.home.HomeActivity
import com.festago.festago.presentation.util.repeatOnStarted
+import com.festago.festago.presentation.util.setOnSingleClickListener
import dagger.hilt.android.AndroidEntryPoint
import java.time.LocalTime
import java.time.format.DateTimeFormatter
@@ -43,7 +44,7 @@ class StudentVerificationActivity : AppCompatActivity() {
}
private fun initRequestVerificationCodeBtn(schoolId: Long) {
- binding.btnRequestVerificationCode.setOnClickListener {
+ binding.btnRequestVerificationCode.setOnSingleClickListener {
vm.sendVerificationCode(binding.tieUserName.text.toString(), schoolId)
}
}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveActivity.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveActivity.kt
index 529e364dd..df3393b94 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveActivity.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveActivity.kt
@@ -8,6 +8,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.ConcatAdapter
import com.festago.festago.R
import com.festago.festago.databinding.ActivityTicketReserveBinding
+import com.festago.festago.model.ErrorCode
import com.festago.festago.model.ReservationTicket
import com.festago.festago.model.ReservedTicket
import com.festago.festago.presentation.ui.customview.OkDialogFragment
@@ -75,7 +76,7 @@ class TicketReserveActivity : AppCompatActivity() {
)
is ReserveTicketSuccess -> handleReserveTicketSuccess(event.reservedTicket)
- is ReserveTicketFailed -> handleReserveTicketFailed()
+ is ReserveTicketFailed -> handleReserveTicketFailed(event.errorCode)
is ShowSignIn -> handleShowSignIn()
}
@@ -113,8 +114,14 @@ class TicketReserveActivity : AppCompatActivity() {
finish()
}
- private fun handleReserveTicketFailed() {
- OkDialogFragment.newInstance("예약에 실패하였습니다.")
+ private fun handleReserveTicketFailed(errorCode: ErrorCode) {
+ val message: String = when (errorCode) {
+ is ErrorCode.TICKET_SOLD_OUT -> getString(R.string.ticket_reserve_dialog_sold_out)
+ is ErrorCode.RESERVE_TICKET_OVER_AMOUNT -> getString(R.string.ticket_reserve_dialog_over_amount)
+ is ErrorCode.NEED_STUDENT_VERIFICATION -> getString(R.string.ticket_reserve_dialog_need_student_verification)
+ is ErrorCode.UNKNOWN -> getString(R.string.ticket_reserve_dialog_unknown)
+ }
+ OkDialogFragment.newInstance(message)
.show(supportFragmentManager, OkDialogFragment::class.java.name)
}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveEvent.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveEvent.kt
index 5cc0f3cc7..3ed05c64f 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveEvent.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveEvent.kt
@@ -1,5 +1,6 @@
package com.festago.festago.presentation.ui.ticketreserve
+import com.festago.festago.model.ErrorCode
import com.festago.festago.model.ReservationTicket
import com.festago.festago.model.ReservedTicket
import java.time.LocalDateTime
@@ -11,6 +12,6 @@ sealed interface TicketReserveEvent {
) : TicketReserveEvent
class ReserveTicketSuccess(val reservedTicket: ReservedTicket) : TicketReserveEvent
- object ReserveTicketFailed : TicketReserveEvent
+ class ReserveTicketFailed(val errorCode: ErrorCode) : TicketReserveEvent
object ShowSignIn : TicketReserveEvent
}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModel.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModel.kt
index 05c056faf..fa558fc8d 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModel.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModel.kt
@@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.festago.festago.analytics.AnalyticsHelper
import com.festago.festago.analytics.logNetworkFailure
+import com.festago.festago.model.ErrorCode
import com.festago.festago.model.ReservationStage
import com.festago.festago.repository.AuthRepository
import com.festago.festago.repository.FestivalRepository
@@ -49,12 +50,9 @@ class TicketReserveViewModel @Inject constructor(
),
stages = it.reservationStages.toTicketReserveItems(),
)
- }.onFailure {
+ }.onFailure { error ->
_uiState.value = TicketReserveUiState.Error
- analyticsHelper.logNetworkFailure(
- KEY_LOAD_RESERVATION_LOG,
- it.message.toString(),
- )
+ analyticsHelper.logNetworkFailure(KEY_LOAD_RESERVATION_LOG, error.message ?: "")
}
}
}
@@ -70,8 +68,12 @@ class TicketReserveViewModel @Inject constructor(
reservationTickets.sortedByTicketTypes(),
),
)
- }.onFailure {
+ }.onFailure { error ->
_uiState.value = TicketReserveUiState.Error
+ analyticsHelper.logNetworkFailure(
+ KEY_SHOW_TICKET_TYPES_LOG,
+ error.message ?: "",
+ )
}
} else {
_event.emit(TicketReserveEvent.ShowSignIn)
@@ -84,8 +86,13 @@ class TicketReserveViewModel @Inject constructor(
ticketRepository.reserveTicket(ticketId)
.onSuccess {
_event.emit(TicketReserveEvent.ReserveTicketSuccess(it))
- }.onFailure {
- _event.emit(TicketReserveEvent.ReserveTicketFailed)
+ }.onFailure { error ->
+ if (error is ErrorCode) {
+ _event.emit(TicketReserveEvent.ReserveTicketFailed(error))
+ } else {
+ _event.emit(TicketReserveEvent.ReserveTicketFailed(ErrorCode.UNKNOWN()))
+ analyticsHelper.logNetworkFailure(KEY_RESERVE_TICKET, error.message ?: "")
+ }
}
}
}
@@ -108,5 +115,7 @@ class TicketReserveViewModel @Inject constructor(
companion object {
private const val KEY_LOAD_RESERVATION_LOG = "load_reservation"
+ private const val KEY_SHOW_TICKET_TYPES_LOG = "show_ticket_types"
+ private const val KEY_RESERVE_TICKET = "reserve_ticket"
}
}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetFragment.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetFragment.kt
index d665e2b94..9c48dfb6e 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetFragment.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetFragment.kt
@@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.DividerItemDecoration
import com.festago.festago.databinding.FragmentTicketReserveBottomSheetBinding
import com.festago.festago.presentation.ui.ticketreserve.TicketReserveViewModel
import com.festago.festago.presentation.util.getParcelableArrayListCompat
@@ -22,11 +23,11 @@ class TicketReserveBottomSheetFragment : BottomSheetDialogFragment() {
private val ticketTypeAdapter = TicketReserveBottomSheetAdapter { ticketId ->
binding.selectedTicketTypeId = ticketId
binding.btnReserveTicket.isEnabled = true
+ binding.tvTicketTypePrompt.visibility = View.INVISIBLE
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
-
val viewModelProvider = ViewModelProvider(requireActivity())
vm = viewModelProvider[TicketReserveViewModel::class.java]
}
@@ -37,6 +38,7 @@ class TicketReserveBottomSheetFragment : BottomSheetDialogFragment() {
savedInstanceState: Bundle?,
): View {
_binding = FragmentTicketReserveBottomSheetBinding.inflate(inflater)
+ dialog?.setCanceledOnTouchOutside(false)
binding.lifecycleOwner = viewLifecycleOwner
return binding.root
}
@@ -56,6 +58,7 @@ class TicketReserveBottomSheetFragment : BottomSheetDialogFragment() {
private fun initView() {
binding.rvTicketTypes.adapter = ticketTypeAdapter
+ binding.rvTicketTypes.addItemDecoration(DividerItemDecoration(requireContext(), 1))
val onReserve: (Int) -> Unit = { id -> vm.reserveTicket(id) }
binding.onReserve = onReserve
binding.btnReserveTicket.isEnabled = false
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/util/Event.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/util/Event.kt
deleted file mode 100644
index e9298dfbb..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/util/Event.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.festago.festago.presentation.util
-
-/**
- * Used as a wrapper for data that is exposed via a LiveData that represents an event.
- */
-open class Event(private val content: T) {
-
- var hasBeenHandled = false
- private set // Allow external read but not write
-
- /**
- * Returns the content and prevents its use again.
- */
- fun getContentIfNotHandled(): T? {
- return if (hasBeenHandled) {
- null
- } else {
- hasBeenHandled = true
- content
- }
- }
-
- /**
- * Returns the content, even if it's already been handled.
- */
- fun peekContent(): T = content
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/util/MutableSingleLiveData.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/util/MutableSingleLiveData.kt
deleted file mode 100644
index 68236a79c..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/util/MutableSingleLiveData.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.festago.festago.presentation.util
-
-class MutableSingleLiveData : SingleLiveData {
-
- constructor() : super()
-
- constructor(value: T) : super(value)
-
- public override fun postValue(value: T) {
- super.postValue(value)
- }
-
- public override fun setValue(value: T) {
- super.setValue(value)
- }
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/util/ParcelizeUtil.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/util/ParcelizeUtil.kt
index f32cd1e3e..abdef748d 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/util/ParcelizeUtil.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/presentation/util/ParcelizeUtil.kt
@@ -4,6 +4,7 @@ import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
+import androidx.core.os.BundleCompat
@Suppress("DEPRECATION")
inline fun Bundle.getParcelableCompat(key: String): T? {
@@ -23,11 +24,11 @@ inline fun Bundle.getParcelableArrayListCompat(key: Str
}
}
-@Suppress("DEPRECATION")
inline fun Intent.getParcelableExtraCompat(key: String): T? {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- getParcelableExtra(key, T::class.java)
- } else {
- getParcelableExtra(key) as? T
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ val bundle = extras ?: return null
+ return BundleCompat.getParcelable(bundle, key, T::class.java)
}
+ @Suppress("DEPRECATION")
+ return getParcelableExtra(key) as? T
}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/util/SingleClickUtil.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/util/SingleClickUtil.kt
new file mode 100644
index 000000000..ae4b96588
--- /dev/null
+++ b/android/festago/app/src/main/java/com/festago/festago/presentation/util/SingleClickUtil.kt
@@ -0,0 +1,30 @@
+package com.festago.festago.presentation.util
+
+import android.os.SystemClock
+import android.view.View
+import androidx.databinding.BindingAdapter
+
+class OnSingleClickListener(
+ private var interval: Int = 600,
+ private var onSingleClick: (View) -> Unit,
+) : View.OnClickListener {
+
+ private var lastClickTime: Long = 0
+
+ override fun onClick(v: View) {
+ val elapsedRealtime = SystemClock.elapsedRealtime()
+ if ((elapsedRealtime - lastClickTime) < interval) {
+ return
+ }
+ lastClickTime = elapsedRealtime
+ onSingleClick(v)
+ }
+}
+
+@BindingAdapter("onSingleClick")
+fun View.setOnSingleClickListener(onSingleClick: (View) -> Unit) {
+ val oneClick = OnSingleClickListener {
+ onSingleClick(it)
+ }
+ setOnClickListener(oneClick)
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/util/SingleLiveData.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/util/SingleLiveData.kt
deleted file mode 100644
index 434744392..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/util/SingleLiveData.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.festago.festago.presentation.util
-
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.MutableLiveData
-
-abstract class SingleLiveData {
-
- private val liveData = MutableLiveData>()
-
- protected constructor()
-
- protected constructor(value: T) {
- liveData.value = Event(value)
- }
-
- protected open fun setValue(value: T) {
- liveData.value = Event(value)
- }
-
- protected open fun postValue(value: T) {
- liveData.postValue(Event(value))
- }
-
- fun getValue() = liveData.value?.peekContent()
-
- fun observe(owner: LifecycleOwner, onResult: (T) -> Unit) {
- liveData.observe(owner) { it.getContentIfNotHandled()?.let(onResult) }
- }
-
- fun observePeek(owner: LifecycleOwner, onResult: (T) -> Unit) {
- liveData.observe(owner) { onResult(it.peekContent()) }
- }
-}
diff --git a/android/festago/app/src/main/res/drawable/bg_chip_festival_list_selected.xml b/android/festago/app/src/main/res/drawable/bg_chip_festival_list_selected.xml
new file mode 100644
index 000000000..b92ade845
--- /dev/null
+++ b/android/festago/app/src/main/res/drawable/bg_chip_festival_list_selected.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/android/festago/app/src/main/res/drawable/img_festago_home_logo.xml b/android/festago/app/src/main/res/drawable/img_festago_home_logo.xml
new file mode 100644
index 000000000..1694c060c
--- /dev/null
+++ b/android/festago/app/src/main/res/drawable/img_festago_home_logo.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/festago/app/src/main/res/drawable/text_chip_festival_list_selected.xml b/android/festago/app/src/main/res/drawable/text_chip_festival_list_selected.xml
new file mode 100644
index 000000000..f7ec5e8b0
--- /dev/null
+++ b/android/festago/app/src/main/res/drawable/text_chip_festival_list_selected.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/android/festago/app/src/main/res/layout/fragment_festival_list.xml b/android/festago/app/src/main/res/layout/fragment_festival_list.xml
index b4b4a20f3..14da2ce1b 100644
--- a/android/festago/app/src/main/res/layout/fragment_festival_list.xml
+++ b/android/festago/app/src/main/res/layout/fragment_festival_list.xml
@@ -15,68 +15,129 @@
android:layout_height="match_parent"
tools:context=".presentation.ui.home.festivallist.FestivalListFragment">
-
-
-
-
-
-
+ android:layout_height="0dp"
+ app:layout_constraintTop_toTopOf="parent">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+ app:layout_constraintTop_toBottomOf="@id/glFestivalListTop"
+ tools:visibility="gone" />
diff --git a/android/festago/app/src/main/res/layout/fragment_ticket_reserve_bottom_sheet.xml b/android/festago/app/src/main/res/layout/fragment_ticket_reserve_bottom_sheet.xml
index f34d68337..384feb9f2 100644
--- a/android/festago/app/src/main/res/layout/fragment_ticket_reserve_bottom_sheet.xml
+++ b/android/festago/app/src/main/res/layout/fragment_ticket_reserve_bottom_sheet.xml
@@ -47,22 +47,42 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="44dp"
+ android:text="@{stageStartTime}"
android:textSize="18sp"
android:textStyle="bold"
- android:text="@{stageStartTime}"
app:layout_constraintEnd_toEndOf="@id/endGuideLine"
app:layout_constraintStart_toStartOf="@id/startGuideline"
app:layout_constraintTop_toTopOf="parent"
tools:text="07.03 (목) 17:00" />
+
+
+
+
+
+
@@ -73,11 +93,11 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="28dp"
- android:onClick="@{() -> onReserve.invoke(selectedTicketTypeId)}"
android:text="@string/ticket_reserve_tv_btn_reserve_ticket"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent" />
+ app:layout_constraintStart_toStartOf="parent"
+ app:onSingleClick="@{() -> onReserve.invoke(selectedTicketTypeId)}" />
diff --git a/android/festago/app/src/main/res/layout/item_ticket_reserve.xml b/android/festago/app/src/main/res/layout/item_ticket_reserve.xml
index c1df86cff..f1835d3db 100644
--- a/android/festago/app/src/main/res/layout/item_ticket_reserve.xml
+++ b/android/festago/app/src/main/res/layout/item_ticket_reserve.xml
@@ -111,11 +111,11 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="28dp"
- android:onClick="@{() -> stage.onShowStageTickets.invoke(stage.id, stage.startTime)}"
android:text="@string/ticket_reserve_btn_reserve_ticket"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/tvAuthGuideStudent" />
+ app:layout_constraintTop_toBottomOf="@id/tvAuthGuideStudent"
+ app:onSingleClick="@{() -> stage.onShowStageTickets.invoke(stage.id, stage.startTime)}" />
diff --git a/android/festago/app/src/main/res/layout/item_ticket_reserve_header.xml b/android/festago/app/src/main/res/layout/item_ticket_reserve_header.xml
index 221d4abaa..eb30c1b19 100644
--- a/android/festago/app/src/main/res/layout/item_ticket_reserve_header.xml
+++ b/android/festago/app/src/main/res/layout/item_ticket_reserve_header.xml
@@ -33,12 +33,13 @@
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvUniversityName"
+ android:layout_marginBottom="20dp"
tools:text="2023.07.03 - 2023.07.09" />
diff --git a/android/festago/app/src/main/res/values/strings.xml b/android/festago/app/src/main/res/values/strings.xml
index 35daf2b7f..8cf6589d1 100644
--- a/android/festago/app/src/main/res/values/strings.xml
+++ b/android/festago/app/src/main/res/values/strings.xml
@@ -39,6 +39,7 @@
티켓 목록
마이페이지
알림 권한을 거부했습니다 :(
+ 한 번 더 누르면 앱이 종료됩니다
%1s ~ %1s
@@ -55,11 +56,17 @@
예매 하기
MM월 dd일 HH:mm 오픈예정
로그인 후 예매
+ 예매 유형을 선택해주세요
+ 공연이 매진되었습니다
+ 이미 예매한 공연입니다
+ 학생 인증이 필요합니다
+ 알 수 없는 오류가 발생했습니다
yyyy.MM.dd
- 진행중
- 지난축제
+ 진행중
+ 진행 예정
+ 지난 축제
축제 조회에 실패했습니다.
조회된 축제가 없습니다.
%s - %s
diff --git a/android/festago/app/src/main/res/values/style.xml b/android/festago/app/src/main/res/values/style.xml
index 004ea7295..d071718c5 100644
--- a/android/festago/app/src/main/res/values/style.xml
+++ b/android/festago/app/src/main/res/values/style.xml
@@ -22,4 +22,10 @@
- 50%
+
+
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/rule/MainDispatcherRule.kt b/android/festago/app/src/test/java/com/festago/festago/presentation/rule/MainDispatcherRule.kt
new file mode 100644
index 000000000..3059da632
--- /dev/null
+++ b/android/festago/app/src/test/java/com/festago/festago/presentation/rule/MainDispatcherRule.kt
@@ -0,0 +1,28 @@
+package com.festago.festago.presentation.rule
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+// Reusable JUnit4 TestRule to override the Main dispatcher
+class MainDispatcherRule
+@OptIn(ExperimentalCoroutinesApi::class)
+constructor(
+ private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(),
+) : TestWatcher() {
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun starting(description: Description) {
+ Dispatchers.setMain(testDispatcher)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun finished(description: Description) {
+ Dispatchers.resetMain()
+ }
+}
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/HomeViewModelTest.kt b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/HomeViewModelTest.kt
index 6a7c5616c..b867fe986 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/HomeViewModelTest.kt
+++ b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/HomeViewModelTest.kt
@@ -1,38 +1,30 @@
package com.festago.festago.presentation.ui.home
import app.cash.turbine.test
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.AuthRepository
import io.mockk.every
import io.mockk.mockk
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.Assertions.assertThat
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
class HomeViewModelTest {
+
private lateinit var vm: HomeViewModel
private lateinit var authRepository: AuthRepository
- @OptIn(ExperimentalCoroutinesApi::class)
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(UnconfinedTestDispatcher())
authRepository = mockk()
vm = HomeViewModel(authRepository)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `사용자 인증 유무가 다음과 같을 때`(isSigned: Boolean) {
every { authRepository.isSigned } returns isSigned
}
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModelTest.kt b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModelTest.kt
index 56b5b060b..2696cbf11 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModelTest.kt
+++ b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModelTest.kt
@@ -3,20 +3,17 @@ package com.festago.festago.presentation.ui.home.festivallist
import app.cash.turbine.test
import com.festago.festago.analytics.AnalyticsHelper
import com.festago.festago.model.Festival
+import com.festago.festago.model.FestivalFilter
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.FestivalRepository
import io.mockk.coEvery
import io.mockk.mockk
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.SoftAssertions
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import java.time.LocalDate
@@ -36,36 +33,31 @@ class FestivalListViewModelTest {
)
}
- @OptIn(ExperimentalCoroutinesApi::class)
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(UnconfinedTestDispatcher())
festivalRepository = mockk()
analyticsHelper = mockk(relaxed = true)
vm = FestivalListViewModel(festivalRepository, analyticsHelper)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `축제 목록 요청 결과가 다음과 같을 때`(result: Result>) {
coEvery {
- festivalRepository.loadFestivals()
+ festivalRepository.loadFestivals(any())
} answers {
result
}
}
@Test
- fun `축제 목록 받아오기에 성공하면 성공 상태이고 축제 목록을 반환한다`() {
+ fun `진행 예정인 축제 목록 받아오기에 성공하면 성공 상태이고 축제 목록을 반환한다`() {
// given
`축제 목록 요청 결과가 다음과 같을 때`(Result.success(fakeFestivals))
// when
- vm.loadFestivals()
+ vm.loadFestivals(FestivalFilter.PLANNED)
// then
val softly = SoftAssertions().apply {
@@ -85,12 +77,12 @@ class FestivalListViewModelTest {
}
@Test
- fun `축제 목록 받아오기에 실패하면 에러 상태다`() {
+ fun `진행 예정 축제 목록 받아오기에 실패하면 에러 상태이다`() {
// given
`축제 목록 요청 결과가 다음과 같을 때`(Result.failure(Exception()))
// when
- vm.loadFestivals()
+ vm.loadFestivals(FestivalFilter.PLANNED)
// then
val softly = SoftAssertions().apply {
@@ -108,14 +100,14 @@ class FestivalListViewModelTest {
fun `축제 목록을 받아오는 중이면 로딩 상태다`() {
// given
coEvery {
- festivalRepository.loadFestivals()
+ festivalRepository.loadFestivals(any())
} coAnswers {
delay(1000)
Result.success(emptyList())
}
// when
- vm.loadFestivals()
+ vm.loadFestivals(FestivalFilter.PLANNED)
// then
val softly = SoftAssertions().apply {
@@ -131,7 +123,6 @@ class FestivalListViewModelTest {
@Test
fun `티켓 예매를 열면 티켓 예매 열기 이벤트가 발생한다`() = runTest {
-
vm.event.test {
// when
val fakeFestivalId = 1L
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/mypage/MyPageViewModelTest.kt b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/mypage/MyPageViewModelTest.kt
index 2eb87bdae..a93a2d8aa 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/mypage/MyPageViewModelTest.kt
+++ b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/mypage/MyPageViewModelTest.kt
@@ -7,26 +7,23 @@ import com.festago.festago.model.Stage
import com.festago.festago.model.Ticket
import com.festago.festago.model.TicketCondition
import com.festago.festago.model.UserProfile
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.AuthRepository
import com.festago.festago.repository.TicketRepository
import com.festago.festago.repository.UserRepository
import io.mockk.coEvery
import io.mockk.mockk
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.SoftAssertions
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import java.time.LocalDateTime
class MyPageViewModelTest {
+
private lateinit var vm: MyPageViewModel
private lateinit var userRepository: UserRepository
private lateinit var ticketRepository: TicketRepository
@@ -55,10 +52,11 @@ class MyPageViewModelTest {
),
)
- @OptIn(ExperimentalCoroutinesApi::class)
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(UnconfinedTestDispatcher())
userRepository = mockk(relaxed = true)
ticketRepository = mockk()
authRepository = mockk()
@@ -66,12 +64,6 @@ class MyPageViewModelTest {
vm = MyPageViewModel(userRepository, ticketRepository, authRepository, analyticsHelper)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `로그인 상태가 다음과 같다`(result: Boolean) {
coEvery {
authRepository.isSigned
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListViewModelTest.kt b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListViewModelTest.kt
index 050fe50fd..2ddeb69f5 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListViewModelTest.kt
+++ b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListViewModelTest.kt
@@ -4,41 +4,33 @@ import app.cash.turbine.test
import com.festago.festago.analytics.AnalyticsHelper
import com.festago.festago.model.Ticket
import com.festago.festago.presentation.fixture.TicketFixture
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.TicketRepository
import io.mockk.coEvery
import io.mockk.mockk
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.SoftAssertions
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
class TicketListViewModelTest {
+
private lateinit var vm: TicketListViewModel
private lateinit var ticketRepository: TicketRepository
private lateinit var analyticsHelper: AnalyticsHelper
- @OptIn(ExperimentalCoroutinesApi::class)
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(UnconfinedTestDispatcher())
ticketRepository = mockk()
analyticsHelper = mockk(relaxed = true)
vm = TicketListViewModel(ticketRepository, analyticsHelper)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `현재 티켓 요청 결과가 다음과 같을 때`(result: Result>) {
coEvery { ticketRepository.loadCurrentTickets() } returns result
}
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolViewModelTest.kt b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolViewModelTest.kt
index d6708dc56..a898f407c 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolViewModelTest.kt
+++ b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolViewModelTest.kt
@@ -3,32 +3,28 @@ package com.festago.festago.presentation.ui.selectschool
import app.cash.turbine.test
import com.festago.festago.analytics.AnalyticsHelper
import com.festago.festago.model.School
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.SchoolRepository
import io.mockk.coEvery
import io.mockk.mockk
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.SoftAssertions.assertSoftly
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
-@OptIn(ExperimentalCoroutinesApi::class)
class SelectSchoolViewModelTest {
- private val testDispatcher = UnconfinedTestDispatcher()
private lateinit var vm: SelectSchoolViewModel
private lateinit var schoolRepository: SchoolRepository
private lateinit var analyticsHelper: AnalyticsHelper
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setup() {
- Dispatchers.setMain(testDispatcher)
schoolRepository = mockk()
analyticsHelper = mockk(relaxed = true)
vm = SelectSchoolViewModel(
@@ -37,11 +33,6 @@ class SelectSchoolViewModelTest {
)
}
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `학교 목록 불러오기 요청 결과가 다음과 같을 때 `(result: Result>) {
coEvery {
schoolRepository.loadSchools()
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/signin/SignInViewModelTest.kt b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/signin/SignInViewModelTest.kt
index c8f209389..5a53f69b5 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/signin/SignInViewModelTest.kt
+++ b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/signin/SignInViewModelTest.kt
@@ -2,41 +2,32 @@ package com.festago.festago.presentation.ui.signin
import app.cash.turbine.test
import com.festago.festago.analytics.AnalyticsHelper
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.AuthRepository
import io.mockk.coEvery
import io.mockk.mockk
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.Assertions.assertThat
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
class SignInViewModelTest {
+
private lateinit var vm: SignInViewModel
private lateinit var authRepository: AuthRepository
private lateinit var analyticsHelper: AnalyticsHelper
- @OptIn(ExperimentalCoroutinesApi::class)
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(UnconfinedTestDispatcher())
-
authRepository = mockk(relaxed = true)
analyticsHelper = mockk(relaxed = true)
vm = SignInViewModel(authRepository, analyticsHelper)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `로그인 결과가 다음과 같을 때`(result: Result) {
coEvery { authRepository.signIn() } returns result
}
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationViewModelTest.kt b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationViewModelTest.kt
index 672066619..2dd025261 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationViewModelTest.kt
+++ b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationViewModelTest.kt
@@ -3,34 +3,30 @@ package com.festago.festago.presentation.ui.studentverification
import app.cash.turbine.test
import com.festago.festago.analytics.AnalyticsHelper
import com.festago.festago.model.StudentVerificationCode
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.SchoolRepository
import com.festago.festago.repository.StudentVerificationRepository
import io.mockk.coEvery
import io.mockk.mockk
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.SoftAssertions.assertSoftly
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
-@OptIn(ExperimentalCoroutinesApi::class)
class StudentVerificationViewModelTest {
- private val testDispatcher = UnconfinedTestDispatcher()
private lateinit var vm: StudentVerificationViewModel
private lateinit var studentVerificationRepository: StudentVerificationRepository
private lateinit var schoolRepository: SchoolRepository
private lateinit var analyticsHelper: AnalyticsHelper
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(testDispatcher)
studentVerificationRepository = mockk()
schoolRepository = mockk()
analyticsHelper = mockk(relaxed = true)
@@ -41,12 +37,6 @@ class StudentVerificationViewModelTest {
)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `이메일 요청 결과가 다음과 같을 때`(result: Result) {
coEvery {
schoolRepository.loadSchoolEmail(any())
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryViewModelTest.kt b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryViewModelTest.kt
index 9369a2a87..356b8af93 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryViewModelTest.kt
+++ b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryViewModelTest.kt
@@ -4,40 +4,32 @@ import com.festago.festago.analytics.AnalyticsHelper
import com.festago.festago.model.Ticket
import com.festago.festago.model.TicketCode
import com.festago.festago.presentation.fixture.TicketFixture
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.TicketRepository
import io.mockk.coEvery
import io.mockk.mockk
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.SoftAssertions
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
class TicketEntryViewModelTest {
+
private lateinit var vm: TicketEntryViewModel
private lateinit var ticketRepository: TicketRepository
private lateinit var analyticsHelper: AnalyticsHelper
- @OptIn(ExperimentalCoroutinesApi::class)
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(UnconfinedTestDispatcher())
ticketRepository = mockk()
analyticsHelper = mockk(relaxed = true)
vm = TicketEntryViewModel(ticketRepository, analyticsHelper)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `티켓 요쳥 결과는 다음과 같을 때`(result: Result) {
coEvery { ticketRepository.loadTicket(any()) } returns result
}
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewModelTest.kt b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewModelTest.kt
index b9c8f8fec..5611f58fe 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewModelTest.kt
+++ b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewModelTest.kt
@@ -3,30 +3,27 @@ package com.festago.festago.presentation.ui.tickethistory
import com.festago.festago.analytics.AnalyticsHelper
import com.festago.festago.model.Ticket
import com.festago.festago.presentation.fixture.TicketFixture
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.TicketRepository
import io.mockk.coEvery
import io.mockk.mockk
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.resetMain
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.SoftAssertions
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
class TicketHistoryViewModelTest {
- private lateinit var vm: TicketHistoryViewModel
+ private lateinit var vm: TicketHistoryViewModel
private lateinit var ticketRepository: TicketRepository
private lateinit var analyticsHelper: AnalyticsHelper
- @OptIn(ExperimentalCoroutinesApi::class)
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(UnconfinedTestDispatcher())
ticketRepository = mockk()
analyticsHelper = mockk(relaxed = true)
@@ -36,12 +33,6 @@ class TicketHistoryViewModelTest {
)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `티켓 기록 요청 결과가 다음과 같을 때`(result: Result>) {
coEvery {
ticketRepository.loadHistoryTickets(any())
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModelTest.kt b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModelTest.kt
index b6a5582c7..bc0252359 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModelTest.kt
+++ b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModelTest.kt
@@ -2,34 +2,32 @@ package com.festago.festago.presentation.ui.ticketreserve
import app.cash.turbine.test
import com.festago.festago.analytics.AnalyticsHelper
+import com.festago.festago.model.ErrorCode
import com.festago.festago.model.Reservation
import com.festago.festago.model.ReservationStage
import com.festago.festago.model.ReservationTicket
import com.festago.festago.model.ReservationTickets
import com.festago.festago.model.ReservedTicket
import com.festago.festago.model.TicketType
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.AuthRepository
import com.festago.festago.repository.FestivalRepository
import com.festago.festago.repository.ReservationTicketRepository
import com.festago.festago.repository.TicketRepository
import io.mockk.coEvery
import io.mockk.mockk
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.AssertionsForClassTypes.assertThat
import org.assertj.core.api.SoftAssertions
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import java.time.LocalDate
import java.time.LocalDateTime
class TicketReserveViewModelTest {
+
private lateinit var vm: TicketReserveViewModel
private lateinit var reservationTicketRepository: ReservationTicketRepository
private lateinit var festivalRepository: FestivalRepository
@@ -66,10 +64,11 @@ class TicketReserveViewModelTest {
number = 1,
)
- @OptIn(ExperimentalCoroutinesApi::class)
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(UnconfinedTestDispatcher())
reservationTicketRepository = mockk()
festivalRepository = mockk()
ticketRepository = mockk()
@@ -84,12 +83,6 @@ class TicketReserveViewModelTest {
)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun tearDown() {
- Dispatchers.resetMain()
- }
-
private fun `예약 정보 요청 결과가 다음과 같을 때`(result: Result) {
coEvery { festivalRepository.loadFestivalDetail(any()) } returns result
}
@@ -196,7 +189,7 @@ class TicketReserveViewModelTest {
}
@Test
- fun `티켓 유형을 선택하고 예약하면 예약 성공 이벤트가 발생한다`() = runTest {
+ fun `티켓 유형을 선택하고 예약하면 예매 성공 이벤트가 발생한다`() = runTest {
// given
coEvery {
ticketRepository.reserveTicket(any())
@@ -214,7 +207,61 @@ class TicketReserveViewModelTest {
}
@Test
- fun `티켓 유형을 선택하고 예약하는 것을 실패하면 예약 실패 이벤트가 발생한다`() = runTest {
+ fun `학생 인증하지 않아 티켓 예매에 실패하면 예매 실패 이벤트가 발생한다`() = runTest {
+ // given
+ `티켓 예약 요청 결과가 다음과 같을 때`(Result.failure(ErrorCode.NEED_STUDENT_VERIFICATION()))
+
+ vm.event.test {
+ // when
+ vm.reserveTicket(0)
+
+ // then
+ val actual = awaitItem() as? TicketReserveEvent.ReserveTicketFailed
+ assertThat(actual).isNotNull
+
+ // and: 학생 인증 필요 예매 실패 코드를 가진다
+ assertThat(actual?.errorCode).isExactlyInstanceOf(ErrorCode.NEED_STUDENT_VERIFICATION::class.java)
+ }
+ }
+
+ @Test
+ fun `이미 예매한 티켓이라서 티켓 예매에 실패하면 예매 실패 이벤트가 발생한다`() = runTest {
+ // given
+ `티켓 예약 요청 결과가 다음과 같을 때`(Result.failure(ErrorCode.RESERVE_TICKET_OVER_AMOUNT()))
+
+ vm.event.test {
+ // when
+ vm.reserveTicket(0)
+
+ // then
+ val actual = awaitItem() as? TicketReserveEvent.ReserveTicketFailed
+ assertThat(actual).isNotNull
+
+ // and: 보유 가능한 수량 초과 예매 실패 코드를 가진다
+ assertThat(actual?.errorCode).isExactlyInstanceOf(ErrorCode.RESERVE_TICKET_OVER_AMOUNT::class.java)
+ }
+ }
+
+ @Test
+ fun `티켓이 매진되어 티켓 예매에 실패하면 예매 실패 이벤트가 발생한다`() = runTest {
+ // given
+ `티켓 예약 요청 결과가 다음과 같을 때`(Result.failure(ErrorCode.TICKET_SOLD_OUT()))
+
+ vm.event.test {
+ // when
+ vm.reserveTicket(0)
+
+ // then
+ val actual = awaitItem() as? TicketReserveEvent.ReserveTicketFailed
+ assertThat(actual).isNotNull
+
+ // and: 티켓 매진 예매 실패 코드를 가진다
+ assertThat(actual?.errorCode).isExactlyInstanceOf(ErrorCode.TICKET_SOLD_OUT::class.java)
+ }
+ }
+
+ @Test
+ fun `알 수 없는 오류로 티켓 예매에 실패하면 예매 실패 이벤트가 발생한다`() = runTest {
// given
`티켓 예약 요청 결과가 다음과 같을 때`(Result.failure(Exception()))
@@ -223,7 +270,11 @@ class TicketReserveViewModelTest {
vm.reserveTicket(0)
// then
- assertThat(awaitItem()).isExactlyInstanceOf(TicketReserveEvent.ReserveTicketFailed::class.java)
+ val actual = awaitItem() as? TicketReserveEvent.ReserveTicketFailed
+ assertThat(actual).isNotNull
+
+ // and: 알 수 없는 예매 실패 코드를 가진다
+ assertThat(actual?.errorCode).isExactlyInstanceOf(ErrorCode.UNKNOWN::class.java)
}
}
}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/ErrorCode.kt b/android/festago/domain/src/main/java/com/festago/festago/model/ErrorCode.kt
new file mode 100644
index 000000000..55baeae7c
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/model/ErrorCode.kt
@@ -0,0 +1,9 @@
+package com.festago.festago.model
+
+sealed class ErrorCode : Throwable() {
+
+ class NEED_STUDENT_VERIFICATION : ErrorCode()
+ class RESERVE_TICKET_OVER_AMOUNT : ErrorCode()
+ class TICKET_SOLD_OUT : ErrorCode()
+ class UNKNOWN : ErrorCode()
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/FestivalFilter.kt b/android/festago/domain/src/main/java/com/festago/festago/model/FestivalFilter.kt
new file mode 100644
index 000000000..4f881328f
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/model/FestivalFilter.kt
@@ -0,0 +1,5 @@
+package com.festago.festago.model
+
+enum class FestivalFilter {
+ ALL, PROGRESS, PLANNED, END
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/repository/FestivalRepository.kt b/android/festago/domain/src/main/java/com/festago/festago/repository/FestivalRepository.kt
index db08d52dd..326077ec2 100644
--- a/android/festago/domain/src/main/java/com/festago/festago/repository/FestivalRepository.kt
+++ b/android/festago/domain/src/main/java/com/festago/festago/repository/FestivalRepository.kt
@@ -1,9 +1,10 @@
package com.festago.festago.repository
import com.festago.festago.model.Festival
+import com.festago.festago.model.FestivalFilter
import com.festago.festago.model.Reservation
interface FestivalRepository {
- suspend fun loadFestivals(): Result>
+ suspend fun loadFestivals(festivalFilter: FestivalFilter): Result>
suspend fun loadFestivalDetail(festivalId: Long): Result
}
diff --git a/backend/src/main/java/com/festago/admin/application/AdminService.java b/backend/src/main/java/com/festago/admin/application/AdminService.java
deleted file mode 100644
index 6caf46f1a..000000000
--- a/backend/src/main/java/com/festago/admin/application/AdminService.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package com.festago.admin.application;
-
-import com.festago.admin.dto.AdminFestivalResponse;
-import com.festago.admin.dto.AdminResponse;
-import com.festago.admin.dto.AdminSchoolResponse;
-import com.festago.admin.dto.AdminStageResponse;
-import com.festago.admin.dto.AdminTicketResponse;
-import com.festago.festival.domain.Festival;
-import com.festago.festival.repository.FestivalRepository;
-import com.festago.school.domain.School;
-import com.festago.school.repository.SchoolRepository;
-import com.festago.stage.domain.Stage;
-import com.festago.stage.repository.StageRepository;
-import com.festago.ticket.domain.Ticket;
-import com.festago.ticket.domain.TicketAmount;
-import com.festago.ticket.domain.TicketEntryTime;
-import com.festago.ticket.repository.TicketRepository;
-import java.time.LocalDateTime;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-import lombok.RequiredArgsConstructor;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-@Transactional
-@Service
-@RequiredArgsConstructor
-public class AdminService {
-
- private final FestivalRepository festivalRepository;
- private final StageRepository stageRepository;
- private final TicketRepository ticketRepository;
- private final SchoolRepository schoolRepository;
-
- @Transactional(readOnly = true)
- public AdminResponse getAdminResponse() {
- List allSchool = schoolRepository.findAll();
- List allTicket = ticketRepository.findAll();
- List allStage = stageRepository.findAll();
- List allFestival = festivalRepository.findAll();
- return new AdminResponse(
- schoolResponses(allSchool),
- ticketResponses(allTicket),
- stageResponses(allStage),
- festivalResponses(allFestival));
- }
-
- private List schoolResponses(List schools) {
- return schools.stream()
- .map(AdminSchoolResponse::from)
- .toList();
- }
-
- private List ticketResponses(List tickets) {
- return tickets.stream()
- .map(this::ticketResponse)
- .toList();
- }
-
- private AdminTicketResponse ticketResponse(Ticket ticket) {
- TicketAmount ticketAmount = ticket.getTicketAmount();
- Map amountByEntryTime = ticket.getTicketEntryTimes().stream()
- .collect(Collectors.toMap(
- TicketEntryTime::getEntryTime,
- TicketEntryTime::getAmount
- ));
- return new AdminTicketResponse(
- ticket.getId(),
- ticket.getStage().getId(),
- ticket.getTicketType(),
- ticketAmount.getTotalAmount(),
- ticketAmount.getReservedAmount(),
- amountByEntryTime
- );
- }
-
- private List stageResponses(List stages) {
- return stages.stream()
- .map(this::stageResponse)
- .toList();
- }
-
- private AdminStageResponse stageResponse(Stage stage) {
- List ticketIds = stage.getTickets().stream()
- .map(Ticket::getId)
- .toList();
- return new AdminStageResponse(
- stage.getId(),
- stage.getFestival().getId(),
- stage.getStartTime(),
- stage.getLineUp(),
- ticketIds
- );
- }
-
- private List festivalResponses(List festivals) {
- return festivals.stream()
- .map(festival -> new AdminFestivalResponse(
- festival.getId(),
- festival.getSchool().getId(),
- festival.getName(),
- festival.getStartDate(),
- festival.getEndDate(),
- festival.getThumbnail()))
- .toList();
- }
-}
diff --git a/backend/src/main/java/com/festago/admin/domain/Admin.java b/backend/src/main/java/com/festago/admin/domain/Admin.java
index a7937444e..009fc5319 100644
--- a/backend/src/main/java/com/festago/admin/domain/Admin.java
+++ b/backend/src/main/java/com/festago/admin/domain/Admin.java
@@ -1,12 +1,15 @@
package com.festago.admin.domain;
import com.festago.common.domain.BaseTimeEntity;
+import com.festago.common.util.Validator;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@@ -22,12 +25,21 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Admin extends BaseTimeEntity {
+ private static final int MIN_USERNAME_LENGTH = 4;
+ private static final int MAX_USERNAME_LENGTH = 20;
+ private static final int MIN_PASSWORD_LENGTH = 4;
+ private static final int MAX_PASSWORD_LENGTH = 255;
+
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
+ @NotNull
+ @Size(min = MIN_USERNAME_LENGTH, max = MAX_USERNAME_LENGTH)
private String username;
+ @NotNull
+ @Size(min = MIN_PASSWORD_LENGTH, max = MAX_PASSWORD_LENGTH)
private String password;
public Admin(String username, String password) {
@@ -35,11 +47,31 @@ public Admin(String username, String password) {
}
public Admin(Long id, String username, String password) {
+ validate(username, password);
this.id = id;
this.username = username;
this.password = password;
}
+ private void validate(String username, String password) {
+ validateUsername(username);
+ validatePassword(password);
+ }
+
+ private void validateUsername(String username) {
+ String fieldName = "username";
+ Validator.hasBlank(username, fieldName);
+ Validator.minLength(username, MIN_USERNAME_LENGTH, fieldName);
+ Validator.maxLength(username, MAX_USERNAME_LENGTH, fieldName);
+ }
+
+ private void validatePassword(String password) {
+ String fieldName = "password";
+ Validator.hasBlank(password, fieldName);
+ Validator.minLength(password, MIN_PASSWORD_LENGTH, fieldName);
+ Validator.maxLength(password, MAX_PASSWORD_LENGTH, fieldName);
+ }
+
public Long getId() {
return id;
}
diff --git a/backend/src/main/java/com/festago/admin/dto/AdminFestivalResponse.java b/backend/src/main/java/com/festago/admin/dto/AdminFestivalResponse.java
deleted file mode 100644
index ea59b850e..000000000
--- a/backend/src/main/java/com/festago/admin/dto/AdminFestivalResponse.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.festago.admin.dto;
-
-import java.time.LocalDate;
-
-public record AdminFestivalResponse(
- Long id,
- Long schoolId,
- String name,
- LocalDate startDate,
- LocalDate endDate,
- String thumbnail) {
-
-}
diff --git a/backend/src/main/java/com/festago/admin/dto/AdminResponse.java b/backend/src/main/java/com/festago/admin/dto/AdminResponse.java
deleted file mode 100644
index 59c473a7f..000000000
--- a/backend/src/main/java/com/festago/admin/dto/AdminResponse.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.festago.admin.dto;
-
-import java.util.List;
-
-public record AdminResponse(
- List adminSchools,
- List adminTickets,
- List adminStageResponse,
- List adminFestivalResponse) {
-
-}
diff --git a/backend/src/main/java/com/festago/admin/dto/AdminSchoolResponse.java b/backend/src/main/java/com/festago/admin/dto/AdminSchoolResponse.java
deleted file mode 100644
index 6a898ba24..000000000
--- a/backend/src/main/java/com/festago/admin/dto/AdminSchoolResponse.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.festago.admin.dto;
-
-import com.festago.school.domain.School;
-
-public record AdminSchoolResponse(
- Long id,
- String domain,
- String name) {
-
- public static AdminSchoolResponse from(School school) {
- return new AdminSchoolResponse(
- school.getId(),
- school.getDomain(),
- school.getName()
- );
- }
-}
diff --git a/backend/src/main/java/com/festago/admin/dto/AdminStageResponse.java b/backend/src/main/java/com/festago/admin/dto/AdminStageResponse.java
deleted file mode 100644
index ab568d4c2..000000000
--- a/backend/src/main/java/com/festago/admin/dto/AdminStageResponse.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.festago.admin.dto;
-
-import java.time.LocalDateTime;
-import java.util.List;
-
-public record AdminStageResponse(
- Long id,
- Long festivalId,
- LocalDateTime startTime,
- String lineUp,
- List ticketId) {
-
-}
diff --git a/backend/src/main/java/com/festago/admin/dto/AdminTicketResponse.java b/backend/src/main/java/com/festago/admin/dto/AdminTicketResponse.java
deleted file mode 100644
index 88c93754d..000000000
--- a/backend/src/main/java/com/festago/admin/dto/AdminTicketResponse.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.festago.admin.dto;
-
-import com.festago.ticket.domain.TicketType;
-import java.time.LocalDateTime;
-import java.util.Map;
-
-public record AdminTicketResponse(
- Long id,
- Long stageId,
- TicketType ticketType,
- Integer totalAmount,
- Integer reservedAmount,
- Map entryTimeAmount) {
-
-}
diff --git a/backend/src/main/java/com/festago/auth/application/OAuth2Clients.java b/backend/src/main/java/com/festago/auth/application/OAuth2Clients.java
index 7805c57f0..8b7144d0e 100644
--- a/backend/src/main/java/com/festago/auth/application/OAuth2Clients.java
+++ b/backend/src/main/java/com/festago/auth/application/OAuth2Clients.java
@@ -3,12 +3,14 @@
import com.festago.auth.domain.SocialType;
import com.festago.common.exception.BadRequestException;
import com.festago.common.exception.ErrorCode;
-import com.festago.common.exception.InternalServerException;
+import com.festago.common.exception.UnexpectedException;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import lombok.extern.slf4j.Slf4j;
+@Slf4j
public class OAuth2Clients {
private final Map oAuth2ClientMap;
@@ -43,7 +45,8 @@ public OAuth2ClientsBuilder addAll(List oAuth2Clients) {
public OAuth2ClientsBuilder add(OAuth2Client oAuth2Client) {
SocialType socialType = oAuth2Client.getSocialType();
if (oAuth2ClientMap.containsKey(socialType)) {
- throw new InternalServerException(ErrorCode.DUPLICATE_SOCIAL_TYPE);
+ log.error("OAuth2 제공자는 중복될 수 없습니다.");
+ throw new UnexpectedException("중복된 OAuth2 제공자 입니다.");
}
oAuth2ClientMap.put(socialType, oAuth2Client);
return this;
diff --git a/backend/src/main/java/com/festago/auth/domain/AuthPayload.java b/backend/src/main/java/com/festago/auth/domain/AuthPayload.java
index 1b174f74c..615ddbeba 100644
--- a/backend/src/main/java/com/festago/auth/domain/AuthPayload.java
+++ b/backend/src/main/java/com/festago/auth/domain/AuthPayload.java
@@ -1,7 +1,6 @@
package com.festago.auth.domain;
-import com.festago.common.exception.ErrorCode;
-import com.festago.common.exception.InternalServerException;
+import com.festago.common.exception.UnexpectedException;
public class AuthPayload {
@@ -16,7 +15,7 @@ public AuthPayload(Long memberId, Role role) {
private void validate(Role role) {
if (role == null) {
- throw new InternalServerException(ErrorCode.INVALID_AUTH_TOKEN_PAYLOAD);
+ throw new UnexpectedException("role은 null이 될 수 없습니다.");
}
}
diff --git a/backend/src/main/java/com/festago/auth/domain/Role.java b/backend/src/main/java/com/festago/auth/domain/Role.java
index 69936580b..d61a3b4a8 100644
--- a/backend/src/main/java/com/festago/auth/domain/Role.java
+++ b/backend/src/main/java/com/festago/auth/domain/Role.java
@@ -3,8 +3,7 @@
import com.festago.auth.annotation.Admin;
import com.festago.auth.annotation.Anonymous;
import com.festago.auth.annotation.Member;
-import com.festago.common.exception.ErrorCode;
-import com.festago.common.exception.InternalServerException;
+import com.festago.common.exception.UnexpectedException;
import java.lang.annotation.Annotation;
public enum Role {
@@ -23,7 +22,7 @@ public static Role from(String role) {
try {
return valueOf(role);
} catch (NullPointerException | IllegalArgumentException e) {
- throw new InternalServerException(ErrorCode.INVALID_ROLE_NAME);
+ throw new UnexpectedException("해당하는 Role이 없습니다.");
}
}
diff --git a/backend/src/main/java/com/festago/auth/dto/KakaoAccessTokenResponse.java b/backend/src/main/java/com/festago/auth/dto/KakaoAccessTokenResponse.java
deleted file mode 100644
index c7f8c793d..000000000
--- a/backend/src/main/java/com/festago/auth/dto/KakaoAccessTokenResponse.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.festago.auth.dto;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-public record KakaoAccessTokenResponse(
- @JsonProperty("token_type") String tokenType,
- @JsonProperty("access_token") String accessToken,
- @JsonProperty("expires_in") Integer expiresIn,
- @JsonProperty("refresh_token") String refreshToken,
- @JsonProperty("refresh_token_expires_in") Integer refreshTokenExpiresIn
-) {
-
-}
diff --git a/backend/src/main/java/com/festago/auth/dto/LoginMember.java b/backend/src/main/java/com/festago/auth/dto/LoginMember.java
deleted file mode 100644
index 48a952fa6..000000000
--- a/backend/src/main/java/com/festago/auth/dto/LoginMember.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.festago.auth.dto;
-
-import io.swagger.v3.oas.annotations.Hidden;
-
-@Hidden
-public record LoginMember(
- Long memberId
-) {
-
-}
diff --git a/backend/src/main/java/com/festago/auth/infrastructure/JwtAuthExtractor.java b/backend/src/main/java/com/festago/auth/infrastructure/JwtAuthExtractor.java
index 5eef34745..32a661e4c 100644
--- a/backend/src/main/java/com/festago/auth/infrastructure/JwtAuthExtractor.java
+++ b/backend/src/main/java/com/festago/auth/infrastructure/JwtAuthExtractor.java
@@ -7,13 +7,17 @@
import com.festago.common.exception.UnauthorizedException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
-import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.MalformedJwtException;
+import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.Keys;
+import io.jsonwebtoken.security.SignatureException;
import java.nio.charset.StandardCharsets;
import javax.crypto.SecretKey;
+import lombok.extern.slf4j.Slf4j;
+@Slf4j
public class JwtAuthExtractor implements AuthExtractor {
private static final String MEMBER_ID_KEY = "memberId";
@@ -42,8 +46,11 @@ private Claims getClaims(String code) {
.getBody();
} catch (ExpiredJwtException e) {
throw new UnauthorizedException(ErrorCode.EXPIRED_AUTH_TOKEN);
- } catch (JwtException | IllegalArgumentException e) {
+ } catch (SignatureException | IllegalArgumentException | MalformedJwtException | UnsupportedJwtException e) {
throw new UnauthorizedException(ErrorCode.INVALID_AUTH_TOKEN);
+ } catch (Exception e) {
+ log.error("JWT 토큰 파싱 중에 문제가 발생했습니다.");
+ throw e;
}
}
}
diff --git a/backend/src/main/java/com/festago/auth/infrastructure/KakaoOAuth2UserInfoErrorHandler.java b/backend/src/main/java/com/festago/auth/infrastructure/KakaoOAuth2UserInfoErrorHandler.java
index 044f4d408..f553f8bb5 100644
--- a/backend/src/main/java/com/festago/auth/infrastructure/KakaoOAuth2UserInfoErrorHandler.java
+++ b/backend/src/main/java/com/festago/auth/infrastructure/KakaoOAuth2UserInfoErrorHandler.java
@@ -4,10 +4,12 @@
import com.festago.common.exception.ErrorCode;
import com.festago.common.exception.InternalServerException;
import java.io.IOException;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;
+@Slf4j
public class KakaoOAuth2UserInfoErrorHandler extends DefaultResponseErrorHandler {
@Override
@@ -15,6 +17,7 @@ public void handleError(ClientHttpResponse response) throws IOException {
HttpStatusCode statusCode = response.getStatusCode();
handle4xxError(statusCode);
handle5xxError(statusCode);
+ log.error("카카오 OAuth2 요청 중 알 수 없는 문제가 발생했습니다.");
throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR);
}
@@ -26,6 +29,7 @@ private void handle4xxError(HttpStatusCode statusCode) {
private void handle5xxError(HttpStatusCode statusCode) {
if (statusCode.is5xxServerError()) {
+ log.warn("카카오 OAuth2 요청에 500 에러가 발생했습니다.");
throw new InternalServerException(ErrorCode.OAUTH2_PROVIDER_NOT_RESPONSE);
}
}
diff --git a/backend/src/main/java/com/festago/common/aop/LogRequestBodyAspect.java b/backend/src/main/java/com/festago/common/aop/LogRequestBodyAspect.java
index e00ad0e9e..90aa094b7 100644
--- a/backend/src/main/java/com/festago/common/aop/LogRequestBodyAspect.java
+++ b/backend/src/main/java/com/festago/common/aop/LogRequestBodyAspect.java
@@ -1,8 +1,6 @@
package com.festago.common.aop;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.festago.common.exception.ErrorCode;
-import com.festago.common.exception.InternalServerException;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Method;
@@ -10,6 +8,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
+import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@@ -22,6 +21,7 @@
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.ContentCachingRequestWrapper;
+@Slf4j
@Component
@Aspect
public class LogRequestBodyAspect {
@@ -86,7 +86,8 @@ private String getRequestPayload(HttpServletRequest request) {
ContentCachingRequestWrapper cachedRequest = (ContentCachingRequestWrapper) request;
return objectMapper.readTree(cachedRequest.getContentAsByteArray()).toPrettyString();
} catch (IOException | ClassCastException e) {
- throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR);
+ log.warn("ObjectMapper에서 직렬화 중에 문제가 발생했습니다.", e);
}
+ return "[ObjectMapper에서 직렬화 중에 문제가 발생했습니다.]";
}
}
diff --git a/backend/src/main/java/com/festago/common/exception/ErrorCode.java b/backend/src/main/java/com/festago/common/exception/ErrorCode.java
index 9c57b4cab..5da1c9a7d 100644
--- a/backend/src/main/java/com/festago/common/exception/ErrorCode.java
+++ b/backend/src/main/java/com/festago/common/exception/ErrorCode.java
@@ -9,14 +9,13 @@ public enum ErrorCode {
INVALID_ENTRY_CODE("올바르지 않은 입장코드입니다."),
INVALID_TICKET_OPEN_TIME("티켓 오픈 시간은 공연 시작 이전 이어야 합니다."),
INVALID_STAGE_START_TIME("공연은 축제 기간 중에만 진행될 수 있습니다."),
- INVALID_MIN_TICKET_AMOUNT("티켓은 적어도 한장 이상 발급해야합니다."),
LATE_TICKET_ENTRY_TIME("입장 시간은 공연 시간보다 빨라야합니다."),
EARLY_TICKET_ENTRY_TIME("입장 시간은 공연 시작 12시간 이내여야 합니다."),
EARLY_TICKET_ENTRY_THAN_OPEN("입장 시간은 티켓 오픈 시간 이후여야합니다."),
TICKET_SOLD_OUT("매진된 티켓입니다."),
INVALID_FESTIVAL_DURATION("축제 시작 일은 종료일 이전이어야 합니다."),
- INVALID_FESTIVAL_START_DATE("축제 시작 일은 과거일 수 없습니다."),
- INVALID_TICKET_CREATE_TIME("티켓 오픈 시간 이후 새롭게 티켓을 발급할 수 없습니다."),
+ INVALID_FESTIVAL_START_DATE("축제 시작 일자는 과거일 수 없습니다."),
+ INVALID_TICKET_CREATE_TIME("티켓 예매 시작 후 새롭게 티켓을 발급할 수 없습니다."),
OAUTH2_NOT_SUPPORTED_SOCIAL_TYPE("해당 OAuth2 제공자는 지원되지 않습니다."),
RESERVE_TICKET_OVER_AMOUNT("예매 가능한 수량을 초과했습니다."),
NEED_STUDENT_VERIFICATION("학생 인증이 필요합니다."),
@@ -29,6 +28,8 @@ public enum ErrorCode {
DELETE_CONSTRAINT_STAGE("티켓이 등록된 공연은 삭제할 수 없습니다."),
DELETE_CONSTRAINT_SCHOOL("학생 또는 축제에 등록된 학교는 삭제할 수 없습니다."),
DUPLICATE_SCHOOL("이미 존재하는 학교 정보입니다."),
+ VALIDATION_FAIL("검증이 실패하였습니다."),
+ INVALID_FESTIVAL_FILTER("유효하지 않은 축제의 필터 값입니다."),
// 401
@@ -55,18 +56,11 @@ public enum ErrorCode {
// 500
INTERNAL_SERVER_ERROR("서버 내부에 문제가 발생했습니다."),
- INVALID_ENTRY_CODE_PERIOD("올바르지 않은 입장코드 유효기간입니다."),
- INVALID_ENTRY_CODE_EXPIRATION_TIME("올바르지 않은 입장코드 만료 일자입니다."),
- INVALID_ENTRY_STATE_INDEX("올바르지 않은 입장상태 인덱스입니다."),
- INVALID_ENTRY_CODE_PAYLOAD("유효하지 않은 입장코드 payload 입니다."),
- INVALID_AUTH_TOKEN_PAYLOAD("유효하지 않은 로그인 토큰 payload 입니다."),
- DUPLICATE_SOCIAL_TYPE("중복된 OAuth2 제공자 입니다."),
OAUTH2_PROVIDER_NOT_RESPONSE("OAuth2 제공자 서버에 문제가 발생했습니다."),
- INVALID_ENTRY_CODE_OFFSET("올바르지 않은 입장코드 오프셋입니다."),
- INVALID_ROLE_NAME("해당하는 Role이 없습니다."),
FOR_TEST_ERROR("테스트용 에러입니다."),
FAIL_SEND_FCM_MESSAGE("FCM Message 전송에 실패했습니다."),
- FCM_NOT_FOUND("유효하지 않은 MemberFCM 이 감지 되었습니다.");
+ FCM_NOT_FOUND("유효하지 않은 MemberFCM이 감지 되었습니다."),
+ ;
private final String message;
diff --git a/backend/src/main/java/com/festago/common/exception/FestaGoException.java b/backend/src/main/java/com/festago/common/exception/FestaGoException.java
index 4bbdab694..0c3baf81c 100644
--- a/backend/src/main/java/com/festago/common/exception/FestaGoException.java
+++ b/backend/src/main/java/com/festago/common/exception/FestaGoException.java
@@ -16,6 +16,11 @@ protected FestaGoException(ErrorCode errorCode, Throwable cause) {
this.errorCode = errorCode;
}
+ protected FestaGoException(ErrorCode errorCode, String message) {
+ super(message);
+ this.errorCode = errorCode;
+ }
+
public ErrorCode getErrorCode() {
return errorCode;
}
diff --git a/backend/src/main/java/com/festago/common/exception/UnexpectedException.java b/backend/src/main/java/com/festago/common/exception/UnexpectedException.java
new file mode 100644
index 000000000..a5414da39
--- /dev/null
+++ b/backend/src/main/java/com/festago/common/exception/UnexpectedException.java
@@ -0,0 +1,8 @@
+package com.festago.common.exception;
+
+public class UnexpectedException extends FestaGoException {
+
+ public UnexpectedException(String message) {
+ super(ErrorCode.INTERNAL_SERVER_ERROR, message);
+ }
+}
diff --git a/backend/src/main/java/com/festago/common/exception/ValidException.java b/backend/src/main/java/com/festago/common/exception/ValidException.java
new file mode 100644
index 000000000..08154be5b
--- /dev/null
+++ b/backend/src/main/java/com/festago/common/exception/ValidException.java
@@ -0,0 +1,8 @@
+package com.festago.common.exception;
+
+public class ValidException extends FestaGoException {
+
+ public ValidException(String message) {
+ super(ErrorCode.VALIDATION_FAIL, message);
+ }
+}
diff --git a/backend/src/main/java/com/festago/common/exception/dto/ErrorResponse.java b/backend/src/main/java/com/festago/common/exception/dto/ErrorResponse.java
index a15d330fc..bf25a4b50 100644
--- a/backend/src/main/java/com/festago/common/exception/dto/ErrorResponse.java
+++ b/backend/src/main/java/com/festago/common/exception/dto/ErrorResponse.java
@@ -2,6 +2,7 @@
import com.festago.common.exception.ErrorCode;
import com.festago.common.exception.FestaGoException;
+import com.festago.common.exception.ValidException;
import java.util.List;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
@@ -21,6 +22,10 @@ public static ErrorResponse from(ErrorCode errorCode) {
return new ErrorResponse(errorCode, errorCode.getMessage());
}
+ public static ErrorResponse from(ValidException e) {
+ return new ErrorResponse(e.getErrorCode(), e.getMessage());
+ }
+
public static ErrorResponse from(ErrorCode errorCode, MethodArgumentNotValidException e) {
List fieldErrors = e.getBindingResult().getFieldErrors();
if (fieldErrors.isEmpty()) {
diff --git a/backend/src/main/java/com/festago/common/util/Validator.java b/backend/src/main/java/com/festago/common/util/Validator.java
index 481a29730..6ae4516aa 100644
--- a/backend/src/main/java/com/festago/common/util/Validator.java
+++ b/backend/src/main/java/com/festago/common/util/Validator.java
@@ -1,49 +1,162 @@
package com.festago.common.util;
+import com.festago.common.exception.UnexpectedException;
+import com.festago.common.exception.ValidException;
+
public final class Validator {
private Validator() {
}
/**
- * 문자열의 최대 길이를 검증합니다. null 값은 무시됩니다. 최대 길이가 0 이하이면 예외를 던집니다. 문자열의 길이가 maxLength보다 작거나 같으면 예외를 던지지 않습니다.
+ * 문자열이 null 또는 공백인지 검사합니다.
+ *
+ * @param input 검증할 문자열
+ * @param fieldName 예외 메시지에 출력할 필드명
+ * @throws ValidException input이 null 또는 공백이면
+ */
+ public static void hasBlank(String input, String fieldName) {
+ if (input == null || input.isBlank()) {
+ throw new ValidException("%s은/는 null 또는 공백이 될 수 없습니다.".formatted(fieldName));
+ }
+ }
+
+ /**
+ * 문자열의 최대 길이를 검증합니다. null 값은 무시됩니다. 최대 길이가 0 이하이면 예외를 던집니다. 문자열의 길이가 maxLength 이하이면 예외를 던지지 않습니다.
*
* @param input 검증할 문자열
* @param maxLength 검증할 문자열의 최대 길이
- * @param message 예외 메시지
- * @throws IllegalArgumentException 문자열의 길이가 초과되거나, 최대 길이가 0 이하이면
+ * @param fieldName 예외 메시지에 출력할 필드명
+ * @throws UnexpectedException 최대 길이가 0 이하이면
+ * @throws ValidException input의 길이가 maxLength 초과하면
*/
- public static void maxLength(CharSequence input, int maxLength, String message) {
+ public static void maxLength(CharSequence input, int maxLength, String fieldName) {
if (maxLength <= 0) {
- throw new IllegalArgumentException("검증 길이는 0보다 커야합니다.");
+ throw new UnexpectedException("최대 길이는 0 이하일 수 없습니다.");
}
// avoid NPE
if (input == null) {
return;
}
if (input.length() > maxLength) {
- throw new IllegalArgumentException(message);
+ throw new ValidException("%s의 길이는 %d글자 이하여야 합니다.".formatted(fieldName, maxLength));
}
}
/**
- * 문자열의 최소 길이를 검증합니다. null 값은 무시됩니다. 최소 길이가 0 이하이면 예외를 던집니다. 문자열의 길이가 minLength보다 크거나 같으면 예외를 던지지 않습니다.
+ * 문자열의 최소 길이를 검증합니다. null 값은 무시됩니다. 최소 길이가 0 이하이면 예외를 던집니다. 문자열의 길이가 minLength보다 이상이면 예외를 던지지 않습니다.
*
* @param input 검증할 문자열
* @param minLength 검증할 문자열의 최소 길이
- * @param message 예외 메시지
- * @throws IllegalArgumentException 문자열의 길이가 작으면, 최대 길이가 0 이하이면
+ * @param fieldName 예외 메시지에 출력할 필드명
+ * @throws UnexpectedException maxLength가 0 이하이면
+ * @throws ValidException input의 길이가 minLength 미만이면
*/
- public static void minLength(CharSequence input, int minLength, String message) {
+ public static void minLength(CharSequence input, int minLength, String fieldName) {
if (minLength <= 0) {
- throw new IllegalArgumentException("검증 길이는 0보다 커야합니다.");
+ throw new UnexpectedException("최소 길이는 0 이하일 수 없습니다.");
}
// avoid NPE
if (input == null) {
return;
}
if (input.length() < minLength) {
- throw new IllegalArgumentException(message);
+ throw new ValidException("%s의 길이는 %d글자 이상이어야 합니다.".formatted(fieldName, minLength));
+ }
+ }
+
+ /**
+ * 객체가 null인지 검사합니다.
+ *
+ * @param object 검증할 객체
+ * @param fieldName 예외 메시지에 출력할 필드명
+ * @throws ValidException object가 null 이면
+ */
+ public static void notNull(Object object, String fieldName) {
+ if (object == null) {
+ throw new ValidException("%s은/는 null이 될 수 없습니다.".formatted(fieldName));
+ }
+ }
+
+ /**
+ * 값의 최대 값을 검증합니다.
+ *
+ * @param value 검증할 값
+ * @param maxValue 검증할 값의 최대 값
+ * @param fieldName 예외 메시지에 출력할 필드명
+ * @throws ValidException value가 maxValue 초과하면
+ */
+ public static void maxValue(int value, int maxValue, String fieldName) {
+ if (value > maxValue) {
+ throw new ValidException("%s은/는 %d 이하여야 합니다.".formatted(fieldName, maxValue));
+ }
+ }
+
+ /**
+ * 값의 최대 값을 검증합니다.
+ *
+ * @param value 검증할 값
+ * @param maxValue 검증할 값의 최대 값
+ * @param fieldName 예외 메시지에 출력할 필드명
+ * @throws ValidException value가 maxValue 초과하면
+ */
+ public static void maxValue(long value, long maxValue, String fieldName) {
+ if (value > maxValue) {
+ throw new ValidException("%s은/는 %d 이하여야 합니다.".formatted(fieldName, maxValue));
+ }
+ }
+
+ /**
+ * 값의 최소 값을 검증합니다.
+ *
+ * @param value 검증할 값
+ * @param minValue 검증할 값의 최소 값
+ * @param fieldName 예외 메시지에 출력할 필드명
+ * @throws ValidException value가 minValue 미만이면
+ */
+ public static void minValue(int value, int minValue, String fieldName) {
+ if (value < minValue) {
+ throw new ValidException("%s은/는 %d 이상이어야 합니다.".formatted(fieldName, minValue));
+ }
+ }
+
+ /**
+ * 값의 최소 값을 검증합니다.
+ *
+ * @param value 검증할 값
+ * @param minValue 검증할 값의 최소 값
+ * @param fieldName 예외 메시지에 출력할 필드명
+ * @throws ValidException value가 minValue 미만이면
+ */
+ public static void minValue(long value, long minValue, String fieldName) {
+ if (value < minValue) {
+ throw new ValidException("%s은/는 %d 이상이어야 합니다.".formatted(fieldName, minValue));
+ }
+ }
+
+ /**
+ * 값이 음수인지 검증합니다.
+ *
+ * @param value 검증할 값
+ * @param fieldName 예외 메시지에 출력할 필드명
+ * @throws ValidException value가 음수이면
+ */
+ public static void isNegative(int value, String fieldName) {
+ if (value < 0) {
+ throw new ValidException("%s은/는 음수가 될 수 없습니다.".formatted(fieldName));
+ }
+ }
+
+ /**
+ * 값이 음수인지 검증합니다.
+ *
+ * @param value 검증할 값
+ * @param fieldName 예외 메시지에 출력할 필드명
+ * @throws ValidException value가 음수이면
+ */
+ public static void isNegative(long value, String fieldName) {
+ if (value < 0) {
+ throw new ValidException("%s은/는 음수가 될 수 없습니다.".formatted(fieldName));
}
}
}
diff --git a/backend/src/main/java/com/festago/entry/domain/EntryCode.java b/backend/src/main/java/com/festago/entry/domain/EntryCode.java
index c75fde867..98da55c23 100644
--- a/backend/src/main/java/com/festago/entry/domain/EntryCode.java
+++ b/backend/src/main/java/com/festago/entry/domain/EntryCode.java
@@ -1,7 +1,7 @@
package com.festago.entry.domain;
-import com.festago.common.exception.ErrorCode;
-import com.festago.common.exception.InternalServerException;
+import com.festago.common.exception.UnexpectedException;
+import org.springframework.util.StringUtils;
public class EntryCode {
@@ -13,18 +13,21 @@ public class EntryCode {
private final long offset;
public EntryCode(String code, long period, long offset) {
- validate(period, offset);
+ validate(code, period, offset);
this.code = code;
this.period = period;
this.offset = offset;
}
- private void validate(long period, long offset) {
+ private void validate(String code, long period, long offset) {
+ if (!StringUtils.hasText(code)) {
+ throw new UnexpectedException("code는 빈 값 또는 null이 될 수 없습니다.");
+ }
if (period <= MINIMUM_PERIOD) {
- throw new InternalServerException(ErrorCode.INVALID_ENTRY_CODE_PERIOD);
+ throw new UnexpectedException("period는 0 또는 음수가 될 수 없습니다.");
}
if (isNegative(offset)) {
- throw new InternalServerException(ErrorCode.INVALID_ENTRY_CODE_OFFSET);
+ throw new UnexpectedException("offset은 음수가 될 수 없습니다.");
}
}
diff --git a/backend/src/main/java/com/festago/entry/domain/EntryCodePayload.java b/backend/src/main/java/com/festago/entry/domain/EntryCodePayload.java
index 38ff7c411..16762a93d 100644
--- a/backend/src/main/java/com/festago/entry/domain/EntryCodePayload.java
+++ b/backend/src/main/java/com/festago/entry/domain/EntryCodePayload.java
@@ -1,7 +1,6 @@
package com.festago.entry.domain;
-import com.festago.common.exception.ErrorCode;
-import com.festago.common.exception.InternalServerException;
+import com.festago.common.exception.UnexpectedException;
import com.festago.ticketing.domain.EntryState;
import com.festago.ticketing.domain.MemberTicket;
@@ -16,16 +15,19 @@ public EntryCodePayload(Long memberTicketId, EntryState entryState) {
this.entryState = entryState;
}
- public static EntryCodePayload from(MemberTicket memberTicket) {
- return new EntryCodePayload(memberTicket.getId(), memberTicket.getEntryState());
- }
-
private void validate(Long memberTicketId, EntryState entryState) {
- if (memberTicketId == null || entryState == null) {
- throw new InternalServerException(ErrorCode.INVALID_ENTRY_CODE_PAYLOAD);
+ if (memberTicketId == null) {
+ throw new UnexpectedException("memberTicketId는 null이 될 수 없습니다.");
+ }
+ if (entryState == null) {
+ throw new UnexpectedException("entryState는 null이 될 수 없습니다.");
}
}
+ public static EntryCodePayload from(MemberTicket memberTicket) {
+ return new EntryCodePayload(memberTicket.getId(), memberTicket.getEntryState());
+ }
+
public Long getMemberTicketId() {
return memberTicketId;
}
diff --git a/backend/src/main/java/com/festago/entry/infrastructure/JwtEntryCodeExtractor.java b/backend/src/main/java/com/festago/entry/infrastructure/JwtEntryCodeExtractor.java
index dbc0bf63c..09c4ed1f0 100644
--- a/backend/src/main/java/com/festago/entry/infrastructure/JwtEntryCodeExtractor.java
+++ b/backend/src/main/java/com/festago/entry/infrastructure/JwtEntryCodeExtractor.java
@@ -7,13 +7,17 @@
import com.festago.ticketing.domain.EntryState;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
-import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.MalformedJwtException;
+import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.Keys;
+import io.jsonwebtoken.security.SignatureException;
import java.nio.charset.StandardCharsets;
import javax.crypto.SecretKey;
+import lombok.extern.slf4j.Slf4j;
+@Slf4j
public class JwtEntryCodeExtractor implements EntryCodeExtractor {
private static final String MEMBER_TICKET_ID_KEY = "ticketId";
@@ -43,8 +47,11 @@ private Claims getClaims(String code) {
.getBody();
} catch (ExpiredJwtException e) {
throw new BadRequestException(ErrorCode.EXPIRED_ENTRY_CODE);
- } catch (JwtException | IllegalArgumentException e) {
+ } catch (SignatureException | IllegalArgumentException | MalformedJwtException | UnsupportedJwtException e) {
throw new BadRequestException(ErrorCode.INVALID_ENTRY_CODE);
+ } catch (Exception e) {
+ log.error("JWT 토큰 파싱 중에 문제가 발생했습니다.");
+ throw e;
}
}
}
diff --git a/backend/src/main/java/com/festago/entry/infrastructure/JwtEntryCodeProvider.java b/backend/src/main/java/com/festago/entry/infrastructure/JwtEntryCodeProvider.java
index 852915e6c..48458f345 100644
--- a/backend/src/main/java/com/festago/entry/infrastructure/JwtEntryCodeProvider.java
+++ b/backend/src/main/java/com/festago/entry/infrastructure/JwtEntryCodeProvider.java
@@ -1,7 +1,6 @@
package com.festago.entry.infrastructure;
-import com.festago.common.exception.ErrorCode;
-import com.festago.common.exception.InternalServerException;
+import com.festago.common.exception.UnexpectedException;
import com.festago.entry.application.EntryCodeProvider;
import com.festago.entry.domain.EntryCodePayload;
import io.jsonwebtoken.Jwts;
@@ -36,7 +35,7 @@ public String provide(EntryCodePayload entryCodePayload, Date expiredAt) {
private void validate(Date expiredAt) {
if (expiredAt.before(new Date())) {
- throw new InternalServerException(ErrorCode.INVALID_ENTRY_CODE_EXPIRATION_TIME);
+ throw new UnexpectedException("입장코드 만료일자는 과거일 수 없습니다.");
}
}
}
diff --git a/backend/src/main/java/com/festago/fcm/domain/MemberFCM.java b/backend/src/main/java/com/festago/fcm/domain/MemberFCM.java
index 8a97f7f07..852f9b615 100644
--- a/backend/src/main/java/com/festago/fcm/domain/MemberFCM.java
+++ b/backend/src/main/java/com/festago/fcm/domain/MemberFCM.java
@@ -1,17 +1,21 @@
package com.festago.fcm.domain;
import com.festago.common.domain.BaseTimeEntity;
+import com.festago.common.util.Validator;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
@Entity
@Table(name = "member_fcm")
public class MemberFCM extends BaseTimeEntity {
+ private static final int MAX_FCM_TOKEN_LENGTH = 255;
+
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@@ -20,6 +24,7 @@ public class MemberFCM extends BaseTimeEntity {
private Long memberId;
@NotNull
+ @Size(max = MAX_FCM_TOKEN_LENGTH)
private String fcmToken;
protected MemberFCM() {
@@ -37,9 +42,18 @@ public MemberFCM(Long id, Long memberId, String fcmToken) {
}
private void validate(Long memberId, String fcmToken) {
- if (memberId == null || fcmToken == null) {
- throw new IllegalArgumentException("MemberFCM 은 허용되지 않은 null 값으로 생성할 수 없습니다.");
- }
+ validateMemberId(memberId);
+ validateFcmToken(fcmToken);
+ }
+
+ private void validateMemberId(Long memberId) {
+ Validator.notNull(memberId, "memberId");
+ }
+
+ private void validateFcmToken(String fcmToken) {
+ String fieldName = "fcmToken";
+ Validator.hasBlank(fcmToken, fieldName);
+ Validator.maxLength(fcmToken, MAX_FCM_TOKEN_LENGTH, fieldName);
}
public Long getId() {
diff --git a/backend/src/main/java/com/festago/festival/application/FestivalService.java b/backend/src/main/java/com/festago/festival/application/FestivalService.java
index 4fe7c285f..5b8185718 100644
--- a/backend/src/main/java/com/festago/festival/application/FestivalService.java
+++ b/backend/src/main/java/com/festago/festival/application/FestivalService.java
@@ -11,6 +11,7 @@
import com.festago.festival.dto.FestivalResponse;
import com.festago.festival.dto.FestivalUpdateRequest;
import com.festago.festival.dto.FestivalsResponse;
+import com.festago.festival.repository.FestivalFilter;
import com.festago.festival.repository.FestivalRepository;
import com.festago.school.domain.School;
import com.festago.school.repository.SchoolRepository;
@@ -19,8 +20,8 @@
import java.time.Clock;
import java.time.LocalDate;
import java.util.List;
-import org.springframework.dao.DataIntegrityViolationException;
import lombok.RequiredArgsConstructor;
+import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -49,8 +50,8 @@ private void validate(Festival festival) {
}
@Transactional(readOnly = true)
- public FestivalsResponse findAll() {
- List festivals = festivalRepository.findAll();
+ public FestivalsResponse findFestivals(FestivalFilter festivalFilter) {
+ List festivals = festivalRepository.findAll(festivalFilter.getSpecification(LocalDate.now(clock)));
return FestivalsResponse.from(festivals);
}
diff --git a/backend/src/main/java/com/festago/festival/domain/Festival.java b/backend/src/main/java/com/festago/festival/domain/Festival.java
index 9b0522c08..1775f2b7a 100644
--- a/backend/src/main/java/com/festago/festival/domain/Festival.java
+++ b/backend/src/main/java/com/festago/festival/domain/Festival.java
@@ -1,6 +1,8 @@
package com.festago.festival.domain;
import com.festago.common.domain.BaseTimeEntity;
+import com.festago.common.exception.BadRequestException;
+import com.festago.common.exception.ErrorCode;
import com.festago.common.util.Validator;
import com.festago.school.domain.School;
import jakarta.persistence.Entity;
@@ -13,7 +15,6 @@
import jakarta.validation.constraints.Size;
import java.time.LocalDate;
import java.time.LocalDateTime;
-import org.springframework.util.Assert;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@@ -22,13 +23,15 @@
public class Festival extends BaseTimeEntity {
private static final String DEFAULT_THUMBNAIL = "https://picsum.photos/536/354";
+ private static final int MAX_NAME_LENGTH = 50;
+ private static final int MAX_THUMBNAIL_LENGTH = 255;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
- @Size(max = 50)
+ @Size(max = MAX_NAME_LENGTH)
private String name;
@NotNull
@@ -38,7 +41,7 @@ public class Festival extends BaseTimeEntity {
private LocalDate endDate;
@NotNull
- @Size(max = 255)
+ @Size(max = MAX_THUMBNAIL_LENGTH)
private String thumbnail;
@NotNull
@@ -70,20 +73,22 @@ private void validate(String name, LocalDate startDate, LocalDate endDate, Strin
}
private void validateName(String name) {
- Assert.notNull(name, "name은 null 값이 될 수 없습니다.");
- Validator.maxLength(name, 50, "name은 50글자를 넘을 수 없습니다.");
+ String fieldName = "name";
+ Validator.hasBlank(name, fieldName);
+ Validator.maxLength(name, MAX_NAME_LENGTH, fieldName);
}
private void validateThumbnail(String thumbnail) {
- Assert.notNull(thumbnail, "thumbnail은 null 값이 될 수 없습니다.");
- Validator.maxLength(thumbnail, 255, "thumbnail은 50글자를 넘을 수 없습니다.");
+ String fieldName = "thumbnail";
+ Validator.hasBlank(thumbnail, fieldName);
+ Validator.maxLength(thumbnail, MAX_THUMBNAIL_LENGTH, fieldName);
}
private void validateDate(LocalDate startDate, LocalDate endDate) {
- Assert.notNull(startDate, "startDate는 null 값이 될 수 없습니다.");
- Assert.notNull(endDate, "endDate는 null 값이 될 수 없습니다.");
+ Validator.notNull(startDate, "startDate");
+ Validator.notNull(endDate, "endDate");
if (startDate.isAfter(endDate)) {
- throw new IllegalArgumentException("축제 시작 일은 종료일 이전이어야 합니다.");
+ throw new BadRequestException(ErrorCode.INVALID_FESTIVAL_DURATION);
}
}
diff --git a/backend/src/main/java/com/festago/festival/repository/FestivalFilter.java b/backend/src/main/java/com/festago/festival/repository/FestivalFilter.java
new file mode 100644
index 000000000..81f60d250
--- /dev/null
+++ b/backend/src/main/java/com/festago/festival/repository/FestivalFilter.java
@@ -0,0 +1,33 @@
+package com.festago.festival.repository;
+
+import com.festago.common.exception.BadRequestException;
+import com.festago.common.exception.ErrorCode;
+import com.festago.festival.domain.Festival;
+import java.time.LocalDate;
+import java.util.function.Function;
+import org.springframework.data.jpa.domain.Specification;
+
+public enum FestivalFilter {
+ PROGRESS(FestivalSpecification::progress),
+ PLANNED(FestivalSpecification::planned),
+ END(FestivalSpecification::end);
+
+ private final Function> filter;
+
+ FestivalFilter(Function> filter) {
+ this.filter = filter;
+ }
+
+ public static FestivalFilter from(String filterName) {
+ return switch (filterName.toUpperCase()) {
+ case "PROGRESS" -> PROGRESS;
+ case "PLANNED" -> PLANNED;
+ case "END" -> END;
+ default -> throw new BadRequestException(ErrorCode.INVALID_FESTIVAL_FILTER);
+ };
+ }
+
+ public Specification getSpecification(LocalDate currentTime) {
+ return filter.apply(currentTime);
+ }
+}
diff --git a/backend/src/main/java/com/festago/festival/repository/FestivalRepository.java b/backend/src/main/java/com/festago/festival/repository/FestivalRepository.java
index 10b3c02c6..032b89a68 100644
--- a/backend/src/main/java/com/festago/festival/repository/FestivalRepository.java
+++ b/backend/src/main/java/com/festago/festival/repository/FestivalRepository.java
@@ -2,7 +2,8 @@
import com.festago.festival.domain.Festival;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
-public interface FestivalRepository extends JpaRepository {
+public interface FestivalRepository extends JpaRepository, JpaSpecificationExecutor {
}
diff --git a/backend/src/main/java/com/festago/festival/repository/FestivalSpecification.java b/backend/src/main/java/com/festago/festival/repository/FestivalSpecification.java
new file mode 100644
index 000000000..50e7efb5c
--- /dev/null
+++ b/backend/src/main/java/com/festago/festival/repository/FestivalSpecification.java
@@ -0,0 +1,38 @@
+package com.festago.festival.repository;
+
+import com.festago.festival.domain.Festival;
+import java.time.LocalDate;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.data.jpa.domain.Specification;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class FestivalSpecification {
+
+ private static final String START_DATE = "startDate";
+ private static final String END_DATE = "endDate";
+
+ public static Specification progress(LocalDate currentTime) {
+ return (root, query, criteriaBuilder) -> {
+ query.orderBy(criteriaBuilder.asc(root.get(START_DATE)));
+ return criteriaBuilder.and(
+ criteriaBuilder.lessThanOrEqualTo(root.get(START_DATE), currentTime),
+ criteriaBuilder.greaterThanOrEqualTo(root.get(END_DATE), currentTime)
+ );
+ };
+ }
+
+ public static Specification planned(LocalDate currentTime) {
+ return (root, query, criteriaBuilder) -> {
+ query.orderBy(criteriaBuilder.asc(root.get(START_DATE)));
+ return criteriaBuilder.greaterThan(root.get(START_DATE), currentTime);
+ };
+ }
+
+ public static Specification end(LocalDate currentTime) {
+ return (root, query, criteriaBuilder) -> {
+ query.orderBy(criteriaBuilder.desc(root.get(END_DATE)));
+ return criteriaBuilder.lessThan(root.get(END_DATE), currentTime);
+ };
+ }
+}
diff --git a/backend/src/main/java/com/festago/member/domain/Member.java b/backend/src/main/java/com/festago/member/domain/Member.java
index 2ee68e879..a7f0e3378 100644
--- a/backend/src/main/java/com/festago/member/domain/Member.java
+++ b/backend/src/main/java/com/festago/member/domain/Member.java
@@ -2,6 +2,7 @@
import com.festago.auth.domain.SocialType;
import com.festago.common.domain.BaseTimeEntity;
+import com.festago.common.util.Validator;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
@@ -17,6 +18,7 @@
import lombok.NoArgsConstructor;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
+import org.springframework.util.StringUtils;
@Entity
@SQLDelete(sql = "UPDATE member SET deleted_at = now(), nickname = '탈퇴한 회원', profile_image = '', social_id = null WHERE id=?")
@@ -36,12 +38,15 @@
public class Member extends BaseTimeEntity {
private static final String DEFAULT_IMAGE_URL = "https://festa-go.site/images/default-profile.png";
+ private static final int MAX_SOCIAL_ID_LENGTH = 255;
+ private static final int MAX_NICKNAME_LENGTH = 30;
+ private static final int MAX_PROFILE_IMAGE_LENGTH = 255;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
- @Size(max = 255)
+ @Size(max = MAX_SOCIAL_ID_LENGTH)
private String socialId;
@NotNull
@@ -49,11 +54,11 @@ public class Member extends BaseTimeEntity {
private SocialType socialType;
@NotNull
- @Size(max = 30)
+ @Size(max = MAX_NICKNAME_LENGTH)
private String nickname;
@NotNull
- @Size(max = 255)
+ @Size(max = MAX_PROFILE_IMAGE_LENGTH)
private String profileImage;
private LocalDateTime deletedAt = null;
@@ -72,35 +77,34 @@ public Member(Long id, String socialId, SocialType socialType, String nickname,
this.socialId = socialId;
this.socialType = socialType;
this.nickname = nickname;
- this.profileImage = (profileImage != null) ? profileImage : DEFAULT_IMAGE_URL;
+ this.profileImage = (StringUtils.hasText(profileImage)) ? profileImage : DEFAULT_IMAGE_URL;
}
private void validate(String socialId, SocialType socialType, String nickname, String profileImage) {
- checkNotNull(socialId, socialType, nickname);
- checkLength(socialId, nickname, profileImage);
+ validateSocialId(socialId);
+ validateSocialType(socialType);
+ validateNickname(nickname);
+ validateProfileImage(profileImage);
}
- private void checkNotNull(String socialId, SocialType socialType, String nickname) {
- if (socialId == null ||
- socialType == null ||
- nickname == null) {
- throw new IllegalArgumentException("Member 는 허용되지 않은 null 값으로 생성할 수 없습니다.");
- }
+ private void validateSocialId(String socialId) {
+ String fieldName = "socialId";
+ Validator.hasBlank(socialId, fieldName);
+ Validator.maxLength(socialId, MAX_SOCIAL_ID_LENGTH, fieldName);
}
- private void checkLength(String socialId, String nickname, String profileImage) {
- if (overLength(socialId, 255) ||
- overLength(nickname, 30) ||
- overLength(profileImage, 255)) {
- throw new IllegalArgumentException("Member 의 필드로 허용된 길이를 넘은 column 을 넣을 수 없습니다.");
- }
+ private void validateSocialType(SocialType socialType) {
+ Validator.notNull(socialType, "socialType");
}
- private boolean overLength(String target, int maxLength) {
- if (target == null) {
- return false;
- }
- return target.length() > maxLength;
+ private void validateNickname(String nickname) {
+ String fieldName = "nickname";
+ Validator.hasBlank(nickname, fieldName);
+ Validator.maxLength(nickname, MAX_NICKNAME_LENGTH, fieldName);
+ }
+
+ private void validateProfileImage(String profileImage) {
+ Validator.maxLength(profileImage, MAX_PROFILE_IMAGE_LENGTH, "profileImage");
}
public Long getId() {
diff --git a/backend/src/main/java/com/festago/presentation/AdminController.java b/backend/src/main/java/com/festago/presentation/AdminController.java
index adb63cff4..62f74a362 100644
--- a/backend/src/main/java/com/festago/presentation/AdminController.java
+++ b/backend/src/main/java/com/festago/presentation/AdminController.java
@@ -1,7 +1,5 @@
package com.festago.presentation;
-import com.festago.admin.application.AdminService;
-import com.festago.admin.dto.AdminResponse;
import com.festago.auth.annotation.Admin;
import com.festago.auth.application.AdminAuthService;
import com.festago.auth.dto.AdminLoginRequest;
@@ -54,7 +52,6 @@ public class AdminController {
private final FestivalService festivalService;
private final StageService stageService;
private final TicketService ticketService;
- private final AdminService adminService;
private final AdminAuthService adminAuthService;
private final SchoolService schoolService;
private final Optional properties;
@@ -147,13 +144,6 @@ private String getCookie(String token) {
.build().toString();
}
- @GetMapping("/data")
- public ResponseEntity adminData() {
- AdminResponse response = adminService.getAdminResponse();
- return ResponseEntity.ok()
- .body(response);
- }
-
@GetMapping("/version")
public ResponseEntity getVersion() {
return properties.map(it -> ResponseEntity.ok(it.getTime().atZone(ZoneId.of("Asia/Seoul")).toString()))
diff --git a/backend/src/main/java/com/festago/presentation/AdminViewController.java b/backend/src/main/java/com/festago/presentation/AdminViewController.java
index 994e2e614..eba8e66c7 100644
--- a/backend/src/main/java/com/festago/presentation/AdminViewController.java
+++ b/backend/src/main/java/com/festago/presentation/AdminViewController.java
@@ -60,7 +60,6 @@ public String manageStagePage(@PathVariable String stageId) {
@ExceptionHandler(UnauthorizedException.class)
public View handle(UnauthorizedException e, HttpServletResponse response) {
-
if (e.getErrorCode() == ErrorCode.EXPIRED_AUTH_TOKEN) {
return new RedirectView("/admin/login");
}
diff --git a/backend/src/main/java/com/festago/presentation/FestivalController.java b/backend/src/main/java/com/festago/presentation/FestivalController.java
index 67b9c94fd..197914043 100644
--- a/backend/src/main/java/com/festago/presentation/FestivalController.java
+++ b/backend/src/main/java/com/festago/presentation/FestivalController.java
@@ -3,6 +3,7 @@
import com.festago.festival.application.FestivalService;
import com.festago.festival.dto.FestivalDetailResponse;
import com.festago.festival.dto.FestivalsResponse;
+import com.festago.festival.repository.FestivalFilter;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
@@ -10,6 +11,7 @@
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@@ -21,9 +23,10 @@ public class FestivalController {
private final FestivalService festivalService;
@GetMapping
- @Operation(description = "모든 축제들을 조회한다.", summary = "축제 목록 조회")
- public ResponseEntity findAll() {
- FestivalsResponse response = festivalService.findAll();
+ @Operation(description = "축제를 조건별로 조회한다. PROGRESS: 진행 중, PLANNED: 진행 예정, END: 종료, 기본값 -> 진행 중", summary = "축제 목록 조회")
+ public ResponseEntity findFestivals(
+ @RequestParam(defaultValue = "PROGRESS") String festivalFilter) {
+ FestivalsResponse response = festivalService.findFestivals(FestivalFilter.from(festivalFilter));
return ResponseEntity.ok()
.body(response);
}
diff --git a/backend/src/main/java/com/festago/presentation/GlobalExceptionHandler.java b/backend/src/main/java/com/festago/presentation/GlobalExceptionHandler.java
index 8c13f7a10..943b39657 100644
--- a/backend/src/main/java/com/festago/presentation/GlobalExceptionHandler.java
+++ b/backend/src/main/java/com/festago/presentation/GlobalExceptionHandler.java
@@ -8,6 +8,8 @@
import com.festago.common.exception.NotFoundException;
import com.festago.common.exception.TooManyRequestException;
import com.festago.common.exception.UnauthorizedException;
+import com.festago.common.exception.UnexpectedException;
+import com.festago.common.exception.ValidException;
import com.festago.common.exception.dto.ErrorResponse;
import com.festago.presentation.auth.AuthenticateContext;
import jakarta.servlet.http.HttpServletRequest;
@@ -29,7 +31,7 @@
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
private static final String LOG_FORMAT_INFO = "\n[🔵INFO] - ({} {})\n(id: {}, role: {})\n{}\n {}: {}";
- private static final String LOG_FORMAT_WARN = "\n[🟠WARN] - ({} {})\n(id: {}, role: {})\n{}\n {}: {}";
+ private static final String LOG_FORMAT_WARN = "\n[🟠WARN] - ({} {})\n(id: {}, role: {})";
private static final String LOG_FORMAT_ERROR = "\n[🔴ERROR] - ({} {})\n(id: {}, role: {})";
// INFO
/*
@@ -45,6 +47,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
(id: 1, role: MEMBER)
FOR_TEST_ERROR
com.festago.exception.InternalServerException: 테스트용 에러입니다.
+ at com.festago.presentation.AdminController.getWarn(AdminController.java:129)
*/
// ERROR
@@ -63,6 +66,16 @@ public ResponseEntity handle(InvalidMediaTypeException e) {
return ResponseEntity.badRequest().build();
}
+ @ExceptionHandler(ValidException.class)
+ public ResponseEntity handle(ValidException e) {
+ return ResponseEntity.badRequest().body(ErrorResponse.from(e));
+ }
+
+ @ExceptionHandler(UnexpectedException.class)
+ public ResponseEntity handle(UnexpectedException e) {
+ return ResponseEntity.internalServerError().body(ErrorResponse.from(e));
+ }
+
@ExceptionHandler(BadRequestException.class)
public ResponseEntity handle(BadRequestException e, HttpServletRequest request) {
logInfo(e, request);
@@ -124,8 +137,8 @@ private void logInfo(FestaGoException e, HttpServletRequest request) {
private void logWarn(FestaGoException e, HttpServletRequest request) {
if (errorLogger.isWarnEnabled()) {
- errorLogger.warn(LOG_FORMAT_WARN, request.getMethod(), request.getRequestURI(), authenticateContext.getId(),
- authenticateContext.getRole(), e.getErrorCode(), e.getClass().getName(), e.getMessage());
+ errorLogger.warn(LOG_FORMAT_WARN, request.getMethod(), request.getRequestURI(),
+ authenticateContext.getId(), authenticateContext.getRole(), e);
}
}
diff --git a/backend/src/main/java/com/festago/presentation/SchoolController.java b/backend/src/main/java/com/festago/presentation/SchoolController.java
index 8667e4597..630f4a270 100644
--- a/backend/src/main/java/com/festago/presentation/SchoolController.java
+++ b/backend/src/main/java/com/festago/presentation/SchoolController.java
@@ -3,6 +3,8 @@
import com.festago.school.application.SchoolService;
import com.festago.school.dto.SchoolResponse;
import com.festago.school.dto.SchoolsResponse;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
@@ -12,18 +14,21 @@
@RestController
@RequestMapping("/schools")
+@Tag(name = "학교 정보 요청")
@RequiredArgsConstructor
public class SchoolController {
private final SchoolService schoolService;
@GetMapping
+ @Operation(description = "모든 학교 정보를 조회한다.", summary = "전체 학교 조회")
public ResponseEntity findAll() {
return ResponseEntity.ok()
.body(schoolService.findAll());
}
@GetMapping("/{schoolId}")
+ @Operation(description = "단일 학교 정보를 조회한다.", summary = "단일 학교 조회")
public ResponseEntity findById(@PathVariable Long schoolId) {
return ResponseEntity.ok()
.body(schoolService.findById(schoolId));
diff --git a/backend/src/main/java/com/festago/presentation/StudentController.java b/backend/src/main/java/com/festago/presentation/StudentController.java
index 96c19f53d..5af069f7e 100644
--- a/backend/src/main/java/com/festago/presentation/StudentController.java
+++ b/backend/src/main/java/com/festago/presentation/StudentController.java
@@ -2,6 +2,7 @@
import com.festago.auth.annotation.Member;
import com.festago.student.application.StudentService;
+import com.festago.student.dto.StudentResponse;
import com.festago.student.dto.StudentSendMailRequest;
import com.festago.student.dto.StudentVerificateRequest;
import io.swagger.v3.oas.annotations.Operation;
@@ -9,6 +10,7 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -33,10 +35,18 @@ public ResponseEntity sendEmail(@Member Long memberId,
@PostMapping("/verification")
@Operation(description = "학교 인증을 수행한다.", summary = "학생 인증 수행")
- public ResponseEntity verificate(@Member Long memberId,
- @RequestBody @Valid StudentVerificateRequest request) {
- studentService.verificate(memberId, request);
+ public ResponseEntity verify(@Member Long memberId,
+ @RequestBody @Valid StudentVerificateRequest request) {
+ studentService.verify(memberId, request);
return ResponseEntity.ok()
.build();
}
+
+ @GetMapping
+ @Operation(description = "학생 인증 정보를 조회한다.", summary = "학생 인증 정보 조회")
+ public ResponseEntity findVerification(@Member Long memberId) {
+ StudentResponse response = studentService.findVerification(memberId);
+ return ResponseEntity.ok()
+ .body(response);
+ }
}
diff --git a/backend/src/main/java/com/festago/school/domain/School.java b/backend/src/main/java/com/festago/school/domain/School.java
index a261c838e..f9bcbae33 100644
--- a/backend/src/main/java/com/festago/school/domain/School.java
+++ b/backend/src/main/java/com/festago/school/domain/School.java
@@ -11,23 +11,25 @@
import jakarta.validation.constraints.Size;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
-import org.springframework.util.Assert;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class School extends BaseTimeEntity {
+ private static final int MAX_DOMAIN_LENGTH = 50;
+ private static final int MAX_NAME_LENGTH = 255;
+
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
- @Size(max = 50)
+ @Size(max = MAX_DOMAIN_LENGTH)
@Column(unique = true)
private String domain;
@NotNull
- @Size(max = 255)
+ @Size(max = MAX_NAME_LENGTH)
@Column(unique = true)
private String name;
@@ -48,13 +50,15 @@ private void validate(String domain, String name) {
}
private void validateDomain(String domain) {
- Assert.notNull(domain, "domain은 null 값이 될 수 없습니다.");
- Validator.maxLength(domain, 50, "domain은 50글자를 넘을 수 없습니다.");
+ String fieldName = "domain";
+ Validator.hasBlank(domain, fieldName);
+ Validator.maxLength(domain, MAX_DOMAIN_LENGTH, fieldName);
}
private void validateName(String name) {
- Assert.notNull(name, "name은 null 값이 될 수 없습니다.");
- Validator.maxLength(name, 255, "name은 255글자를 넘을 수 없습니다.");
+ String fieldName = "name";
+ Validator.hasBlank(name, fieldName);
+ Validator.maxLength(name, MAX_NAME_LENGTH, fieldName);
}
public void changeDomain(String domain) {
diff --git a/backend/src/main/java/com/festago/stage/domain/Stage.java b/backend/src/main/java/com/festago/stage/domain/Stage.java
index 5c5cea7d7..6f0ffd4ea 100644
--- a/backend/src/main/java/com/festago/stage/domain/Stage.java
+++ b/backend/src/main/java/com/festago/stage/domain/Stage.java
@@ -20,12 +20,13 @@
import java.util.List;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
-import org.springframework.util.Assert;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Stage extends BaseTimeEntity {
+ private static final int MAX_LINEUP_LENGTH = 255;
+
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@@ -33,7 +34,7 @@ public class Stage extends BaseTimeEntity {
@NotNull
private LocalDateTime startTime;
- @Size(max = 255)
+ @Size(max = MAX_LINEUP_LENGTH)
private String lineUp;
@NotNull
@@ -71,18 +72,18 @@ private void validate(LocalDateTime startTime, String lineUp, LocalDateTime tick
}
private void validateLineUp(String lineUp) {
- Validator.maxLength(lineUp, 255, "lineUp은 50글자를 넘을 수 없습니다.");
+ Validator.maxLength(lineUp, MAX_LINEUP_LENGTH, "lineUp");
}
private void validateFestival(Festival festival) {
- Assert.notNull(festival, "festival은 null 값이 될 수 없습니다.");
+ Validator.notNull(festival, "festival");
}
private void validateTime(LocalDateTime startTime, LocalDateTime ticketOpenTime, Festival festival) {
- Assert.notNull(startTime, "startTime은 null 값이 될 수 없습니다.");
- Assert.notNull(ticketOpenTime, "ticketOpenTime은 null 값이 될 수 없습니다.");
+ Validator.notNull(startTime, "startTime");
+ Validator.notNull(ticketOpenTime, "ticketOpenTime");
if (ticketOpenTime.isAfter(startTime) || ticketOpenTime.isEqual(startTime)) {
- throw new IllegalArgumentException("티켓 오픈 시간은 공연 시작 이전 이어야 합니다.");
+ throw new BadRequestException(ErrorCode.INVALID_TICKET_OPEN_TIME);
}
if (festival.isNotInDuration(startTime)) {
throw new BadRequestException(ErrorCode.INVALID_STAGE_START_TIME);
diff --git a/backend/src/main/java/com/festago/student/application/StudentService.java b/backend/src/main/java/com/festago/student/application/StudentService.java
index 82c1ba037..6d0b9104f 100644
--- a/backend/src/main/java/com/festago/student/application/StudentService.java
+++ b/backend/src/main/java/com/festago/student/application/StudentService.java
@@ -12,6 +12,7 @@
import com.festago.student.domain.StudentCode;
import com.festago.student.domain.VerificationCode;
import com.festago.student.domain.VerificationMailPayload;
+import com.festago.student.dto.StudentResponse;
import com.festago.student.dto.StudentSendMailRequest;
import com.festago.student.dto.StudentVerificateRequest;
import com.festago.student.repository.StudentCodeRepository;
@@ -89,7 +90,7 @@ private void saveStudentCode(VerificationCode code, Member member, School school
);
}
- public void verificate(Long memberId, StudentVerificateRequest request) {
+ public void verify(Long memberId, StudentVerificateRequest request) {
validateStudent(memberId);
Member member = findMember(memberId);
StudentCode studentCode = studentCodeRepository.findByCodeAndMember(new VerificationCode(request.code()),
@@ -98,4 +99,10 @@ public void verificate(Long memberId, StudentVerificateRequest request) {
studentRepository.save(new Student(member, studentCode.getSchool(), studentCode.getUsername()));
studentCodeRepository.deleteByMember(member);
}
+
+ public StudentResponse findVerification(Long memberId) {
+ return studentRepository.findByMemberIdWithFetch(memberId)
+ .map(value -> StudentResponse.verified(value.getSchool()))
+ .orElseGet(StudentResponse::notVerified);
+ }
}
diff --git a/backend/src/main/java/com/festago/student/domain/Student.java b/backend/src/main/java/com/festago/student/domain/Student.java
index b1559d660..7857d6a8d 100644
--- a/backend/src/main/java/com/festago/student/domain/Student.java
+++ b/backend/src/main/java/com/festago/student/domain/Student.java
@@ -1,8 +1,7 @@
package com.festago.student.domain;
import com.festago.common.domain.BaseTimeEntity;
-import com.festago.common.exception.ErrorCode;
-import com.festago.common.exception.InternalServerException;
+import com.festago.common.util.Validator;
import com.festago.member.domain.Member;
import com.festago.school.domain.School;
import jakarta.persistence.Entity;
@@ -21,6 +20,8 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Student extends BaseTimeEntity {
+ private static final int MAX_USERNAME_LENGTH = 255;
+
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@@ -33,7 +34,7 @@ public class Student extends BaseTimeEntity {
@ManyToOne(fetch = FetchType.LAZY)
private School school;
- @Size(max = 255)
+ @Size(max = MAX_USERNAME_LENGTH)
@NotNull
private String username;
@@ -50,29 +51,23 @@ public Student(Long id, Member member, School school, String username) {
}
private void validate(Member member, School school, String username) {
- checkNotNull(member, school, username);
- checkLength(username);
+ validateMember(member);
+ validateSchool(school);
+ validateUsername(username);
}
- private void checkNotNull(Member member, School school, String username) {
- if (member == null ||
- school == null ||
- username == null) {
- throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR);
- }
+ private void validateMember(Member member) {
+ Validator.notNull(member, "member");
}
- private void checkLength(String username) {
- if (overLength(username, 255)) {
- throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR);
- }
+ private void validateSchool(School school) {
+ Validator.notNull(school, "school");
}
- private boolean overLength(String target, int maxLength) {
- if (target == null) {
- return false;
- }
- return target.length() > maxLength;
+ private void validateUsername(String username) {
+ String fieldName = "username";
+ Validator.hasBlank(username, fieldName);
+ Validator.maxLength(username, MAX_USERNAME_LENGTH, fieldName);
}
public Long getId() {
diff --git a/backend/src/main/java/com/festago/student/domain/StudentCode.java b/backend/src/main/java/com/festago/student/domain/StudentCode.java
index 899993530..6a2468b15 100644
--- a/backend/src/main/java/com/festago/student/domain/StudentCode.java
+++ b/backend/src/main/java/com/festago/student/domain/StudentCode.java
@@ -2,8 +2,7 @@
import static java.time.temporal.ChronoUnit.SECONDS;
-import com.festago.common.exception.ErrorCode;
-import com.festago.common.exception.InternalServerException;
+import com.festago.common.util.Validator;
import com.festago.member.domain.Member;
import com.festago.school.domain.School;
import jakarta.persistence.Embedded;
@@ -17,12 +16,12 @@
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
-import org.springframework.util.StringUtils;
@Entity
@EntityListeners(AuditingEntityListener.class)
@@ -30,6 +29,7 @@
public class StudentCode {
private static final int MIN_REQUEST_TERM_SECONDS = 30;
+ private static final int MAX_USERNAME_LENGTH = 255;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -45,6 +45,8 @@ public class StudentCode {
@JoinColumn(unique = true)
private Member member;
+ @NotNull
+ @Size(max = MAX_USERNAME_LENGTH)
private String username;
@NotNull
@@ -57,7 +59,7 @@ public StudentCode(VerificationCode code, School school, Member member, String u
public StudentCode(Long id, VerificationCode code, School school, Member member, String username,
LocalDateTime issuedAt) {
- validate(username);
+ validate(code, school, member, username);
this.id = id;
this.code = code;
this.school = school;
@@ -66,14 +68,29 @@ public StudentCode(Long id, VerificationCode code, School school, Member member,
this.issuedAt = issuedAt;
}
- private void validate(String username) {
- if (!StringUtils.hasText(username)) {
- throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR);
- }
+ private void validate(VerificationCode code, School school, Member member, String username) {
+ validateVerificationCode(code);
+ validateSchool(school);
+ validateMember(member);
+ validateUsername(username);
+ }
+
+ private void validateVerificationCode(VerificationCode code) {
+ Validator.notNull(code, "validateVerificationCode");
+ }
+
+ private void validateSchool(School school) {
+ Validator.notNull(school, "school");
+ }
+
+ private void validateMember(Member member) {
+ Validator.notNull(member, "member");
+ }
- if (username.length() > 255) {
- throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR);
- }
+ private void validateUsername(String username) {
+ String fieldName = "username";
+ Validator.hasBlank(username, fieldName);
+ Validator.maxLength(username, MAX_USERNAME_LENGTH, fieldName);
}
public boolean canReissue(LocalDateTime currentTime) {
diff --git a/backend/src/main/java/com/festago/student/domain/VerificationCode.java b/backend/src/main/java/com/festago/student/domain/VerificationCode.java
index 4c5f751be..359e6889a 100644
--- a/backend/src/main/java/com/festago/student/domain/VerificationCode.java
+++ b/backend/src/main/java/com/festago/student/domain/VerificationCode.java
@@ -1,12 +1,12 @@
package com.festago.student.domain;
-import com.festago.common.exception.ErrorCode;
-import com.festago.common.exception.InternalServerException;
+import com.festago.common.exception.UnexpectedException;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import java.util.regex.Pattern;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
+import org.springframework.util.StringUtils;
@Embeddable
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@@ -23,26 +23,26 @@ public VerificationCode(String value) {
}
private void validate(String value) {
- validateNull(value);
+ validateBlank(value);
validateLength(value);
validatePositive(value);
}
- private void validateNull(String value) {
- if (value == null) {
- throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR);
+ private void validateBlank(String value) {
+ if (!StringUtils.hasText(value)) {
+ throw new UnexpectedException("VerificationCode는 null 또는 공백이 될 수 없습니다.");
}
}
private void validateLength(String value) {
if (value.length() != LENGTH) {
- throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR);
+ throw new UnexpectedException("VerificationCode의 길이는 %d 이어야 합니다.".formatted(LENGTH));
}
}
private void validatePositive(String value) {
if (!POSITIVE_REGEX.matcher(value).matches()) {
- throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR);
+ throw new UnexpectedException("VerificationCode는 0~9의 양수 형식이어야 합니다.");
}
}
diff --git a/backend/src/main/java/com/festago/student/domain/VerificationMailPayload.java b/backend/src/main/java/com/festago/student/domain/VerificationMailPayload.java
index e6e159d06..6a07dc575 100644
--- a/backend/src/main/java/com/festago/student/domain/VerificationMailPayload.java
+++ b/backend/src/main/java/com/festago/student/domain/VerificationMailPayload.java
@@ -1,13 +1,14 @@
package com.festago.student.domain;
-import com.festago.common.exception.ErrorCode;
-import com.festago.common.exception.InternalServerException;
+import com.festago.common.exception.UnexpectedException;
+import com.festago.common.exception.ValidException;
+import org.springframework.util.StringUtils;
public class VerificationMailPayload {
- private VerificationCode code;
- private String username;
- private String domain;
+ private final VerificationCode code;
+ private final String username;
+ private final String domain;
public VerificationMailPayload(VerificationCode code, String username, String domain) {
validate(code, username, domain);
@@ -17,8 +18,14 @@ public VerificationMailPayload(VerificationCode code, String username, String do
}
private void validate(VerificationCode code, String username, String domain) {
- if (code == null || username == null || domain == null) {
- throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR);
+ if (code == null) {
+ throw new UnexpectedException("code는 null이 될 수 없습니다.");
+ }
+ if (!StringUtils.hasText(username)) {
+ throw new ValidException("username은 null 또는 공백이 될 수 없습니다.");
+ }
+ if (!StringUtils.hasText(domain)) {
+ throw new UnexpectedException("domain은 null 또는 공백이 될 수 없습니다.");
}
}
diff --git a/backend/src/main/java/com/festago/student/dto/StudentResponse.java b/backend/src/main/java/com/festago/student/dto/StudentResponse.java
new file mode 100644
index 000000000..e870ae88a
--- /dev/null
+++ b/backend/src/main/java/com/festago/student/dto/StudentResponse.java
@@ -0,0 +1,22 @@
+package com.festago.student.dto;
+
+import com.festago.school.domain.School;
+
+public record StudentResponse(
+ boolean isVerified,
+ StudentSchoolResponse school
+) {
+
+ private static final StudentResponse NOT_VERIFIED = new StudentResponse(false, null);
+
+ public static StudentResponse verified(School school) {
+ return new StudentResponse(
+ true,
+ StudentSchoolResponse.from(school)
+ );
+ }
+
+ public static StudentResponse notVerified() {
+ return NOT_VERIFIED;
+ }
+}
diff --git a/backend/src/main/java/com/festago/student/dto/StudentSchoolResponse.java b/backend/src/main/java/com/festago/student/dto/StudentSchoolResponse.java
new file mode 100644
index 000000000..479fc5cd9
--- /dev/null
+++ b/backend/src/main/java/com/festago/student/dto/StudentSchoolResponse.java
@@ -0,0 +1,18 @@
+package com.festago.student.dto;
+
+import com.festago.school.domain.School;
+
+public record StudentSchoolResponse(
+ Long id,
+ String schoolName,
+ String domain
+) {
+
+ public static StudentSchoolResponse from(School school) {
+ return new StudentSchoolResponse(
+ school.getId(),
+ school.getName(),
+ school.getDomain()
+ );
+ }
+}
diff --git a/backend/src/main/java/com/festago/student/repository/StudentRepository.java b/backend/src/main/java/com/festago/student/repository/StudentRepository.java
index f196c95ba..9db2ab470 100644
--- a/backend/src/main/java/com/festago/student/repository/StudentRepository.java
+++ b/backend/src/main/java/com/festago/student/repository/StudentRepository.java
@@ -2,7 +2,10 @@
import com.festago.member.domain.Member;
import com.festago.student.domain.Student;
+import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
public interface StudentRepository extends JpaRepository {
@@ -11,4 +14,12 @@ public interface StudentRepository extends JpaRepository {
boolean existsByUsernameAndSchoolId(String username, Long id);
boolean existsByMemberId(Long id);
+
+ @Query("""
+ SELECT st
+ FROM Student st
+ INNER JOIN FETCH st.school sc
+ WHERE st.member.id = :memberId
+ """)
+ Optional findByMemberIdWithFetch(@Param("memberId") Long memberId);
}
diff --git a/backend/src/main/java/com/festago/ticket/application/TicketService.java b/backend/src/main/java/com/festago/ticket/application/TicketService.java
index 74e72d5f4..fe9424e52 100644
--- a/backend/src/main/java/com/festago/ticket/application/TicketService.java
+++ b/backend/src/main/java/com/festago/ticket/application/TicketService.java
@@ -46,6 +46,6 @@ private Stage findStageById(Long stageId) {
@Transactional(readOnly = true)
public StageTicketsResponse findStageTickets(Long stageId) {
- return StageTicketsResponse.from(ticketRepository.findAllByStageId(stageId));
+ return StageTicketsResponse.from(ticketRepository.findAllByStageIdWithFetch(stageId));
}
}
diff --git a/backend/src/main/java/com/festago/ticket/domain/Ticket.java b/backend/src/main/java/com/festago/ticket/domain/Ticket.java
index b371db699..6c0e76abb 100644
--- a/backend/src/main/java/com/festago/ticket/domain/Ticket.java
+++ b/backend/src/main/java/com/festago/ticket/domain/Ticket.java
@@ -3,6 +3,7 @@
import com.festago.common.domain.BaseTimeEntity;
import com.festago.common.exception.BadRequestException;
import com.festago.common.exception.ErrorCode;
+import com.festago.common.util.Validator;
import com.festago.member.domain.Member;
import com.festago.school.domain.School;
import com.festago.stage.domain.Stage;
@@ -70,7 +71,7 @@ public Ticket(Long id, Stage stage, TicketType ticketType, School school) {
}
public Ticket(Long id, Stage stage, TicketType ticketType, Long schoolId) {
- validate(stage, ticketType);
+ validate(stage, ticketType, schoolId);
this.id = id;
this.stage = stage;
this.ticketType = ticketType;
@@ -78,15 +79,22 @@ public Ticket(Long id, Stage stage, TicketType ticketType, Long schoolId) {
this.schoolId = schoolId;
}
- private void validate(Stage stage, TicketType ticketType) {
- checkNotNull(stage, ticketType);
+ private void validate(Stage stage, TicketType ticketType, Long schoolId) {
+ validateStage(stage);
+ validateTicketType(ticketType);
+ validateSchoolId(schoolId);
}
- private void checkNotNull(Stage stage, TicketType ticketType) {
- if (stage == null ||
- ticketType == null) {
- throw new IllegalArgumentException("Ticket 은 허용되지 않은 null 값으로 생성할 수 없습니다.");
- }
+ private void validateStage(Stage stage) {
+ Validator.notNull(stage, "stage");
+ }
+
+ private void validateTicketType(TicketType ticketType) {
+ Validator.notNull(ticketType, "ticketType");
+ }
+
+ private void validateSchoolId(Long schoolId) {
+ Validator.notNull(schoolId, "schoolId");
}
public void addTicketEntryTime(LocalDateTime currentTime, LocalDateTime entryTime, int amount) {
diff --git a/backend/src/main/java/com/festago/ticket/domain/TicketAmount.java b/backend/src/main/java/com/festago/ticket/domain/TicketAmount.java
index 738ccfc6c..9665b4517 100644
--- a/backend/src/main/java/com/festago/ticket/domain/TicketAmount.java
+++ b/backend/src/main/java/com/festago/ticket/domain/TicketAmount.java
@@ -3,6 +3,7 @@
import com.festago.common.domain.BaseTimeEntity;
import com.festago.common.exception.BadRequestException;
import com.festago.common.exception.ErrorCode;
+import com.festago.common.util.Validator;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
@@ -36,13 +37,7 @@ public TicketAmount(Ticket ticket) {
}
private void validate(Ticket ticket) {
- checkNotNull(ticket);
- }
-
- private void checkNotNull(Ticket ticket) {
- if (ticket == null) {
- throw new IllegalArgumentException("TicketAmount 는 허용되지 않은 null 값으로 생성할 수 없습니다.");
- }
+ Validator.notNull(ticket, "ticket");
}
public void increaseReservedAmount() {
diff --git a/backend/src/main/java/com/festago/ticket/domain/TicketEntryTime.java b/backend/src/main/java/com/festago/ticket/domain/TicketEntryTime.java
index f26cf7d4b..baf1dbb99 100644
--- a/backend/src/main/java/com/festago/ticket/domain/TicketEntryTime.java
+++ b/backend/src/main/java/com/festago/ticket/domain/TicketEntryTime.java
@@ -1,8 +1,7 @@
package com.festago.ticket.domain;
import com.festago.common.domain.BaseTimeEntity;
-import com.festago.common.exception.BadRequestException;
-import com.festago.common.exception.ErrorCode;
+import com.festago.common.util.Validator;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
@@ -17,7 +16,7 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class TicketEntryTime extends BaseTimeEntity implements Comparable {
- private static final int MIN_TOTAL_AMOUNT = 1;
+ private static final int MIN_AMOUNT_VALUE = 1;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -26,7 +25,7 @@ public class TicketEntryTime extends BaseTimeEntity implements Comparable {
- List findAllByStageId(Long stageId);
+ @Query("""
+ SELECT t from Ticket t
+ INNER JOIN FETCH t.ticketAmount
+ WHERE t.stage.id = :stageId
+ """)
+ List findAllByStageIdWithFetch(@Param("stageId") Long stageId);
Optional findByTicketTypeAndStage(TicketType ticketType, Stage stage);
diff --git a/backend/src/main/java/com/festago/ticketing/domain/EntryState.java b/backend/src/main/java/com/festago/ticketing/domain/EntryState.java
index 76c68a288..a0f172ddc 100644
--- a/backend/src/main/java/com/festago/ticketing/domain/EntryState.java
+++ b/backend/src/main/java/com/festago/ticketing/domain/EntryState.java
@@ -1,7 +1,6 @@
package com.festago.ticketing.domain;
-import com.festago.common.exception.ErrorCode;
-import com.festago.common.exception.InternalServerException;
+import com.festago.common.exception.UnexpectedException;
public enum EntryState {
BEFORE_ENTRY(0),
@@ -16,18 +15,18 @@ public enum EntryState {
}
public static EntryState from(Integer index) {
- validate(index);
+ validateNull(index);
return switch (index) {
case 0 -> BEFORE_ENTRY;
case 1 -> AFTER_ENTRY;
case 2 -> AWAY;
- default -> throw new InternalServerException(ErrorCode.INVALID_ENTRY_STATE_INDEX);
+ default -> throw new UnexpectedException("entryState의 인덱스가 올바르지 않습니다. index: " + index);
};
}
- private static void validate(Integer index) {
+ private static void validateNull(Integer index) {
if (index == null) {
- throw new InternalServerException(ErrorCode.INVALID_ENTRY_STATE_INDEX);
+ throw new UnexpectedException("entryState의 인덱스는 null이 될 수 없습니다.");
}
}
diff --git a/backend/src/main/java/com/festago/ticketing/domain/MemberTicket.java b/backend/src/main/java/com/festago/ticketing/domain/MemberTicket.java
index ec5f881f2..b7b31cf9e 100644
--- a/backend/src/main/java/com/festago/ticketing/domain/MemberTicket.java
+++ b/backend/src/main/java/com/festago/ticketing/domain/MemberTicket.java
@@ -1,6 +1,7 @@
package com.festago.ticketing.domain;
import com.festago.common.domain.BaseTimeEntity;
+import com.festago.common.util.Validator;
import com.festago.member.domain.Member;
import com.festago.stage.domain.Stage;
import com.festago.ticket.domain.TicketType;
@@ -24,6 +25,7 @@
public class MemberTicket extends BaseTimeEntity {
private static final long ENTRY_LIMIT_HOUR = 24;
+ private static final int MIN_NUMBER_VALUE = 1;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -41,7 +43,7 @@ public class MemberTicket extends BaseTimeEntity {
@ManyToOne(fetch = FetchType.LAZY)
private Stage stage;
- @Min(value = 0)
+ @Min(value = MIN_NUMBER_VALUE)
private int number;
@NotNull
@@ -67,23 +69,31 @@ public MemberTicket(Long id, Member owner, Stage stage, int number, LocalDateTim
}
private void validate(Member owner, Stage stage, int number, LocalDateTime entryTime, TicketType ticketType) {
- checkNotNull(owner, stage, entryTime, ticketType);
- checkScope(number);
+ validateOwner(owner);
+ validateStage(stage);
+ validateNumber(number);
+ validateEntryTime(entryTime);
+ validateTicketType(ticketType);
}
- private void checkNotNull(Member owner, Stage stage, LocalDateTime entryTime, TicketType ticketType) {
- if (owner == null ||
- stage == null ||
- entryTime == null ||
- ticketType == null) {
- throw new IllegalArgumentException("MemberTicket 은 허용되지 않은 null 값으로 생성할 수 없습니다.");
- }
+ private void validateOwner(Member owner) {
+ Validator.notNull(owner, "owner");
}
- private void checkScope(int number) {
- if (number < 0) {
- throw new IllegalArgumentException("MemberTicket 의 필드로 허용된 범위를 넘은 column 을 넣을 수 없습니다.");
- }
+ private void validateStage(Stage stage) {
+ Validator.notNull(stage, "stage");
+ }
+
+ private void validateNumber(int number) {
+ Validator.minValue(number, MIN_NUMBER_VALUE, "number");
+ }
+
+ private void validateEntryTime(LocalDateTime entryTime) {
+ Validator.notNull(entryTime, "entryTime");
+ }
+
+ private void validateTicketType(TicketType ticketType) {
+ Validator.notNull(ticketType, "ticketType");
}
public void changeState(EntryState originState) {
diff --git a/backend/src/main/resources/db/migration/V9__festival_date_index.sql b/backend/src/main/resources/db/migration/V9__festival_date_index.sql
new file mode 100644
index 000000000..e52df8ad8
--- /dev/null
+++ b/backend/src/main/resources/db/migration/V9__festival_date_index.sql
@@ -0,0 +1,4 @@
+create index festival_start_date_index
+ on festival (start_date);
+create index festival_end_date_index
+ on festival (end_date desc);
diff --git a/backend/src/main/resources/festago-config b/backend/src/main/resources/festago-config
index 3214a7b3a..0eec697d8 160000
--- a/backend/src/main/resources/festago-config
+++ b/backend/src/main/resources/festago-config
@@ -1 +1 @@
-Subproject commit 3214a7b3a466f5878fd957c130f47e94f36e5893
+Subproject commit 0eec697d85125fa4c6526365fde41f66e8e348e4
diff --git a/backend/src/test/java/com/festago/admin/domain/AdminTest.java b/backend/src/test/java/com/festago/admin/domain/AdminTest.java
new file mode 100644
index 000000000..46492486b
--- /dev/null
+++ b/backend/src/test/java/com/festago/admin/domain/AdminTest.java
@@ -0,0 +1,64 @@
+package com.festago.admin.domain;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+import com.festago.common.exception.ValidException;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
+import org.junit.jupiter.api.Test;
+
+@DisplayNameGeneration(ReplaceUnderscores.class)
+@SuppressWarnings("NonAsciiCharacters")
+class AdminTest {
+
+ @Test
+ void 어드민_생성_성공() {
+ // given
+ Admin admin = new Admin("admin", "password");
+
+ // when & then
+ assertThat(admin.getUsername()).isEqualTo("admin");
+ assertThat(admin.getPassword()).isEqualTo("password");
+ }
+
+ @Test
+ void username이_4글자_미만이면_예외() {
+ // given
+ String username = "1".repeat(3);
+
+ // when & then
+ assertThatThrownBy(() -> new Admin(username, "password"))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @Test
+ void username이_20글자_초과하면_예외() {
+ // given
+ String username = "1".repeat(21);
+
+ // when & then
+ assertThatThrownBy(() -> new Admin(username, "password"))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @Test
+ void password가_4글자_미만이면_예외() {
+ // given
+ String password = "1".repeat(3);
+
+ // when & then
+ assertThatThrownBy(() -> new Admin("admin", password))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @Test
+ void password가_255글자_초과하면_예외() {
+ // given
+ String password = "1".repeat(256);
+
+ // when & then
+ assertThatThrownBy(() -> new Admin("admin", password))
+ .isInstanceOf(ValidException.class);
+ }
+}
diff --git a/backend/src/test/java/com/festago/auth/application/integration/AuthFacadeServiceIntegrationTest.java b/backend/src/test/java/com/festago/auth/application/integration/AuthFacadeServiceIntegrationTest.java
index 4024fe487..d894f8a96 100644
--- a/backend/src/test/java/com/festago/auth/application/integration/AuthFacadeServiceIntegrationTest.java
+++ b/backend/src/test/java/com/festago/auth/application/integration/AuthFacadeServiceIntegrationTest.java
@@ -3,11 +3,11 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
-import com.festago.application.integration.ApplicationIntegrationTest;
import com.festago.auth.application.AuthFacadeService;
import com.festago.auth.domain.SocialType;
import com.festago.member.domain.Member;
import com.festago.member.repository.MemberRepository;
+import com.festago.support.ApplicationIntegrationTest;
import com.festago.support.MemberFixture;
import jakarta.persistence.EntityManager;
import java.util.List;
diff --git a/backend/src/test/java/com/festago/auth/domain/OAuth2ClientsTest.java b/backend/src/test/java/com/festago/auth/domain/OAuth2ClientsTest.java
index f1df89a40..ee021dc4f 100644
--- a/backend/src/test/java/com/festago/auth/domain/OAuth2ClientsTest.java
+++ b/backend/src/test/java/com/festago/auth/domain/OAuth2ClientsTest.java
@@ -1,6 +1,5 @@
package com.festago.auth.domain;
-import static com.festago.common.exception.ErrorCode.DUPLICATE_SOCIAL_TYPE;
import static com.festago.common.exception.ErrorCode.OAUTH2_NOT_SUPPORTED_SOCIAL_TYPE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -13,7 +12,7 @@
import com.festago.auth.infrastructure.KakaoOAuth2Client;
import com.festago.auth.infrastructure.KakaoOAuth2UserInfoClient;
import com.festago.common.exception.BadRequestException;
-import com.festago.common.exception.InternalServerException;
+import com.festago.common.exception.UnexpectedException;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
import org.junit.jupiter.api.Test;
@@ -33,8 +32,8 @@ class OAuth2ClientsTest {
// when & then
assertThatThrownBy(() -> builder.add(new FestagoOAuth2Client()))
- .isInstanceOf(InternalServerException.class)
- .hasMessage(DUPLICATE_SOCIAL_TYPE.getMessage());
+ .isInstanceOf(UnexpectedException.class)
+ .hasMessage("중복된 OAuth2 제공자 입니다.");
}
@Test
diff --git a/backend/src/test/java/com/festago/auth/infrastructure/JwtAuthExtractorTest.java b/backend/src/test/java/com/festago/auth/infrastructure/JwtAuthExtractorTest.java
index 2af3d66f8..7835cdf74 100644
--- a/backend/src/test/java/com/festago/auth/infrastructure/JwtAuthExtractorTest.java
+++ b/backend/src/test/java/com/festago/auth/infrastructure/JwtAuthExtractorTest.java
@@ -2,13 +2,12 @@
import static com.festago.common.exception.ErrorCode.EXPIRED_AUTH_TOKEN;
import static com.festago.common.exception.ErrorCode.INVALID_AUTH_TOKEN;
-import static com.festago.common.exception.ErrorCode.INVALID_ROLE_NAME;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import com.festago.auth.domain.AuthPayload;
import com.festago.auth.domain.Role;
-import com.festago.common.exception.InternalServerException;
import com.festago.common.exception.UnauthorizedException;
+import com.festago.common.exception.UnexpectedException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
@@ -85,8 +84,8 @@ class JwtAuthExtractorTest {
// when & then
assertThatThrownBy(() -> jwtAuthExtractor.extract(token))
- .isInstanceOf(InternalServerException.class)
- .hasMessage(INVALID_ROLE_NAME.getMessage());
+ .isInstanceOf(UnexpectedException.class)
+ .hasMessage("해당하는 Role이 없습니다.");
}
@Test
diff --git a/backend/src/test/java/com/festago/exception/FestaGoExceptionTest.java b/backend/src/test/java/com/festago/common/exception/FestaGoExceptionTest.java
similarity index 92%
rename from backend/src/test/java/com/festago/exception/FestaGoExceptionTest.java
rename to backend/src/test/java/com/festago/common/exception/FestaGoExceptionTest.java
index b71a58dea..eb14ace99 100644
--- a/backend/src/test/java/com/festago/exception/FestaGoExceptionTest.java
+++ b/backend/src/test/java/com/festago/common/exception/FestaGoExceptionTest.java
@@ -1,10 +1,7 @@
-package com.festago.exception;
+package com.festago.common.exception;
import static org.assertj.core.api.Assertions.assertThat;
-import com.festago.common.exception.ErrorCode;
-import com.festago.common.exception.FestaGoException;
-import com.festago.common.exception.InternalServerException;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
import org.junit.jupiter.api.Test;
diff --git a/backend/src/test/java/com/festago/common/util/ValidatorTest.java b/backend/src/test/java/com/festago/common/util/ValidatorTest.java
index 8b8cf9abd..dad4a00dd 100644
--- a/backend/src/test/java/com/festago/common/util/ValidatorTest.java
+++ b/backend/src/test/java/com/festago/common/util/ValidatorTest.java
@@ -3,65 +3,235 @@
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import com.festago.common.exception.UnexpectedException;
+import com.festago.common.exception.ValidException;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
@DisplayNameGeneration(ReplaceUnderscores.class)
@SuppressWarnings("NonAsciiCharacters")
class ValidatorTest {
- @Test
- void 문자열_maxLength_검증_성공() {
- // given
- String input = "1234567890"; // 10
+ @Nested
+ class hasBlank {
- // when & then
- assertThatNoException()
- .isThrownBy(() -> Validator.maxLength(input, 10, ""));
+ @ParameterizedTest
+ @NullSource
+ void 문자열이_null이면_예외(String input) {
+ // when & then
+ assertThatThrownBy(() -> Validator.hasBlank(input, ""))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"", " ", "\t", "\n"})
+ void 문자열이_공백이면_예외(String input) {
+ // when & then
+ assertThatThrownBy(() -> Validator.hasBlank(input, ""))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @Test
+ void 문자열이_공백이_아니면_통과() {
+ // given
+ String input = "1";
+
+ // when & then
+ assertThatNoException()
+ .isThrownBy(() -> Validator.hasBlank(input, ""));
+ }
+ }
+
+
+ @Nested
+ class maxLength {
+
+ @Test
+ void 문자열의_길이가_10이고_최대_길이가_10이면_통과() {
+ // given
+ String input = "1234567890"; // 10
+ int maxLength = 10;
+
+ // when & then
+ assertThatNoException()
+ .isThrownBy(() -> Validator.maxLength(input, maxLength, ""));
+ }
+
+ @Test
+ void 문자열의_길이가_11이고_최대_길이가_10이면_예외() {
+ // given
+ String input = "12345678901"; // 11
+ int maxLength = 10;
+
+ // when & then
+ assertThatThrownBy(() -> Validator.maxLength(input, maxLength, ""))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {0, -1})
+ void 최대_길이가_0이하면_UnexpectedException(int maxLength) {
+ // given
+ String input = "1234567890"; // 10
+
+ // when & then
+ assertThatThrownBy(() -> Validator.maxLength(input, maxLength, ""))
+ .isInstanceOf(UnexpectedException.class);
+ }
+
+ @ParameterizedTest
+ @NullSource
+ void 문자열이_null이면_통과(String input) {
+ // when & then
+ assertThatNoException()
+ .isThrownBy(() -> Validator.maxLength(input, 10, ""));
+ }
}
- @Test
- void 문자열_maxLength_검증_실패() {
- // given
- String input = "12345678901"; // 11
+ @Nested
+ class minLength {
+
+ @Test
+ void 문자열의_길이가_10이고_최소_길이가_10이면_통과() {
+ // given
+ String input = "1234567890"; // 10
+ int minLength = 10;
+
+ // when & then
+ assertThatNoException()
+ .isThrownBy(() -> Validator.minLength(input, minLength, ""));
+ }
+
+ @Test
+ void 문자열의_길이가_9이고_최소_길이가_10이면_예외() {
+ // given
+ String input = "123456789"; // 9
+ int minLength = 10;
- // when & then
- assertThatThrownBy(() -> Validator.maxLength(input, 10, ""))
- .isInstanceOf(IllegalArgumentException.class);
+ // when & then
+ assertThatThrownBy(() -> Validator.minLength(input, minLength, ""))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {0, -1})
+ void 최소_길이가_0이하면_UnexpectedException(int minLength) {
+ // given
+ String input = "1234567890"; // 10
+
+ // when & then
+ assertThatThrownBy(() -> Validator.minLength(input, minLength, ""))
+ .isInstanceOf(UnexpectedException.class);
+ }
+
+ @ParameterizedTest
+ @NullSource
+ void 문자열이_null이면_통과(String input) {
+ // when & then
+ assertThatNoException()
+ .isThrownBy(() -> Validator.minLength(input, 10, ""));
+ }
}
- @Test
- void 문자열_maxLength_검증_null_성공() {
- // when & then
- assertThatNoException()
- .isThrownBy(() -> Validator.maxLength(null, 10, ""));
+
+ @Nested
+ class notNull {
+
+ @ParameterizedTest
+ @NullSource
+ void 객체가_null이면_예외(Object object) {
+ // when & then
+ assertThatThrownBy(() -> Validator.notNull(object, ""))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @Test
+ void 객체가_null이_아니면_통과() {
+ // given
+ Object object = "";
+
+ // when & then
+ assertThatNoException()
+ .isThrownBy(() -> Validator.notNull(object, ""));
+ }
}
- @Test
- void 문자열_minLength_검증_성공() {
- // given
- String input = "1234567890"; // 10
+ @Nested
+ class maxValue {
+
+ @Test
+ void 값이_100이고_최대_값이_100이면_통과() {
+ // given
+ int value = 100;
+ int maxValue = 100;
- // when & then
- assertThatNoException()
- .isThrownBy(() -> Validator.minLength(input, 10, ""));
+ // when & then
+ assertThatNoException()
+ .isThrownBy(() -> Validator.maxValue(value, maxValue, ""));
+ }
+
+ @Test
+ void 값이_101이고_최대_값이_100이면_예외() {
+ // given
+ int value = 101;
+ int maxValue = 100;
+
+ // when & then
+ assertThatThrownBy(() -> Validator.maxValue(value, maxValue, ""))
+ .isInstanceOf(ValidException.class);
+ }
}
- @Test
- void 문자열_minLength_검증_실패() {
- // given
- String input = "123456789"; // 9
+ @Nested
+ class minValue {
+
+ @Test
+ void 값이_100이고_최소_값이_100이면_통과() {
+ // given
+ int value = 100;
+ int minValue = 100;
+
+ // when & then
+ assertThatNoException()
+ .isThrownBy(() -> Validator.minValue(value, minValue, ""));
+ }
- // when & then
- assertThatThrownBy(() -> Validator.minLength(input, 10, ""))
- .isInstanceOf(IllegalArgumentException.class);
+ @Test
+ void 값이_99이고_최소_값이_100이면_예외() {
+ // given
+ int value = 99;
+ int minValue = 100;
+
+ // when & then
+ assertThatThrownBy(() -> Validator.minValue(value, minValue, ""))
+ .isInstanceOf(ValidException.class);
+ }
}
- @Test
- void 문자열_minLength_검증_null_성공() {
- // when & then
- assertThatNoException()
- .isThrownBy(() -> Validator.minLength(null, 1, ""));
+ @Nested
+ class isNegative {
+
+ @Test
+ void 값이_음수이면_예외() {
+ // given
+ int value = -1;
+
+ // when & then
+ assertThatThrownBy(() -> Validator.isNegative(value, ""))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {0, 1})
+ void 값이_음수가_아니면_통과(int value) {
+ // when & then
+ assertThatNoException()
+ .isThrownBy(() -> Validator.isNegative(value, ""));
+ }
}
}
diff --git a/backend/src/test/java/com/festago/domain/MemberTest.java b/backend/src/test/java/com/festago/domain/MemberTest.java
deleted file mode 100644
index e88b98641..000000000
--- a/backend/src/test/java/com/festago/domain/MemberTest.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.festago.domain;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import com.festago.member.domain.Member;
-import com.festago.support.MemberFixture;
-import org.junit.jupiter.api.DisplayNameGeneration;
-import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
-import org.junit.jupiter.api.Test;
-
-@DisplayNameGeneration(ReplaceUnderscores.class)
-@SuppressWarnings("NonAsciiCharacters")
-class MemberTest {
-
- @Test
- void 프로필_이미지가_null이면_기본값이_할당된다() {
- // when
- Member actual = MemberFixture.member()
- .profileImage(null)
- .build();
-
- // then
- assertThat(actual.getProfileImage()).isNotNull();
- }
-}
diff --git a/backend/src/test/java/com/festago/domain/TicketEntryTimeTest.java b/backend/src/test/java/com/festago/domain/TicketEntryTimeTest.java
deleted file mode 100644
index 3c2025d6b..000000000
--- a/backend/src/test/java/com/festago/domain/TicketEntryTimeTest.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.festago.domain;
-
-import static com.festago.common.exception.ErrorCode.INVALID_MIN_TICKET_AMOUNT;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-import com.festago.common.exception.BadRequestException;
-import com.festago.ticket.domain.TicketEntryTime;
-import java.time.LocalDateTime;
-import org.junit.jupiter.api.DisplayNameGeneration;
-import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
-import org.junit.jupiter.api.Test;
-
-@DisplayNameGeneration(ReplaceUnderscores.class)
-@SuppressWarnings("NonAsciiCharacters")
-class TicketEntryTimeTest {
-
- @Test
- void 총_수량이_음수일_경우_에외() {
- // given
- LocalDateTime now = LocalDateTime.now();
-
- // when & then
- assertThatThrownBy(() -> new TicketEntryTime(now, -1))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("TicketEntryTime 의 필드로 허용된 범위를 넘은 column 을 넣을 수 없습니다.");
- }
-
- @Test
- void 총_수량이_1_미만이면_예외() {
- // given
- LocalDateTime now = LocalDateTime.now();
-
- // when & then
- assertThatThrownBy(() -> new TicketEntryTime(now, 0))
- .isInstanceOf(BadRequestException.class)
- .hasMessage(INVALID_MIN_TICKET_AMOUNT.getMessage());
- }
-}
diff --git a/backend/src/test/java/com/festago/application/EntryServiceTest.java b/backend/src/test/java/com/festago/entry/application/EntryServiceTest.java
similarity index 98%
rename from backend/src/test/java/com/festago/application/EntryServiceTest.java
rename to backend/src/test/java/com/festago/entry/application/EntryServiceTest.java
index 8786f8de6..0bb7cbc69 100644
--- a/backend/src/test/java/com/festago/application/EntryServiceTest.java
+++ b/backend/src/test/java/com/festago/entry/application/EntryServiceTest.java
@@ -1,4 +1,4 @@
-package com.festago.application;
+package com.festago.entry.application;
import static com.festago.common.exception.ErrorCode.MEMBER_TICKET_NOT_FOUND;
import static com.festago.common.exception.ErrorCode.NOT_ENTRY_TIME;
@@ -6,16 +6,14 @@
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.BDDMockito.anyLong;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import com.festago.common.exception.BadRequestException;
import com.festago.common.exception.NotFoundException;
-import com.festago.entry.application.EntryCodeManager;
-import com.festago.entry.application.EntryService;
import com.festago.entry.domain.EntryCode;
import com.festago.entry.domain.EntryCodePayload;
import com.festago.entry.dto.EntryCodeResponse;
@@ -50,9 +48,9 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationEventPublisher;
-@ExtendWith(MockitoExtension.class)
@DisplayNameGeneration(ReplaceUnderscores.class)
@SuppressWarnings("NonAsciiCharacters")
+@ExtendWith(MockitoExtension.class)
class EntryServiceTest {
@Mock
diff --git a/backend/src/test/java/com/festago/domain/EntryCodeTest.java b/backend/src/test/java/com/festago/entry/domain/EntryCodeTest.java
similarity index 59%
rename from backend/src/test/java/com/festago/domain/EntryCodeTest.java
rename to backend/src/test/java/com/festago/entry/domain/EntryCodeTest.java
index b85b744ad..b01ddcfb9 100644
--- a/backend/src/test/java/com/festago/domain/EntryCodeTest.java
+++ b/backend/src/test/java/com/festago/entry/domain/EntryCodeTest.java
@@ -1,37 +1,44 @@
-package com.festago.domain;
+package com.festago.entry.domain;
-import static com.festago.common.exception.ErrorCode.INVALID_ENTRY_CODE_OFFSET;
-import static com.festago.common.exception.ErrorCode.INVALID_ENTRY_CODE_PERIOD;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
-import com.festago.common.exception.InternalServerException;
-import com.festago.entry.domain.EntryCode;
+import com.festago.common.exception.UnexpectedException;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;
@DisplayNameGeneration(ReplaceUnderscores.class)
@SuppressWarnings("NonAsciiCharacters")
class EntryCodeTest {
+ @ParameterizedTest
+ @NullSource
+ @ValueSource(strings = {""})
+ void 입장_코드의_코드가_null_또는_공백이면_예외(String code) {
+ assertThatThrownBy(() -> new EntryCode(code, 30, 10))
+ .isInstanceOf(UnexpectedException.class)
+ .hasMessage("code는 빈 값 또는 null이 될 수 없습니다.");
+ }
+
@ParameterizedTest
@ValueSource(longs = {0, -1})
void 입장_코드의_수명이_0_또는_음수이면_예외(long period) {
// when & then
assertThatThrownBy(() -> new EntryCode("code", period, 0))
- .isInstanceOf(InternalServerException.class)
- .hasMessage(INVALID_ENTRY_CODE_PERIOD.getMessage());
+ .isInstanceOf(UnexpectedException.class)
+ .hasMessage("period는 0 또는 음수가 될 수 없습니다.");
}
@Test
void 입장_코드의_오프셋이_음수이면_예외() {
- // when & tehn
+ // when & then
assertThatThrownBy(() -> new EntryCode("code", 30, -1))
- .isInstanceOf(InternalServerException.class)
- .hasMessage(INVALID_ENTRY_CODE_OFFSET.getMessage());
+ .isInstanceOf(UnexpectedException.class)
+ .hasMessage("offset은 음수가 될 수 없습니다.");
}
@ParameterizedTest
diff --git a/backend/src/test/java/com/festago/infrastructure/JwtEntryCodeExtractorTest.java b/backend/src/test/java/com/festago/entry/infrastructure/JwtEntryCodeExtractorTest.java
similarity index 89%
rename from backend/src/test/java/com/festago/infrastructure/JwtEntryCodeExtractorTest.java
rename to backend/src/test/java/com/festago/entry/infrastructure/JwtEntryCodeExtractorTest.java
index cd362d593..c0e8d54c7 100644
--- a/backend/src/test/java/com/festago/infrastructure/JwtEntryCodeExtractorTest.java
+++ b/backend/src/test/java/com/festago/entry/infrastructure/JwtEntryCodeExtractorTest.java
@@ -1,16 +1,13 @@
-package com.festago.infrastructure;
+package com.festago.entry.infrastructure;
import static com.festago.common.exception.ErrorCode.EXPIRED_ENTRY_CODE;
import static com.festago.common.exception.ErrorCode.INVALID_ENTRY_CODE;
-import static com.festago.common.exception.ErrorCode.INVALID_ENTRY_CODE_PAYLOAD;
-import static com.festago.common.exception.ErrorCode.INVALID_ENTRY_STATE_INDEX;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
import com.festago.common.exception.BadRequestException;
-import com.festago.common.exception.InternalServerException;
+import com.festago.common.exception.UnexpectedException;
import com.festago.entry.domain.EntryCodePayload;
-import com.festago.entry.infrastructure.JwtEntryCodeExtractor;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
@@ -98,8 +95,8 @@ class JwtEntryCodeExtractorTest {
// when & then
assertThatThrownBy(() -> jwtEntryCodeExtractor.extract(code))
- .isInstanceOf(InternalServerException.class)
- .hasMessage(INVALID_ENTRY_CODE_PAYLOAD.getMessage());
+ .isInstanceOf(UnexpectedException.class)
+ .hasMessage("memberTicketId는 null이 될 수 없습니다.");
}
@Test
@@ -113,8 +110,8 @@ class JwtEntryCodeExtractorTest {
// when & then
assertThatThrownBy(() -> jwtEntryCodeExtractor.extract(code))
- .isInstanceOf(InternalServerException.class)
- .hasMessage(INVALID_ENTRY_STATE_INDEX.getMessage());
+ .isInstanceOf(UnexpectedException.class)
+ .hasMessage("entryState의 인덱스는 null이 될 수 없습니다.");
}
@Test
diff --git a/backend/src/test/java/com/festago/infrastructure/JwtEntryCodeProviderTest.java b/backend/src/test/java/com/festago/entry/infrastructure/JwtEntryCodeProviderTest.java
similarity index 86%
rename from backend/src/test/java/com/festago/infrastructure/JwtEntryCodeProviderTest.java
rename to backend/src/test/java/com/festago/entry/infrastructure/JwtEntryCodeProviderTest.java
index f1509b032..b11ea8ad1 100644
--- a/backend/src/test/java/com/festago/infrastructure/JwtEntryCodeProviderTest.java
+++ b/backend/src/test/java/com/festago/entry/infrastructure/JwtEntryCodeProviderTest.java
@@ -1,13 +1,11 @@
-package com.festago.infrastructure;
+package com.festago.entry.infrastructure;
-import static com.festago.common.exception.ErrorCode.INVALID_ENTRY_CODE_EXPIRATION_TIME;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
-import com.festago.common.exception.InternalServerException;
+import com.festago.common.exception.UnexpectedException;
import com.festago.entry.application.EntryCodeProvider;
import com.festago.entry.domain.EntryCodePayload;
-import com.festago.entry.infrastructure.JwtEntryCodeProvider;
import com.festago.support.MemberTicketFixture;
import com.festago.ticketing.domain.MemberTicket;
import io.jsonwebtoken.Claims;
@@ -37,8 +35,8 @@ class JwtEntryCodeProviderTest {
// when & then
assertThatThrownBy(() -> entryCodeProvider.provide(entryCodePayload, expiredAt))
- .isInstanceOf(InternalServerException.class)
- .hasMessage(INVALID_ENTRY_CODE_EXPIRATION_TIME.getMessage());
+ .isInstanceOf(UnexpectedException.class)
+ .hasMessage("입장코드 만료일자는 과거일 수 없습니다.");
}
@Test
diff --git a/backend/src/test/java/com/festago/fcm/domain/MemberFCMTest.java b/backend/src/test/java/com/festago/fcm/domain/MemberFCMTest.java
new file mode 100644
index 000000000..194885c73
--- /dev/null
+++ b/backend/src/test/java/com/festago/fcm/domain/MemberFCMTest.java
@@ -0,0 +1,54 @@
+package com.festago.fcm.domain;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import com.festago.common.exception.ValidException;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+@DisplayNameGeneration(ReplaceUnderscores.class)
+@SuppressWarnings("NonAsciiCharacters")
+class MemberFCMTest {
+
+ @Test
+ void MemberFCM_생성_성공() {
+ // given
+ MemberFCM memberFCM = new MemberFCM(1L, "token");
+
+ // when & then
+ assertThat(memberFCM.getMemberId()).isEqualTo(1L);
+ assertThat(memberFCM.getFcmToken()).isEqualTo("token");
+ }
+
+ @ParameterizedTest
+ @NullSource
+ void memberId가_null이면_예외(Long memberId) {
+ // when & then
+ assertThatThrownBy(() -> new MemberFCM(memberId, "token"))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @ParameterizedTest
+ @NullSource
+ @ValueSource(strings = {"", " ", "\t", "\n"})
+ void token이_null_또는_공백이면_예외(String token) {
+ // when & then
+ assertThatThrownBy(() -> new MemberFCM(1L, token))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @Test
+ void token의_길이가_255자를_초과하면_예외() {
+ // given
+ String token = "1".repeat(256);
+
+ // when & then
+ assertThatThrownBy(() -> new MemberFCM(1L, token))
+ .isInstanceOf(ValidException.class);
+ }
+}
diff --git a/backend/src/test/java/com/festago/application/FestivalServiceTest.java b/backend/src/test/java/com/festago/festival/application/FestivalServiceTest.java
similarity index 89%
rename from backend/src/test/java/com/festago/application/FestivalServiceTest.java
rename to backend/src/test/java/com/festago/festival/application/FestivalServiceTest.java
index c8c5c3387..d6a078ae1 100644
--- a/backend/src/test/java/com/festago/application/FestivalServiceTest.java
+++ b/backend/src/test/java/com/festago/festival/application/FestivalServiceTest.java
@@ -1,4 +1,4 @@
-package com.festago.application;
+package com.festago.festival.application;
import static com.festago.common.exception.ErrorCode.FESTIVAL_NOT_FOUND;
import static com.festago.common.exception.ErrorCode.INVALID_FESTIVAL_START_DATE;
@@ -11,13 +11,11 @@
import com.festago.common.exception.BadRequestException;
import com.festago.common.exception.NotFoundException;
-import com.festago.festival.application.FestivalService;
import com.festago.festival.domain.Festival;
import com.festago.festival.dto.FestivalCreateRequest;
import com.festago.festival.dto.FestivalDetailResponse;
import com.festago.festival.dto.FestivalDetailStageResponse;
import com.festago.festival.dto.FestivalResponse;
-import com.festago.festival.dto.FestivalsResponse;
import com.festago.festival.repository.FestivalRepository;
import com.festago.school.domain.School;
import com.festago.school.repository.SchoolRepository;
@@ -41,9 +39,9 @@
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
-@ExtendWith(MockitoExtension.class)
@DisplayNameGeneration(ReplaceUnderscores.class)
@SuppressWarnings("NonAsciiCharacters")
+@ExtendWith(MockitoExtension.class)
class FestivalServiceTest {
@Mock
@@ -61,22 +59,6 @@ class FestivalServiceTest {
@InjectMocks
FestivalService festivalService;
- @Test
- void 모든_축제_조회() {
- // given
- Festival festival1 = FestivalFixture.festival().id(1L).build();
- Festival festival2 = FestivalFixture.festival().id(2L).build();
- given(festivalRepository.findAll()).willReturn(List.of(festival1, festival2));
-
- // when
- FestivalsResponse response = festivalService.findAll();
-
- // then
- List festivalIds = response.festivals().stream().map(FestivalResponse::id).toList();
-
- assertThat(festivalIds).containsExactly(1L, 2L);
- }
-
@Nested
class 축제_생성 {
diff --git a/backend/src/test/java/com/festago/application/integration/FestivalServiceIntegrationTest.java b/backend/src/test/java/com/festago/festival/application/integration/FestivalServiceIntegrationTest.java
similarity index 97%
rename from backend/src/test/java/com/festago/application/integration/FestivalServiceIntegrationTest.java
rename to backend/src/test/java/com/festago/festival/application/integration/FestivalServiceIntegrationTest.java
index d7d850f87..8094925d8 100644
--- a/backend/src/test/java/com/festago/application/integration/FestivalServiceIntegrationTest.java
+++ b/backend/src/test/java/com/festago/festival/application/integration/FestivalServiceIntegrationTest.java
@@ -1,4 +1,4 @@
-package com.festago.application.integration;
+package com.festago.festival.application.integration;
import static org.assertj.core.api.Assertions.assertThat;
@@ -14,6 +14,7 @@
import com.festago.school.repository.SchoolRepository;
import com.festago.stage.domain.Stage;
import com.festago.stage.repository.StageRepository;
+import com.festago.support.ApplicationIntegrationTest;
import com.festago.support.FestivalFixture;
import com.festago.support.SchoolFixture;
import com.festago.support.StageFixture;
diff --git a/backend/src/test/java/com/festago/festival/domain/FestivalFilterTest.java b/backend/src/test/java/com/festago/festival/domain/FestivalFilterTest.java
new file mode 100644
index 000000000..8c7ea210f
--- /dev/null
+++ b/backend/src/test/java/com/festago/festival/domain/FestivalFilterTest.java
@@ -0,0 +1,54 @@
+package com.festago.festival.domain;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import com.festago.common.exception.BadRequestException;
+import com.festago.festival.repository.FestivalFilter;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+@DisplayNameGeneration(ReplaceUnderscores.class)
+@SuppressWarnings("NonAsciiCharacters")
+class FestivalFilterTest {
+
+ @Test
+ void 유효하지_않은_name_이면_예외() {
+ // given && when && then
+ assertThatThrownBy(() -> FestivalFilter.from("unvalid"))
+ .isInstanceOf(BadRequestException.class);
+ }
+
+ @ValueSource(strings = {"progress", "Progress", "PROGRESS"})
+ @ParameterizedTest
+ void PROGRESS_반환(String value) {
+ // given && when
+ FestivalFilter filter = FestivalFilter.from(value);
+
+ // then
+ assertThat(filter).isEqualTo(FestivalFilter.PROGRESS);
+ }
+
+ @ValueSource(strings = {"planned", "Planned", "PLANNED"})
+ @ParameterizedTest
+ void PLANNED_반환(String value) {
+ // given && when
+ FestivalFilter filter = FestivalFilter.from(value);
+
+ // then
+ assertThat(filter).isEqualTo(FestivalFilter.PLANNED);
+ }
+
+ @ValueSource(strings = {"end", "End", "END"})
+ @ParameterizedTest
+ void END_반환(String value) {
+ // given && when
+ FestivalFilter filter = FestivalFilter.from(value);
+
+ // then
+ assertThat(filter).isEqualTo(FestivalFilter.END);
+ }
+}
diff --git a/backend/src/test/java/com/festago/domain/FestivalTest.java b/backend/src/test/java/com/festago/festival/domain/FestivalTest.java
similarity index 94%
rename from backend/src/test/java/com/festago/domain/FestivalTest.java
rename to backend/src/test/java/com/festago/festival/domain/FestivalTest.java
index ac29a3627..0baad5c55 100644
--- a/backend/src/test/java/com/festago/domain/FestivalTest.java
+++ b/backend/src/test/java/com/festago/festival/domain/FestivalTest.java
@@ -1,10 +1,10 @@
-package com.festago.domain;
+package com.festago.festival.domain;
import static com.festago.common.exception.ErrorCode.INVALID_FESTIVAL_DURATION;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import com.festago.festival.domain.Festival;
+import com.festago.common.exception.BadRequestException;
import com.festago.school.domain.School;
import com.festago.support.FestivalFixture;
import com.festago.support.SchoolFixture;
@@ -28,7 +28,7 @@ class FestivalTest {
// when & then
assertThatThrownBy(() -> new Festival("테코대학교", tomorrow, today, school))
- .isInstanceOf(IllegalArgumentException.class)
+ .isInstanceOf(BadRequestException.class)
.hasMessage(INVALID_FESTIVAL_DURATION.getMessage());
}
diff --git a/backend/src/test/java/com/festago/dto/FestivalCreateRequestTest.java b/backend/src/test/java/com/festago/festival/dto/FestivalCreateRequestTest.java
similarity index 95%
rename from backend/src/test/java/com/festago/dto/FestivalCreateRequestTest.java
rename to backend/src/test/java/com/festago/festival/dto/FestivalCreateRequestTest.java
index 9252f0d1c..e7677b266 100644
--- a/backend/src/test/java/com/festago/dto/FestivalCreateRequestTest.java
+++ b/backend/src/test/java/com/festago/festival/dto/FestivalCreateRequestTest.java
@@ -1,9 +1,8 @@
-package com.festago.dto;
+package com.festago.festival.dto;
import static org.assertj.core.api.Assertions.assertThat;
import com.festago.festival.domain.Festival;
-import com.festago.festival.dto.FestivalCreateRequest;
import com.festago.school.domain.School;
import com.festago.support.SchoolFixture;
import java.time.LocalDate;
@@ -50,5 +49,4 @@ class FestivalCreateRequestTest {
// then
assertThat(festival.getThumbnail()).isEqualTo("img");
}
-
}
diff --git a/backend/src/test/java/com/festago/festival/repository/FestivalRepositoryTest.java b/backend/src/test/java/com/festago/festival/repository/FestivalRepositoryTest.java
new file mode 100644
index 000000000..81e13a050
--- /dev/null
+++ b/backend/src/test/java/com/festago/festival/repository/FestivalRepositoryTest.java
@@ -0,0 +1,162 @@
+package com.festago.festival.repository;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.SoftAssertions.assertSoftly;
+
+import com.festago.festival.domain.Festival;
+import com.festago.school.domain.School;
+import com.festago.school.repository.SchoolRepository;
+import java.time.LocalDate;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+
+@DisplayNameGeneration(ReplaceUnderscores.class)
+@SuppressWarnings("NonAsciiCharacters")
+@DataJpaTest
+class FestivalRepositoryTest {
+
+ private static final String CURRENT_FESTIVAL = "현재 축제";
+ private static final String PAST_FESTIVAL = "과거 축제";
+ private static final String FUTURE_FESTIVAL = "미래 축제";
+
+ @Autowired
+ FestivalRepository festivalRepository;
+
+ @Autowired
+ SchoolRepository schoolRepository;
+
+ LocalDate now = LocalDate.parse("2023-06-30");
+
+ School school;
+
+ @BeforeEach
+ public void setting() {
+ this.school = schoolRepository.save(new School("domain", "name"));
+ }
+
+ private void prepareNotOrderedFestivals() {
+ festivalRepository.save(new Festival(FUTURE_FESTIVAL, now.plusDays(1L), now.plusDays(3L), school));
+ festivalRepository.save(new Festival(CURRENT_FESTIVAL, now, now, school));
+ festivalRepository.save(new Festival(PAST_FESTIVAL, now.minusDays(3L), now.minusDays(1L), school));
+ }
+
+ @Nested
+ class 진행_에정_축제_반환 {
+
+ @Test
+ void 성공() {
+ // given
+ FestivalFilter filter = FestivalFilter.PLANNED;
+ prepareNotOrderedFestivals();
+
+ // when
+ List actual = festivalRepository.findAll(filter.getSpecification(now));
+
+ // then
+ assertSoftly(softAssertions -> {
+ assertThat(actual).hasSize(1);
+ assertThat(actual.get(0)).matches(festival -> festival.getName().equals(FUTURE_FESTIVAL));
+ });
+ }
+
+ @Test
+ void 은_시작_시점이_빠른_순서로_반환된다() {
+ // given
+ FestivalFilter filter = FestivalFilter.PLANNED;
+ Festival festival2 = festivalRepository.save(
+ new Festival("festival2", now.plusDays(2), now.plusDays(10), school));
+ Festival festival3 = festivalRepository.save(
+ new Festival("festival3", now.plusDays(3), now.plusDays(10), school));
+ Festival festival1 = festivalRepository.save(
+ new Festival("festival1", now.plusDays(1), now.plusDays(10), school));
+
+ // when
+ List actual = festivalRepository.findAll(filter.getSpecification(now));
+
+ // then
+ assertThat(actual).isEqualTo(List.of(festival1, festival2, festival3));
+ }
+ }
+
+ @Nested
+ class 진행_축제_반환 {
+
+ @Test
+ void 성공() {
+ // given
+ FestivalFilter filter = FestivalFilter.PROGRESS;
+ prepareNotOrderedFestivals();
+
+ // when
+ List actual = festivalRepository.findAll(filter.getSpecification(now));
+
+ // then
+ assertSoftly(softAssertions -> {
+ assertThat(actual).hasSize(1);
+ assertThat(actual.get(0)).matches(festival -> festival.getName().equals(CURRENT_FESTIVAL));
+ });
+ }
+
+ @Test
+ void 은_시작_시점이_빠른_순서로_반환된다() {
+ // given
+ FestivalFilter filter = FestivalFilter.PROGRESS;
+ Festival festival2 = festivalRepository.save(
+ new Festival("festival2", now.minusDays(2), now.plusDays(10), school));
+ Festival festival3 = festivalRepository.save(
+ new Festival("festival3", now.minusDays(1), now.plusDays(10), school));
+ Festival festival1 = festivalRepository.save(
+ new Festival("festival1", now.minusDays(3), now.plusDays(10), school));
+
+ // when
+ List actual = festivalRepository.findAll(filter.getSpecification(now));
+
+ // then
+ assertThat(actual).isEqualTo(List.of(festival1, festival2, festival3));
+ }
+ }
+
+ @Nested
+ class 종료_축제_반환 {
+
+ @Test
+ void 성공() {
+ // given
+ FestivalFilter filter = FestivalFilter.END;
+ prepareNotOrderedFestivals();
+
+ // when
+ List actual = festivalRepository.findAll(filter.getSpecification(now));
+
+ // then
+ assertSoftly(softAssertions -> {
+ assertThat(actual).hasSize(1);
+ assertThat(actual.get(0)).matches(festival -> festival.getName().equals(PAST_FESTIVAL));
+ });
+ }
+
+ @Test
+ void 은_종료_시점이_느린_순서로_반환된다() {
+ // given
+ FestivalFilter filter = FestivalFilter.END;
+ Festival festival2 = festivalRepository.save(
+ new Festival("festival2", now.minusDays(10), now.minusDays(2), school));
+ Festival festival3 = festivalRepository.save(
+ new Festival("festival3", now.minusDays(10), now.minusDays(1), school));
+ Festival festival1 = festivalRepository.save(
+ new Festival("festival1", now.minusDays(10), now.minusDays(3), school));
+
+ // when
+ List actual = festivalRepository.findAll(filter.getSpecification(now));
+
+ // then
+ assertThat(actual).isEqualTo(List.of(festival3, festival2, festival1));
+ }
+ }
+}
diff --git a/backend/src/test/java/com/festago/member/domain/MemberTest.java b/backend/src/test/java/com/festago/member/domain/MemberTest.java
new file mode 100644
index 000000000..43ccf576e
--- /dev/null
+++ b/backend/src/test/java/com/festago/member/domain/MemberTest.java
@@ -0,0 +1,95 @@
+package com.festago.member.domain;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import com.festago.auth.domain.SocialType;
+import com.festago.common.exception.ValidException;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+@DisplayNameGeneration(ReplaceUnderscores.class)
+@SuppressWarnings("NonAsciiCharacters")
+class MemberTest {
+
+ @Test
+ void Member_생성_성공() {
+ // given
+ Member member = new Member(1L, "12345", SocialType.FESTAGO, "nickname", "profileImage.png");
+
+ // when & then
+ assertThat(member.getId()).isEqualTo(1L);
+ }
+
+ @ParameterizedTest
+ @NullSource
+ @ValueSource(strings = {"", " ", "\t", "\n"})
+ void socialId가_null_또는_공백이면_예외(String socialId) {
+ // when & then
+ assertThatThrownBy(() -> new Member(1L, socialId, SocialType.FESTAGO, "nickname", "profileImage.png"))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @Test
+ void socialId의_길이가_255자를_초과하면_예외() {
+ // given
+ String socialId = "1".repeat(256);
+
+ // when & then
+ assertThatThrownBy(() -> new Member(1L, socialId, SocialType.FESTAGO, "nickname", "profileImage.png"))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @ParameterizedTest
+ @NullSource
+ void socialType이_null이면_예외(SocialType socialType) {
+ // when & then
+ assertThatThrownBy(() -> new Member(1L, "12345", socialType, "nickname", "profileImage.png"))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @ParameterizedTest
+ @NullSource
+ @ValueSource(strings = {"", " ", "\t", "\n"})
+ void nickname이_null_또는_공백이면_예외(String nickname) {
+ // when & then
+ assertThatThrownBy(() -> new Member(1L, "12345", SocialType.FESTAGO, nickname, "profileImage.png"))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @Test
+ void nickname의_길이가_30자를_초과하면_예외() {
+ // given
+ String nickname = "1".repeat(31);
+
+ // when & then
+ assertThatThrownBy(() -> new Member(1L, "12345", SocialType.FESTAGO, nickname, "profileImage.png"))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @Test
+ void profileImage의_길이가_255자를_초과하면_예외() {
+ // given
+ String profileImage = "1".repeat(256);
+
+ // when & then
+ assertThatThrownBy(() -> new Member(1L, "12345", SocialType.FESTAGO, "nickname", profileImage))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @ParameterizedTest
+ @NullSource
+ @ValueSource(strings = {"", " ", "\t", "\n"})
+ void profileImage가_null_또는_공백이면_기본값이_할당된다(String profileImage) {
+ // given
+ Member actual = new Member("12345", SocialType.FESTAGO, "nickname", profileImage);
+
+ // when & then
+ assertThat(actual.getProfileImage()).isNotNull();
+ assertThat(actual.getProfileImage()).isNotBlank();
+ }
+}
diff --git a/backend/src/test/java/com/festago/domain/MemberRepositoryTest.java b/backend/src/test/java/com/festago/member/repository/MemberRepositoryTest.java
similarity index 95%
rename from backend/src/test/java/com/festago/domain/MemberRepositoryTest.java
rename to backend/src/test/java/com/festago/member/repository/MemberRepositoryTest.java
index 6e6e2e927..c66c3678c 100644
--- a/backend/src/test/java/com/festago/domain/MemberRepositoryTest.java
+++ b/backend/src/test/java/com/festago/member/repository/MemberRepositoryTest.java
@@ -1,10 +1,9 @@
-package com.festago.domain;
+package com.festago.member.repository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
import com.festago.member.domain.Member;
-import com.festago.member.repository.MemberRepository;
import com.festago.support.MemberFixture;
import jakarta.persistence.EntityManager;
import org.junit.jupiter.api.DisplayNameGeneration;
diff --git a/backend/src/test/java/com/festago/presentation/AdminControllerTest.java b/backend/src/test/java/com/festago/presentation/AdminControllerTest.java
index 2fc07ecbd..d4a9b6644 100644
--- a/backend/src/test/java/com/festago/presentation/AdminControllerTest.java
+++ b/backend/src/test/java/com/festago/presentation/AdminControllerTest.java
@@ -13,7 +13,6 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.festago.admin.application.AdminService;
import com.festago.auth.application.AdminAuthService;
import com.festago.auth.application.AuthExtractor;
import com.festago.auth.domain.Role;
@@ -70,9 +69,6 @@ class AdminControllerTest {
@MockBean
TicketService ticketService;
- @MockBean
- AdminService adminService;
-
@MockBean
AdminAuthService adminAuthService;
diff --git a/backend/src/test/java/com/festago/presentation/FestivalControllerTest.java b/backend/src/test/java/com/festago/presentation/FestivalControllerTest.java
index 12f038314..8fef4e0a3 100644
--- a/backend/src/test/java/com/festago/presentation/FestivalControllerTest.java
+++ b/backend/src/test/java/com/festago/presentation/FestivalControllerTest.java
@@ -1,8 +1,12 @@
package com.festago.presentation;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.SoftAssertions.assertSoftly;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -12,6 +16,7 @@
import com.festago.festival.dto.FestivalDetailResponse;
import com.festago.festival.dto.FestivalResponse;
import com.festago.festival.dto.FestivalsResponse;
+import com.festago.festival.repository.FestivalFilter;
import com.festago.support.CustomWebMvcTest;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
@@ -20,6 +25,8 @@
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
@@ -39,15 +46,18 @@ class FestivalControllerTest {
@MockBean
FestivalService festivalService;
+ @Captor
+ ArgumentCaptor festivalFilterCaptor;
+
@Test
- void 모든_축제를_조회한다() throws Exception {
+ void 축제를_조회한다() throws Exception {
// given
FestivalResponse festivalResponse1 = new FestivalResponse(1L, 1L, "테코대학교", LocalDate.now(),
LocalDate.now().plusDays(3), "https://image1.png");
FestivalResponse festivalResponse2 = new FestivalResponse(2L, 2L, "우테대학교", LocalDate.now().minusDays(3),
LocalDate.now(), "https://image2.png");
FestivalsResponse expected = new FestivalsResponse(List.of(festivalResponse1, festivalResponse2));
- given(festivalService.findAll())
+ given(festivalService.findFestivals(any(FestivalFilter.class)))
.willReturn(expected);
// when & then
@@ -59,7 +69,12 @@ class FestivalControllerTest {
.getResponse()
.getContentAsString(StandardCharsets.UTF_8);
FestivalsResponse actual = objectMapper.readValue(content, FestivalsResponse.class);
- assertThat(actual).isEqualTo(expected);
+ assertSoftly(softAssertions -> {
+ verify(festivalService, times(1)).findFestivals(festivalFilterCaptor.capture());
+ assertThat(festivalFilterCaptor.getValue()).isEqualTo(FestivalFilter.PROGRESS);
+ assertThat(actual).isEqualTo(expected);
+ }
+ );
}
@Test
diff --git a/backend/src/test/java/com/festago/presentation/StudentControllerTest.java b/backend/src/test/java/com/festago/presentation/StudentControllerTest.java
index 541d79f40..8dfd17dae 100644
--- a/backend/src/test/java/com/festago/presentation/StudentControllerTest.java
+++ b/backend/src/test/java/com/festago/presentation/StudentControllerTest.java
@@ -1,14 +1,22 @@
package com.festago.presentation;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.BDDMockito.given;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.festago.student.application.StudentService;
+import com.festago.student.dto.StudentResponse;
+import com.festago.student.dto.StudentSchoolResponse;
import com.festago.student.dto.StudentSendMailRequest;
import com.festago.student.dto.StudentVerificateRequest;
import com.festago.support.CustomWebMvcTest;
import com.festago.support.WithMockAuth;
+import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
import org.junit.jupiter.api.Nested;
@@ -93,4 +101,42 @@ class 학생_인증 {
.andExpect(status().isOk());
}
}
+
+ @Nested
+ class 학생_인증_정보_조회 {
+
+ @Test
+ void 인증이_되지_않으면_401() throws Exception {
+ // given
+ StudentVerificateRequest request = new StudentVerificateRequest("123456");
+
+ // when & then
+ mockMvc.perform(get("/students")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(request)))
+ .andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ @WithMockAuth
+ void 학생_인증_조회() throws Exception {
+ // given
+ StudentResponse expected = new StudentResponse(true, new StudentSchoolResponse(1L, "테코대학교", "teco.ac.kr"));
+ given(studentService.findVerification(anyLong()))
+ .willReturn(expected);
+
+ // when & then
+ String content = mockMvc.perform(get("/students")
+ .contentType(MediaType.APPLICATION_JSON)
+ .header(HttpHeaders.AUTHORIZATION, "Bearer token")
+ )
+ .andExpect(status().isOk())
+ .andDo(print())
+ .andReturn()
+ .getResponse()
+ .getContentAsString(StandardCharsets.UTF_8);
+ StudentResponse actual = objectMapper.readValue(content, StudentResponse.class);
+ assertThat(actual).isEqualTo(expected);
+ }
+ }
}
diff --git a/backend/src/test/java/com/festago/school/domain/SchoolTest.java b/backend/src/test/java/com/festago/school/domain/SchoolTest.java
index f3305223f..45150b9ef 100644
--- a/backend/src/test/java/com/festago/school/domain/SchoolTest.java
+++ b/backend/src/test/java/com/festago/school/domain/SchoolTest.java
@@ -3,10 +3,12 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import com.festago.common.exception.ValidException;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;
@DisplayNameGeneration(ReplaceUnderscores.class)
@@ -16,43 +18,95 @@ class SchoolTest {
@Test
void 학교의_도메인_길이가_50자를_넘으면_예외() {
// given
- String domain = "1234567890".repeat(5) + "1";
+ String domain = "1".repeat(51);
// when & then
- assertThatThrownBy(() -> new School(domain, "name"))
- .isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> new School(domain, "테코대학교"))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @ParameterizedTest
+ @NullSource
+ @ValueSource(strings = {"", " ", "\t", "\n"})
+ void 학교의_도메인이_null_또는_공백이면_예외(String domain) {
+ // when & then
+ assertThatThrownBy(() -> new School(domain, "테코대학교"))
+ .isInstanceOf(ValidException.class);
}
@Test
void 학교의_이름이_255자를_넘으면_예외() {
// given
- String name = "1234567890".repeat(25) + "123456";
+ String name = "1".repeat(256);
+
+ // when & then
+ assertThatThrownBy(() -> new School("teco.ac.kr", name))
+ .isInstanceOf(ValidException.class);
+ }
+ @ParameterizedTest
+ @NullSource
+ @ValueSource(strings = {"", " ", "\t", "\n"})
+ void 학교의_이름이_null_또는_공백이면_예외(String name) {
// when & then
- assertThatThrownBy(() -> new School("domain", name))
- .isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> new School("teco.ac.kr", name))
+ .isInstanceOf(ValidException.class);
}
@Test
void 학교의_도메인을_수정할때_255자를_넘으면_예외() {
// given
- School school = new School("domain", "name");
+ School school = new School("teco.ac.kr", "테코대학교");
+
+ // when & then
+ String domain = "1".repeat(256);
+ assertThatThrownBy(() -> school.changeDomain(domain))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @ParameterizedTest
+ @NullSource
+ @ValueSource(strings = {"", " ", "\t", "\n"})
+ void 학교의_도메인을_수정할때_null_또는_공백이면_예외(String domain) {
+ // given
+ School school = new School("teco.ac.kr", "테코대학교");
// when & then
- String domain = "1234567890".repeat(5) + "1";
assertThatThrownBy(() -> school.changeDomain(domain))
- .isInstanceOf(IllegalArgumentException.class);
+ .isInstanceOf(ValidException.class);
}
@Test
void 학교의_이름을_수정할때_255자를_넘으면_예외() {
// given
- School school = new School("domain", "name");
+ School school = new School("teco.ac.kr", "테코대학교");
+
+ // when & then
+ String name = "1".repeat(256);
+ assertThatThrownBy(() -> school.changeName(name))
+ .isInstanceOf(ValidException.class);
+ }
+
+ @ParameterizedTest
+ @NullSource
+ @ValueSource(strings = {"", " ", "\t", "\n"})
+ void 학교의_이름을_수정할때_null_또는_공백이면_예외(String name) {
+ // given
+ School school = new School("teco.ac.kr", "테코대학교");
// when & then
- String name = "1234567890".repeat(25) + "123456";
assertThatThrownBy(() -> school.changeName(name))
- .isInstanceOf(IllegalArgumentException.class);
+ .isInstanceOf(ValidException.class);
+ }
+
+ @Test
+ void 학교_생성_성공() {
+ // given
+ School school = new School("teco.ac.kr", "테코대학교");
+
+ // when & then
+ assertThat(school.getName()).isEqualTo("테코대학교");
+ assertThat(school.getDomain()).isEqualTo("teco.ac.kr");
}
@ParameterizedTest
@@ -62,7 +116,7 @@ class SchoolTest {
String domain = "1".repeat(length);
// when
- School school = new School(domain, "name");
+ School school = new School(domain, "테코대학교");
// then
assertThat(school.getDomain()).isEqualTo(domain);
@@ -75,7 +129,7 @@ class SchoolTest {
String name = "1".repeat(length);
// when
- School school = new School("domain", name);
+ School school = new School("teco.ac.kr", name);
// then
assertThat(school.getName()).isEqualTo(name);
diff --git a/backend/src/test/java/com/festago/application/StageServiceTest.java b/backend/src/test/java/com/festago/stage/application/StageServiceTest.java
similarity index 91%
rename from backend/src/test/java/com/festago/application/StageServiceTest.java
rename to backend/src/test/java/com/festago/stage/application/StageServiceTest.java
index a2261cd22..3fb4d7da3 100644
--- a/backend/src/test/java/com/festago/application/StageServiceTest.java
+++ b/backend/src/test/java/com/festago/stage/application/StageServiceTest.java
@@ -1,13 +1,12 @@
-package com.festago.application;
+package com.festago.stage.application;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.BDDMockito.any;
-import static org.mockito.BDDMockito.anyLong;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
import com.festago.festival.domain.Festival;
import com.festago.festival.repository.FestivalRepository;
-import com.festago.stage.application.StageService;
import com.festago.stage.domain.Stage;
import com.festago.stage.dto.StageCreateRequest;
import com.festago.stage.dto.StageResponse;
diff --git a/backend/src/test/java/com/festago/application/integration/StageServiceIntegrationTest.java b/backend/src/test/java/com/festago/stage/application/integration/StageServiceIntegrationTest.java
similarity index 93%
rename from backend/src/test/java/com/festago/application/integration/StageServiceIntegrationTest.java
rename to backend/src/test/java/com/festago/stage/application/integration/StageServiceIntegrationTest.java
index 31ecc5b58..0a43cd783 100644
--- a/backend/src/test/java/com/festago/application/integration/StageServiceIntegrationTest.java
+++ b/backend/src/test/java/com/festago/stage/application/integration/StageServiceIntegrationTest.java
@@ -1,4 +1,4 @@
-package com.festago.application.integration;
+package com.festago.stage.application.integration;
import static com.festago.common.exception.ErrorCode.FESTIVAL_NOT_FOUND;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -7,6 +7,7 @@
import com.festago.festival.repository.FestivalRepository;
import com.festago.stage.application.StageService;
import com.festago.stage.dto.StageCreateRequest;
+import com.festago.support.ApplicationIntegrationTest;
import java.time.LocalDateTime;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
diff --git a/backend/src/test/java/com/festago/domain/StageTest.java b/backend/src/test/java/com/festago/stage/domain/StageTest.java
similarity index 97%
rename from backend/src/test/java/com/festago/domain/StageTest.java
rename to backend/src/test/java/com/festago/stage/domain/StageTest.java
index b73afd416..6c8304b16 100644
--- a/backend/src/test/java/com/festago/domain/StageTest.java
+++ b/backend/src/test/java/com/festago/stage/domain/StageTest.java
@@ -1,4 +1,4 @@
-package com.festago.domain;
+package com.festago.stage.domain;
import static com.festago.common.exception.ErrorCode.INVALID_STAGE_START_TIME;
import static com.festago.common.exception.ErrorCode.INVALID_TICKET_OPEN_TIME;
@@ -79,7 +79,7 @@ class StageTest {
.festival(festival)
.build()
)
- .isInstanceOf(IllegalArgumentException.class)
+ .isInstanceOf(BadRequestException.class)
.hasMessage(INVALID_TICKET_OPEN_TIME.getMessage());
}
@@ -100,5 +100,4 @@ class StageTest {
.festival(festival)
.build());
}
-
}
diff --git a/backend/src/test/java/com/festago/domain/StageRepositoryTest.java b/backend/src/test/java/com/festago/stage/repository/StageRepositoryTest.java
similarity index 97%
rename from backend/src/test/java/com/festago/domain/StageRepositoryTest.java
rename to backend/src/test/java/com/festago/stage/repository/StageRepositoryTest.java
index c88bb4cf9..7f90511d1 100644
--- a/backend/src/test/java/com/festago/domain/StageRepositoryTest.java
+++ b/backend/src/test/java/com/festago/stage/repository/StageRepositoryTest.java
@@ -1,4 +1,4 @@
-package com.festago.domain;
+package com.festago.stage.repository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
@@ -8,7 +8,6 @@
import com.festago.school.domain.School;
import com.festago.school.repository.SchoolRepository;
import com.festago.stage.domain.Stage;
-import com.festago.stage.repository.StageRepository;
import com.festago.support.FestivalFixture;
import com.festago.support.SchoolFixture;
import com.festago.support.StageFixture;
@@ -24,9 +23,9 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
-@DataJpaTest
@DisplayNameGeneration(ReplaceUnderscores.class)
@SuppressWarnings("NonAsciiCharacters")
+@DataJpaTest
class StageRepositoryTest {
@Autowired
@@ -73,7 +72,7 @@ class StageRepositoryTest {
List actual = stageRepository.findAllDetailByFestivalId(festival.getId());
// then
- assertThat(actual.size()).isEqualTo(1);
+ assertThat(actual).hasSize(1);
}
@Test
diff --git a/backend/src/test/java/com/festago/student/application/StudentServiceTest.java b/backend/src/test/java/com/festago/student/application/StudentServiceTest.java
index f7ef57a17..35671209f 100644
--- a/backend/src/test/java/com/festago/student/application/StudentServiceTest.java
+++ b/backend/src/test/java/com/festago/student/application/StudentServiceTest.java
@@ -8,6 +8,7 @@
import static com.festago.common.exception.ErrorCode.TOO_FREQUENT_REQUESTS;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.SoftAssertions.assertSoftly;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@@ -20,8 +21,10 @@
import com.festago.member.repository.MemberRepository;
import com.festago.school.domain.School;
import com.festago.school.repository.SchoolRepository;
+import com.festago.student.domain.Student;
import com.festago.student.domain.StudentCode;
import com.festago.student.domain.VerificationCode;
+import com.festago.student.dto.StudentResponse;
import com.festago.student.dto.StudentSendMailRequest;
import com.festago.student.dto.StudentVerificateRequest;
import com.festago.student.infrastructure.MockMailClient;
@@ -32,6 +35,7 @@
import com.festago.support.SchoolFixture;
import com.festago.support.SetUpMockito;
import com.festago.support.StudentCodeFixture;
+import com.festago.support.StudentFixture;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.Optional;
@@ -186,7 +190,7 @@ class 학생_인증 {
.willReturn(true);
// when & then
- assertThatThrownBy(() -> studentService.verificate(memberId, request))
+ assertThatThrownBy(() -> studentService.verify(memberId, request))
.isInstanceOf(BadRequestException.class)
.hasMessage(ALREADY_STUDENT_VERIFIED.getMessage());
}
@@ -202,7 +206,7 @@ class 학생_인증 {
.willReturn(Optional.empty());
// when & then
- assertThatThrownBy(() -> studentService.verificate(memberId, request))
+ assertThatThrownBy(() -> studentService.verify(memberId, request))
.isInstanceOf(BadRequestException.class)
.hasMessage(INVALID_STUDENT_VERIFICATION_CODE.getMessage());
}
@@ -225,7 +229,47 @@ class 학생_인증 {
// when & then
assertThatNoException()
- .isThrownBy(() -> studentService.verificate(memberId, request));
+ .isThrownBy(() -> studentService.verify(memberId, request));
+ }
+ }
+
+ @Nested
+ class 멤버_아이디로_인증정보_조회 {
+
+ @Test
+ void 학생_인증된_멤버의_경우() {
+ // given
+ Long memberId = 1L;
+ School school = SchoolFixture.school().id(2L).build();
+ Student student = StudentFixture.student().id(3L).school(school).build();
+ given(studentRepository.findByMemberIdWithFetch(memberId))
+ .willReturn(Optional.of(student));
+
+ // when
+ StudentResponse actual = studentService.findVerification(memberId);
+
+ // then
+ assertSoftly(softly -> {
+ softly.assertThat(actual.isVerified()).isTrue();
+ softly.assertThat(actual.school().id()).isEqualTo(school.getId());
+ });
+ }
+
+ @Test
+ void 학생_인증되지_않은_사용자의_경우() {
+ // given
+ Long memberId = 1L;
+ given(studentRepository.findByMemberIdWithFetch(memberId))
+ .willReturn(Optional.empty());
+
+ // when
+ StudentResponse actual = studentService.findVerification(memberId);
+
+ // then
+ assertSoftly(softly -> {
+ softly.assertThat(actual.isVerified()).isFalse();
+ softly.assertThat(actual.school()).isNull();
+ });
}
}
}
diff --git a/backend/src/test/java/com/festago/domain/VerificationCodeTest.java b/backend/src/test/java/com/festago/student/domain/VerificationCodeTest.java
similarity index 67%
rename from backend/src/test/java/com/festago/domain/VerificationCodeTest.java
rename to backend/src/test/java/com/festago/student/domain/VerificationCodeTest.java
index 4cccf3fc4..c9c4da5c0 100644
--- a/backend/src/test/java/com/festago/domain/VerificationCodeTest.java
+++ b/backend/src/test/java/com/festago/student/domain/VerificationCodeTest.java
@@ -1,10 +1,9 @@
-package com.festago.domain;
+package com.festago.student.domain;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import com.festago.common.exception.InternalServerException;
-import com.festago.student.domain.VerificationCode;
+import com.festago.common.exception.UnexpectedException;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
import org.junit.jupiter.api.Test;
@@ -19,7 +18,8 @@ class VerificationCodeTest {
void null_이면_예외() {
// when & then
assertThatThrownBy(() -> new VerificationCode(null))
- .isInstanceOf(InternalServerException.class);
+ .isInstanceOf(UnexpectedException.class)
+ .hasMessage("VerificationCode는 null 또는 공백이 될 수 없습니다.");
}
@ParameterizedTest
@@ -27,21 +27,24 @@ class VerificationCodeTest {
void 길이가_6자리가_아니면_예외(String code) {
// when & then
assertThatThrownBy(() -> new VerificationCode(code))
- .isInstanceOf(InternalServerException.class);
+ .isInstanceOf(UnexpectedException.class)
+ .hasMessage("VerificationCode의 길이는 6 이어야 합니다.");
}
@Test
void 숫자가_아니면_예외() {
// when & then
assertThatThrownBy(() -> new VerificationCode("일이삼사오육"))
- .isInstanceOf(InternalServerException.class);
+ .isInstanceOf(UnexpectedException.class)
+ .hasMessage("VerificationCode는 0~9의 양수 형식이어야 합니다.");
}
@Test
void 음수이면_예외() {
// when & then
assertThatThrownBy(() -> new VerificationCode("-12345"))
- .isInstanceOf(InternalServerException.class);
+ .isInstanceOf(UnexpectedException.class)
+ .hasMessage("VerificationCode는 0~9의 양수 형식이어야 합니다.");
}
@Test
diff --git a/backend/src/test/java/com/festago/domain/RandomVerificationCodeProviderTest.java b/backend/src/test/java/com/festago/student/infrastructure/RandomVerificationCodeProviderTest.java
similarity index 86%
rename from backend/src/test/java/com/festago/domain/RandomVerificationCodeProviderTest.java
rename to backend/src/test/java/com/festago/student/infrastructure/RandomVerificationCodeProviderTest.java
index 1bf3e8a23..f070b0363 100644
--- a/backend/src/test/java/com/festago/domain/RandomVerificationCodeProviderTest.java
+++ b/backend/src/test/java/com/festago/student/infrastructure/RandomVerificationCodeProviderTest.java
@@ -1,9 +1,8 @@
-package com.festago.domain;
+package com.festago.student.infrastructure;
import static org.assertj.core.api.Assertions.assertThat;
import com.festago.student.domain.VerificationCode;
-import com.festago.student.infrastructure.RandomVerificationCodeProvider;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
import org.junit.jupiter.api.Test;
diff --git a/backend/src/test/java/com/festago/student/repository/StudentRepositoryTest.java b/backend/src/test/java/com/festago/student/repository/StudentRepositoryTest.java
new file mode 100644
index 000000000..6076dab94
--- /dev/null
+++ b/backend/src/test/java/com/festago/student/repository/StudentRepositoryTest.java
@@ -0,0 +1,50 @@
+package com.festago.student.repository;
+
+import static org.assertj.core.api.SoftAssertions.assertSoftly;
+
+import com.festago.member.domain.Member;
+import com.festago.member.repository.MemberRepository;
+import com.festago.school.domain.School;
+import com.festago.school.repository.SchoolRepository;
+import com.festago.student.domain.Student;
+import com.festago.support.MemberFixture;
+import com.festago.support.SchoolFixture;
+import com.festago.support.StudentFixture;
+import java.util.Optional;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+
+@DisplayNameGeneration(ReplaceUnderscores.class)
+@SuppressWarnings("NonAsciiCharacters")
+@DataJpaTest
+class StudentRepositoryTest {
+
+ @Autowired
+ StudentRepository studentRepository;
+
+ @Autowired
+ SchoolRepository schoolRepository;
+
+ @Autowired
+ MemberRepository memberRepository;
+
+ @Test
+ void 멤버id로_조회() {
+ // given
+ Member member = memberRepository.save(MemberFixture.member().build());
+ School school = schoolRepository.save(SchoolFixture.school().build());
+ Student student = studentRepository.save(StudentFixture.student().school(school).member(member).build());
+
+ // when
+ Optional actual = studentRepository.findByMemberIdWithFetch(member.getId());
+
+ // then
+ assertSoftly(softly -> {
+ softly.assertThat(actual).isNotEmpty();
+ softly.assertThat(actual.get().getId()).isEqualTo(student.getId());
+ });
+ }
+}
diff --git a/backend/src/test/java/com/festago/application/integration/ApplicationIntegrationTest.java b/backend/src/test/java/com/festago/support/ApplicationIntegrationTest.java
similarity index 77%
rename from backend/src/test/java/com/festago/application/integration/ApplicationIntegrationTest.java
rename to backend/src/test/java/com/festago/support/ApplicationIntegrationTest.java
index e24b3510d..6bd69a561 100644
--- a/backend/src/test/java/com/festago/application/integration/ApplicationIntegrationTest.java
+++ b/backend/src/test/java/com/festago/support/ApplicationIntegrationTest.java
@@ -1,6 +1,5 @@
-package com.festago.application.integration;
+package com.festago.support;
-import com.festago.support.DatabaseClearExtension;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
diff --git a/backend/src/test/java/com/festago/support/StudentFixture.java b/backend/src/test/java/com/festago/support/StudentFixture.java
new file mode 100644
index 000000000..d57e0fa8a
--- /dev/null
+++ b/backend/src/test/java/com/festago/support/StudentFixture.java
@@ -0,0 +1,44 @@
+package com.festago.support;
+
+import com.festago.member.domain.Member;
+import com.festago.school.domain.School;
+import com.festago.student.domain.Student;
+
+public class StudentFixture {
+
+ private Long id;
+ private Member member = MemberFixture.member().build();
+ private School school = SchoolFixture.school().build();
+ private String username = "xxeol2";
+
+ private StudentFixture() {
+ }
+
+ public static StudentFixture student() {
+ return new StudentFixture();
+ }
+
+ public StudentFixture id(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public StudentFixture member(Member member) {
+ this.member = member;
+ return this;
+ }
+
+ public StudentFixture school(School school) {
+ this.school = school;
+ return this;
+ }
+
+ public StudentFixture username(String username) {
+ this.username = username;
+ return this;
+ }
+
+ public Student build() {
+ return new Student(id, member, school, username);
+ }
+}
diff --git a/backend/src/test/java/com/festago/application/TicketServiceTest.java b/backend/src/test/java/com/festago/ticket/application/TicketServiceTest.java
similarity index 92%
rename from backend/src/test/java/com/festago/application/TicketServiceTest.java
rename to backend/src/test/java/com/festago/ticket/application/TicketServiceTest.java
index cca7c198a..5ba1bc2fe 100644
--- a/backend/src/test/java/com/festago/application/TicketServiceTest.java
+++ b/backend/src/test/java/com/festago/ticket/application/TicketServiceTest.java
@@ -1,4 +1,4 @@
-package com.festago.application;
+package com.festago.ticket.application;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@@ -6,7 +6,6 @@
import com.festago.stage.domain.Stage;
import com.festago.support.StageFixture;
import com.festago.support.TicketFixture;
-import com.festago.ticket.application.TicketService;
import com.festago.ticket.domain.Ticket;
import com.festago.ticket.domain.TicketType;
import com.festago.ticket.dto.StageTicketResponse;
@@ -21,9 +20,9 @@
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
-@ExtendWith(MockitoExtension.class)
@DisplayNameGeneration(ReplaceUnderscores.class)
@SuppressWarnings("NonAsciiCharacters")
+@ExtendWith(MockitoExtension.class)
class TicketServiceTest {
@Mock
@@ -41,7 +40,7 @@ class TicketServiceTest {
TicketFixture.ticket().id(1L).ticketType(TicketType.STUDENT).stage(stage).build(),
TicketFixture.ticket().id(2L).ticketType(TicketType.VISITOR).stage(stage).build()
);
- given(ticketRepository.findAllByStageId(stageId))
+ given(ticketRepository.findAllByStageIdWithFetch(stageId))
.willReturn(tickets);
// when
diff --git a/backend/src/test/java/com/festago/application/integration/TicketServiceIntegrationTest.java b/backend/src/test/java/com/festago/ticket/application/integration/TicketServiceIntegrationTest.java
similarity index 97%
rename from backend/src/test/java/com/festago/application/integration/TicketServiceIntegrationTest.java
rename to backend/src/test/java/com/festago/ticket/application/integration/TicketServiceIntegrationTest.java
index 8f50652b7..b77516f87 100644
--- a/backend/src/test/java/com/festago/application/integration/TicketServiceIntegrationTest.java
+++ b/backend/src/test/java/com/festago/ticket/application/integration/TicketServiceIntegrationTest.java
@@ -1,4 +1,4 @@
-package com.festago.application.integration;
+package com.festago.ticket.application.integration;
import static com.festago.common.exception.ErrorCode.STAGE_NOT_FOUND;
import static org.assertj.core.api.Assertions.assertThat;
@@ -12,6 +12,7 @@
import com.festago.school.repository.SchoolRepository;
import com.festago.stage.domain.Stage;
import com.festago.stage.repository.StageRepository;
+import com.festago.support.ApplicationIntegrationTest;
import com.festago.support.FestivalFixture;
import com.festago.support.SchoolFixture;
import com.festago.support.StageFixture;
diff --git a/backend/src/test/java/com/festago/domain/TicketAmountTest.java b/backend/src/test/java/com/festago/ticket/domain/TicketAmountTest.java
similarity index 92%
rename from backend/src/test/java/com/festago/domain/TicketAmountTest.java
rename to backend/src/test/java/com/festago/ticket/domain/TicketAmountTest.java
index 167eff3c6..2e4828884 100644
--- a/backend/src/test/java/com/festago/domain/TicketAmountTest.java
+++ b/backend/src/test/java/com/festago/ticket/domain/TicketAmountTest.java
@@ -1,10 +1,9 @@
-package com.festago.domain;
+package com.festago.ticket.domain;
import static com.festago.common.exception.ErrorCode.TICKET_SOLD_OUT;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import com.festago.common.exception.BadRequestException;
-import com.festago.ticket.domain.TicketAmount;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
import org.junit.jupiter.api.Test;
diff --git a/backend/src/test/java/com/festago/ticket/domain/TicketEntryTimeTest.java b/backend/src/test/java/com/festago/ticket/domain/TicketEntryTimeTest.java
new file mode 100644
index 000000000..d4aa10e4d
--- /dev/null
+++ b/backend/src/test/java/com/festago/ticket/domain/TicketEntryTimeTest.java
@@ -0,0 +1,27 @@
+package com.festago.ticket.domain;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import com.festago.common.exception.ValidException;
+import java.time.LocalDateTime;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+@DisplayNameGeneration(ReplaceUnderscores.class)
+@SuppressWarnings("NonAsciiCharacters")
+class TicketEntryTimeTest {
+
+ @ParameterizedTest
+ @ValueSource(ints = {0, -1})
+ void 총_수량이_0_또는_음수일_경우_에외(int amount) {
+ // given
+ LocalDateTime now = LocalDateTime.now();
+
+ // when & then
+ assertThatThrownBy(() -> new TicketEntryTime(now, amount))
+ .isInstanceOf(ValidException.class)
+ .hasMessage("amount은/는 1 이상이어야 합니다.");
+ }
+}
diff --git a/backend/src/test/java/com/festago/domain/TicketTest.java b/backend/src/test/java/com/festago/ticket/domain/TicketTest.java
similarity index 98%
rename from backend/src/test/java/com/festago/domain/TicketTest.java
rename to backend/src/test/java/com/festago/ticket/domain/TicketTest.java
index d22c54593..c63bf3b87 100644
--- a/backend/src/test/java/com/festago/domain/TicketTest.java
+++ b/backend/src/test/java/com/festago/ticket/domain/TicketTest.java
@@ -1,4 +1,4 @@
-package com.festago.domain;
+package com.festago.ticket.domain;
import static com.festago.common.exception.ErrorCode.EARLY_TICKET_ENTRY_THAN_OPEN;
import static com.festago.common.exception.ErrorCode.EARLY_TICKET_ENTRY_TIME;
@@ -18,7 +18,6 @@
import com.festago.support.MemberFixture;
import com.festago.support.StageFixture;
import com.festago.support.TicketFixture;
-import com.festago.ticket.domain.Ticket;
import com.festago.ticketing.domain.MemberTicket;
import java.time.LocalDateTime;
import org.junit.jupiter.api.DisplayNameGeneration;
@@ -189,7 +188,7 @@ class 예매_티켓_생성 {
}
@ParameterizedTest
- @ValueSource(ints = {0, 100})
+ @ValueSource(ints = {1, 100})
void 성공(int reservationSequence) {
// given
LocalDateTime stageStartTime = LocalDateTime.parse("2022-08-12T18:00:00");
diff --git a/backend/src/test/java/com/festago/domain/TicketRepositoryTest.java b/backend/src/test/java/com/festago/ticket/repository/TicketRepositoryTest.java
similarity index 92%
rename from backend/src/test/java/com/festago/domain/TicketRepositoryTest.java
rename to backend/src/test/java/com/festago/ticket/repository/TicketRepositoryTest.java
index 9c9361b71..c093a5b69 100644
--- a/backend/src/test/java/com/festago/domain/TicketRepositoryTest.java
+++ b/backend/src/test/java/com/festago/ticket/repository/TicketRepositoryTest.java
@@ -1,4 +1,4 @@
-package com.festago.domain;
+package com.festago.ticket.repository;
import static org.assertj.core.api.Assertions.assertThat;
@@ -14,7 +14,6 @@
import com.festago.support.TicketFixture;
import com.festago.ticket.domain.Ticket;
import com.festago.ticket.domain.TicketType;
-import com.festago.ticket.repository.TicketRepository;
import java.util.List;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
@@ -52,7 +51,7 @@ class TicketRepositoryTest {
ticketRepository.save(TicketFixture.ticket().stage(otherStage).build());
// when
- List actual = ticketRepository.findAllByStageId(stage.getId());
+ List actual = ticketRepository.findAllByStageIdWithFetch(stage.getId());
// then
assertThat(actual).hasSize(2);
diff --git a/backend/src/test/java/com/festago/application/MemberTicketServiceTest.java b/backend/src/test/java/com/festago/ticketing/application/MemberTicketServiceTest.java
similarity index 98%
rename from backend/src/test/java/com/festago/application/MemberTicketServiceTest.java
rename to backend/src/test/java/com/festago/ticketing/application/MemberTicketServiceTest.java
index 31f515d3f..3cd3fdbcf 100644
--- a/backend/src/test/java/com/festago/application/MemberTicketServiceTest.java
+++ b/backend/src/test/java/com/festago/ticketing/application/MemberTicketServiceTest.java
@@ -1,4 +1,4 @@
-package com.festago.application;
+package com.festago.ticketing.application;
import static com.festago.common.exception.ErrorCode.MEMBER_NOT_FOUND;
import static com.festago.common.exception.ErrorCode.MEMBER_TICKET_NOT_FOUND;
@@ -6,8 +6,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.BDDMockito.anyLong;
import static org.mockito.BDDMockito.given;
import com.festago.common.exception.BadRequestException;
@@ -18,7 +18,6 @@
import com.festago.support.MemberFixture;
import com.festago.support.MemberTicketFixture;
import com.festago.support.StageFixture;
-import com.festago.ticketing.application.MemberTicketService;
import com.festago.ticketing.domain.MemberTicket;
import com.festago.ticketing.dto.MemberTicketResponse;
import com.festago.ticketing.dto.MemberTicketsResponse;
@@ -39,9 +38,9 @@
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
-@ExtendWith(MockitoExtension.class)
@DisplayNameGeneration(ReplaceUnderscores.class)
@SuppressWarnings("NonAsciiCharacters")
+@ExtendWith(MockitoExtension.class)
class MemberTicketServiceTest {
@Mock
diff --git a/backend/src/test/java/com/festago/application/integration/MemberTicketIntegrationTest.java b/backend/src/test/java/com/festago/ticketing/application/integration/MemberTicketIntegrationTest.java
similarity index 95%
rename from backend/src/test/java/com/festago/application/integration/MemberTicketIntegrationTest.java
rename to backend/src/test/java/com/festago/ticketing/application/integration/MemberTicketIntegrationTest.java
index 275dc210b..5a0c814e2 100644
--- a/backend/src/test/java/com/festago/application/integration/MemberTicketIntegrationTest.java
+++ b/backend/src/test/java/com/festago/ticketing/application/integration/MemberTicketIntegrationTest.java
@@ -1,4 +1,4 @@
-package com.festago.application.integration;
+package com.festago.ticketing.application.integration;
import static org.assertj.core.api.Assertions.assertThat;
@@ -10,6 +10,7 @@
import com.festago.school.repository.SchoolRepository;
import com.festago.stage.domain.Stage;
import com.festago.stage.repository.StageRepository;
+import com.festago.support.ApplicationIntegrationTest;
import com.festago.support.FestivalFixture;
import com.festago.support.MemberFixture;
import com.festago.support.MemberTicketFixture;
diff --git a/backend/src/test/java/com/festago/application/integration/TicketingServiceIntegrationTest.java b/backend/src/test/java/com/festago/ticketing/application/integration/TicketingServiceIntegrationTest.java
similarity index 96%
rename from backend/src/test/java/com/festago/application/integration/TicketingServiceIntegrationTest.java
rename to backend/src/test/java/com/festago/ticketing/application/integration/TicketingServiceIntegrationTest.java
index 78fc657a8..adbae8124 100644
--- a/backend/src/test/java/com/festago/application/integration/TicketingServiceIntegrationTest.java
+++ b/backend/src/test/java/com/festago/ticketing/application/integration/TicketingServiceIntegrationTest.java
@@ -1,4 +1,4 @@
-package com.festago.application.integration;
+package com.festago.ticketing.application.integration;
import static com.festago.common.exception.ErrorCode.RESERVE_TICKET_OVER_AMOUNT;
import static org.assertj.core.api.Assertions.assertThat;
@@ -11,6 +11,7 @@
import com.festago.member.repository.MemberRepository;
import com.festago.school.repository.SchoolRepository;
import com.festago.stage.domain.Stage;
+import com.festago.support.ApplicationIntegrationTest;
import com.festago.support.MemberFixture;
import com.festago.ticketing.application.TicketingService;
import com.festago.ticketing.dto.TicketingRequest;
diff --git a/backend/src/test/java/com/festago/domain/EntryStateTest.java b/backend/src/test/java/com/festago/ticketing/domain/EntryStateTest.java
similarity index 70%
rename from backend/src/test/java/com/festago/domain/EntryStateTest.java
rename to backend/src/test/java/com/festago/ticketing/domain/EntryStateTest.java
index 1ea7bc851..23352d449 100644
--- a/backend/src/test/java/com/festago/domain/EntryStateTest.java
+++ b/backend/src/test/java/com/festago/ticketing/domain/EntryStateTest.java
@@ -1,11 +1,9 @@
-package com.festago.domain;
+package com.festago.ticketing.domain;
-import static com.festago.common.exception.ErrorCode.INVALID_ENTRY_STATE_INDEX;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import com.festago.common.exception.InternalServerException;
-import com.festago.ticketing.domain.EntryState;
+import com.festago.common.exception.UnexpectedException;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
import org.junit.jupiter.api.Test;
@@ -21,16 +19,16 @@ class EntryStateTest {
void 유효하지않은_인덱스로_생성시_예외(int index) {
// when & then
assertThatThrownBy(() -> EntryState.from(index))
- .isInstanceOf(InternalServerException.class)
- .hasMessage(INVALID_ENTRY_STATE_INDEX.getMessage());
+ .isInstanceOf(UnexpectedException.class)
+ .hasMessageStartingWith("entryState의 인덱스가 올바르지 않습니다.");
}
@Test
void 인덱스로_생성시_인자가_null이면_예외() {
// when & then
assertThatThrownBy(() -> EntryState.from(null))
- .isInstanceOf(InternalServerException.class)
- .hasMessage(INVALID_ENTRY_STATE_INDEX.getMessage());
+ .isInstanceOf(UnexpectedException.class)
+ .hasMessage("entryState의 인덱스는 null이 될 수 없습니다.");
}
@ParameterizedTest
diff --git a/backend/src/test/java/com/festago/domain/MemberTicketTest.java b/backend/src/test/java/com/festago/ticketing/domain/MemberTicketTest.java
similarity index 98%
rename from backend/src/test/java/com/festago/domain/MemberTicketTest.java
rename to backend/src/test/java/com/festago/ticketing/domain/MemberTicketTest.java
index 9189725ca..97858e740 100644
--- a/backend/src/test/java/com/festago/domain/MemberTicketTest.java
+++ b/backend/src/test/java/com/festago/ticketing/domain/MemberTicketTest.java
@@ -1,4 +1,4 @@
-package com.festago.domain;
+package com.festago.ticketing.domain;
import static com.festago.ticketing.domain.EntryState.AFTER_ENTRY;
import static com.festago.ticketing.domain.EntryState.AWAY;
@@ -11,7 +11,6 @@
import com.festago.support.FestivalFixture;
import com.festago.support.MemberTicketFixture;
import com.festago.support.StageFixture;
-import com.festago.ticketing.domain.MemberTicket;
import java.time.LocalDateTime;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
@@ -203,6 +202,5 @@ class 티켓_주인_검사 {
// when && then
assertThat(memberTicket.isOwner(memberId)).isFalse();
}
-
}
}
diff --git a/backend/src/test/java/com/festago/domain/MemberTicketRepositoryTest.java b/backend/src/test/java/com/festago/ticketing/repository/MemberTicketRepositoryTest.java
similarity index 98%
rename from backend/src/test/java/com/festago/domain/MemberTicketRepositoryTest.java
rename to backend/src/test/java/com/festago/ticketing/repository/MemberTicketRepositoryTest.java
index bafb2b838..5e78bc0c7 100644
--- a/backend/src/test/java/com/festago/domain/MemberTicketRepositoryTest.java
+++ b/backend/src/test/java/com/festago/ticketing/repository/MemberTicketRepositoryTest.java
@@ -1,4 +1,4 @@
-package com.festago.domain;
+package com.festago.ticketing.repository;
import static org.assertj.core.api.Assertions.assertThat;
@@ -19,7 +19,6 @@
import com.festago.support.StageFixture;
import com.festago.ticket.repository.TicketRepository;
import com.festago.ticketing.domain.MemberTicket;
-import com.festago.ticketing.repository.MemberTicketRepository;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
diff --git a/backend/src/test/resources/application-test.yml b/backend/src/test/resources/application-test.yml
index 44d7f0166..6895504ad 100644
--- a/backend/src/test/resources/application-test.yml
+++ b/backend/src/test/resources/application-test.yml
@@ -2,10 +2,7 @@ spring:
datasource:
url: jdbc:h2:mem:test;MODE=MYSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
jpa:
- properties:
- hibernate:
- format_sql: true
- show-sql: true
+ show-sql: false
hibernate:
ddl-auto: create
open-in-view: false
@@ -15,12 +12,6 @@ spring:
logging:
file:
path: ./
- level:
- org:
- hibernate:
- orm:
- jdbc:
- bind: trace
festago:
qr-secret-key: festagofestagofestagofestagofestagofestagofestagofestagofestagofestagofestagofestagofestagofestago