Skip to content

Commit

Permalink
[AN/USER] feat: 예매 실패 케이스 처리(#369) (#593)
Browse files Browse the repository at this point in the history
* feat: 에러 코드를 구분한다

* feat: API 요청 실패 결과에 따라 에러 코드를 예외로 던진다

* feat: 티켓 예매 요청에 실패하면 예외 이벤트가 발생한다.

* feat: 에러 코드에 따라 다른 메세지를 띄운다
  • Loading branch information
SeongHoonC authored and seokjin8678 committed Nov 16, 2023
1 parent 12ed714 commit 1732d72
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -31,9 +32,16 @@ class TicketDefaultRepository @Inject constructor(
.onSuccessOrCatch { it.toDomain() }

override suspend fun reserveTicket(ticketId: Int): Result<ReservedTicket> =
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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -75,7 +76,7 @@ class TicketReserveActivity : AppCompatActivity() {
)

is ReserveTicketSuccess -> handleReserveTicketSuccess(event.reservedTicket)
is ReserveTicketFailed -> handleReserveTicketFailed()
is ReserveTicketFailed -> handleReserveTicketFailed(event.errorCode)
is ShowSignIn -> handleShowSignIn()
}

Expand Down Expand Up @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -49,12 +50,9 @@ class TicketReserveViewModel @Inject constructor(
),
stages = it.reservationStages.toTicketReserveItems(),
)
}.onFailure {
}.onFailure { throwable ->
_uiState.value = TicketReserveUiState.Error
analyticsHelper.logNetworkFailure(
KEY_LOAD_RESERVATION_LOG,
it.message.toString(),
)
throwable.logNetworkFailure(KEY_LOAD_RESERVATION_LOG)
}
}
}
Expand All @@ -70,8 +68,9 @@ class TicketReserveViewModel @Inject constructor(
reservationTickets.sortedByTicketTypes(),
),
)
}.onFailure {
}.onFailure { throwable ->
_uiState.value = TicketReserveUiState.Error
throwable.logNetworkFailure(KEY_SHOW_TICKET_TYPES_LOG)
}
} else {
_event.emit(TicketReserveEvent.ShowSignIn)
Expand All @@ -84,8 +83,13 @@ class TicketReserveViewModel @Inject constructor(
ticketRepository.reserveTicket(ticketId)
.onSuccess {
_event.emit(TicketReserveEvent.ReserveTicketSuccess(it))
}.onFailure {
_event.emit(TicketReserveEvent.ReserveTicketFailed)
}.onFailure { throwable ->
if (throwable is ErrorCode) {
_event.emit(TicketReserveEvent.ReserveTicketFailed(throwable))
} else {
_event.emit(TicketReserveEvent.ReserveTicketFailed(ErrorCode.UNKNOWN()))
throwable.logNetworkFailure(KEY_RESERVE_TICKET)
}
}
}
}
Expand All @@ -105,8 +109,14 @@ class TicketReserveViewModel @Inject constructor(
it.toTicketReserveItem()
}

private fun Throwable.logNetworkFailure(key: String) {
analyticsHelper.logNetworkFailure(key, message.toString())
}

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"
}
}
4 changes: 4 additions & 0 deletions android/festago/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
<string name="ticket_reserve_tv_btn_reserve_ticket_not_open">MM월 dd일 HH:mm 오픈예정</string>
<string name="ticket_reserve_tv_signin">로그인 후 예매</string>
<string name="ticket_reserve_tv_ticket_type_prompt">예매 유형을 선택해주세요</string>
<string name="ticket_reserve_dialog_sold_out">공연이 매진되었습니다</string>
<string name="ticket_reserve_dialog_over_amount">이미 예매한 공연입니다</string>
<string name="ticket_reserve_dialog_need_student_verification">학생 인증이 필요합니다</string>
<string name="ticket_reserve_dialog_unknown">알 수 없는 오류가 발생했습니다</string>

<!-- Strings related to festival list -->
<string name="festival_list_tv_date_format">yyyy.MM.dd</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ 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
Expand Down Expand Up @@ -188,7 +189,7 @@ class TicketReserveViewModelTest {
}

@Test
fun `티켓 유형을 선택하고 예약하면 예약 성공 이벤트가 발생한다`() = runTest {
fun `티켓 유형을 선택하고 예약하면 예매 성공 이벤트가 발생한다`() = runTest {
// given
coEvery {
ticketRepository.reserveTicket(any())
Expand All @@ -206,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()))

Expand All @@ -215,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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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()
}

0 comments on commit 1732d72

Please sign in to comment.