Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ data class OttItemResponseDto(
val name: String,
@SerialName("logoUrl")
val logoUrl: String,
// 서버 응답(GetOttResponse)에 없는 필드. 기본값이 없으면 역직렬화가 실패한다
@SerialName("contentUrl")
val contentUrl: String
val contentUrl: String = "",
)
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ data class UserProfileResponseDto(
val isFliner: Boolean,
@SerialName("nickname")
val nickname: String,
// 내 프로필(/users/me) 응답에만 존재, 이메일 미보유 시 null
@SerialName("email")
val email: String? = null,
@SerialName("keywordRecalculatable")
val keywordRecalculatable: Boolean = false,
)
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ fun UserProfileResponseDto.toModel(): UserProfileResponseModel =
isFliner = isFliner,
nickname = nickname,
profileImageUrl = profileImageUrl,
email = email,
keywordRecalculatable = keywordRecalculatable,
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ data class UserProfileResponseModel(
val isFliner: Boolean,
val nickname: String,
val profileImageUrl: String?,
val email: String? = null,
val keywordRecalculatable: Boolean = false,
) {
companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ fun HomeRoute(
onFamousCollectionItemClick = { navigateToCollectionDetail(it) },
onFamousCollectionAllClick = { navigateToCollectionList(CollectionListRouteType.FAMOUS) },
onRecommendCollectionItemClick = { navigateToCollectionDetail(it) },
onSavedContentItemClick = { viewModel.getOttListPerContent(it) },
onSavedContentItemClick = { viewModel.showOttList(it) },
modifier = Modifier.padding(paddingValues),
)
}
Expand Down
42 changes: 35 additions & 7 deletions app/src/main/java/com/flint/presentation/home/HomeViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import com.flint.core.common.util.UiState
import com.flint.data.local.PreferencesManager
import com.flint.domain.model.collection.CollectionListModel
import com.flint.domain.model.content.BookmarkedContentListModel
import com.flint.domain.repository.ContentRepository
import com.flint.domain.model.ott.OttListModel
import com.flint.domain.model.ott.OttModel
import com.flint.domain.repository.HomeRepository
import com.flint.domain.repository.UserRepository
import com.flint.presentation.home.sideeffect.HomeSideEffect
import com.flint.presentation.home.uistate.HomeUiState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
Expand All @@ -29,7 +31,6 @@ class HomeViewModel @Inject constructor(
private val preferencesManager: PreferencesManager,
private val homeRepository: HomeRepository,
private val userRepository: UserRepository,
private val contentRepository: ContentRepository,
) : ViewModel() {

private val _userName = preferencesManager.getString(USER_NAME)
Expand Down Expand Up @@ -71,7 +72,18 @@ class HomeViewModel @Inject constructor(

fun getBookmarkedContentList() = viewModelScope.launch {
userRepository.getUserBookmarkedContents(userId = null)
.onSuccess { _bookmarkedContentListLoadState.emit(UiState.Success(it)) }
.onSuccess { bookmarkedContents ->
// 홈에서는 최근 저장한 콘텐츠 10개까지만 노출 (전체 목록은 프로필 > 저장한 콘텐츠에서 확인)
_bookmarkedContentListLoadState.emit(
UiState.Success(
bookmarkedContents.copy(
contents = bookmarkedContents.contents
.take(MAX_SAVED_CONTENT_COUNT)
.toImmutableList(),
Comment on lines +80 to +82

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# UserRepository 선언과 구현을 확인합니다.
fd -t f 'UserRepository.kt' app/src/main/java -x ast-grep outline {} --items all

# 호출부와 구현부의 정렬·페이지네이션 계약을 확인합니다.
rg -n -C 8 --glob '*.kt' '\bgetUserBookmarkedContents\s*\(' app/src/main/java
rg -n -C 8 --glob '*.kt' 'bookmarks|savedAt|createdAt|sort|cursor' app/src/main/java

Repository: imflint/Flint-Android

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== UserRepository relevant implementation =="
sed -n '68,120p' app/src/main/java/com/flint/domain/repository/UserRepository.kt

echo
echo "== ContentApi bookmark DTOs =="
fd -t f 'MyBookmarkedContentListResponseDto.kt|BookmarkedContentItemDto.kt|BookmarkedContentItemModel.kt|ContentModelNew.kt' app/src/main/java -x sh -c 'echo "--- $1"; ast-grep outline "$1" --items all; sed -n "1,140p" "$1"' sh {}

echo
echo "== bookmark date fields and sort mentions in relevant API/dto/domain =="
rg -n -C 3 --glob '*.kt' 'bookmarked|savedAt|savedAtMs|createdAt|createdAtMs|sort|order|size|MyBookmarkedContent|BookmarkedContentItem|contentId|id' app/src/main/java/com/flint/data/dto/app/src/main/java/com/flint/domain/model

echo
echo "== HomeViewModel relevant state/comparator derivation =="
sed -n '1,130p' app/src/main/java/com/flint/presentation/home/HomeViewModel.kt

Repository: imflint/Flint-Android

Length of output: 2585


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== focused repository implementation slices =="
sed -n '68,120p' app/src/main/java/com/flint/domain/repository/UserRepository.kt

echo
echo "== bookmark-related DTO/model files =="
fd -i '.*bookmark*.kt$|.*savedContent*.kt$|.*content.*dto.*\.kt$|.*content.*model.*\.kt$' app/src/main/java -x sh -c '
  path="$1"
  name=$(basename "$path")
  printf "### %s\n" "$path"
  wc -l "$path"
  sed -n "1,180p" "$path"
' sh {}

echo
echo "== sorted bookmark API parameters and saved timestamps =="
rg -n -C 3 --glob '*.kt' '(/contents/bookmarks|bookmarked-contents|bookmarkedContents|savedAt|savedAtMs|createdAt|createdAtMs|sort|order|size|cursor|MyBookmarkedContentListResponseDto|MyBookmarkedContentListDto|BookmarkedContentItemDto|BookmarkedContentItemModel)' app/src/main/java

echo
echo "== HomeViewModel relevant state/derivation =="
sed -n '1,120p' app/src/main/java/com/flint/presentation/home/HomeViewModel.kt

Repository: imflint/Flint-Android

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== UserApi bookmark endpoint declaration =="
fd -t f 'UserApi.kt' app/src/main/java -x sh -c 'echo "--- $1"; sed -n "1,120p" "$1"' sh {}

echo
echo "== Bookmark DTO/model files =="
fd -t f 'BookmarkedContent.*Dto.kt|BookmarkedContent.*Model.kt|MyBookmarkedContent.*Dto.kt|MyBookmarkedContent.*Model.kt|Bookmark.*Dto.kt|Bookmark.*Model.kt' app/src/main/java \
  | sed -n '1,80p' \
  | xargs -r sh -c 'echo "--- $1"; wc -l "$1"; sed -n "1,200p" "$1"' sh {}

echo
echo "== Exact bookmark timestamp/sort/sortBy fields in relevant DTOs/models (case-insensitive) =="
rg -n --iglob '*.kt' '(\bbookmarked|SavedAt|savedAt|createdAt|created_at|sort|Sort|sortBy|SortBy|cursor|size|BookmarkedContent|MyBookmarkedContent|bookmarked-contents|contents/bookmarks)' app/src/main/java/com/flint/data/app/src/main/java/com/flint/domain \
  | rg -n '(Dto|Model|`@GET`|`@Query`|bookmarked|SavedAt|savedAt|createdAt|created_at|sort|Sort|sortBy|SortBy|contents/bookmarks|bookmarked-contents)' \
  | sed -n '1,220p'

Repository: imflint/Flint-Android

Length of output: 3394


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== bookmark DTO/model files =="
fd -t f '.*Bookmark.*\.kt$' app/src/main/java/com/flint/data/dto app/src/main/java/com/flint/domain/model/content | sort

echo
echo "== bookmark timestamps, sorts, and params in bookmark DTOs/models =="
python3 - <<'PY'
import subprocess, pathlib, re
base = pathlib.Path("app/src/main/java")
files = [
    p for f in subprocess.check_output(["fd", "-t", "f", r'.*Bookmark.*\.kt$', "app/src/main/java"], text=True).splitlines()
    for p in [base/f] if p.is_file()
]
terms = ["BookmarkedContent", "MyBookmarkedContent", "BookmarkedContentListResponseDto", "BookmarkedContentItemDto", "BookmarkedContentItemModel", "savedAt", "createdAt", "sort", "sortBy", "cursor", "size", "contents/bookmarks", "bookmarked-contents"]
for f in files:
    text = f.read_text(errors="replace")
    matches = [i+1 for i, line in enumerate(text.splitlines()) if any(t.lower() in line.lower() for t in terms)]
    if matches:
        print(f"--- {f}")
        for line in matches:
            print(f"{line}: {text.splitlines()[line-1]}")
PY

echo
echo "== HomeViewModel relevant lines =="
sed -n '73,83p' app/src/main/java/com/flint/presentation/home/HomeViewModel.kt

Repository: imflint/Flint-Android

Length of output: 1308


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== BookmarkedContentListResponseDto and mapper =="
for f in \
  app/src/main/java/com/flint/data/dto/content/response/BookmarkedContentListResponseDto.kt \
  app/src/main/java/com/flint/domain/mapper/content/BookmarkedContentMapper.kt
do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,220p' "$f"
done

echo
echo "== ContentApi bookmark API declaration =="
sed -n '1,25p' app/src/main/java/com/flint/data/api/ContentApi.kt

echo
echo "== Any API params/fields mentioning saved time or sort for bookmarks (focused) =="
python3 - <<'PY'
from pathlib import Path
terms = ["contents/bookmarks", "bookmarked-contents", "BookmarkedContentListResponseDto", "savedAt", "createdAt", "cursor", "size", "sort", "sortBy"]
for p in Path("app/src/main/java").rglob("*.kt"):
    if "test" in p.parts:
        continue
    lines = p.read_text(errors="replace").splitlines()
    hits = [
        (i+1, line.strip())
        for i, line in enumerate(lines)
        if any(t in line for t in terms)
    ]
    if not hits:
        continue
    print(f"--- {p}")
    for n, line in hits:
        print(f"{n}: {line}")
PY

Repository: imflint/Flint-Android

Length of output: 2242


홈 저장 콘텐츠 목록 정렬 계약을 명시하거나 take() 앞에서 정렬하세요.

getContentBookmarkedContents(userId == null)/api/v1/contents/bookmarks를 cursor로 페이지네이션하고 taken 순서로 contents에 누적합니다. 저장 시간 필드가 응답 DTO에 없으므로 take(MAX_SAVED_CONTENT_COUNT) 순서가 최신 10건이 되지 않을 수 있습니다. 저장 시간 내림차순을 보장해주지 않으면 getUserBookmarkedContents()에서 정렬이나 save-date 필드를 추가하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/flint/presentation/home/HomeViewModel.kt` around lines
80 - 82, Update getUserBookmarkedContents() and the bookmarked-content flow so
contents are ordered by save date descending before
take(MAX_SAVED_CONTENT_COUNT), or add the save-date field to the response DTO to
enable that ordering. Ensure the resulting home list always contains the latest
saved 10 items, and keep the immutable-list conversion after limiting.

),
),
)
}
.onFailure { Timber.e(it.message) }
}

Expand All @@ -81,9 +93,25 @@ class HomeViewModel @Inject constructor(
.onFailure { Timber.e(it.message) }
}

fun getOttListPerContent(contentId: String) = viewModelScope.launch {
contentRepository.getOttListPerContent(contentId)
.onSuccess { _homeSideEffect.emit(HomeSideEffect.ShowOttListBottomSheet(it)) }
.onFailure { Timber.e(it.message) }
// 콘텐츠별 OTT 목록 API(/contents/ott/{id})가 빈 배열만 반환하므로
// 이미 로드된 북마크 목록의 OTT 정보를 사용한다.
// 프로필/저장한 콘텐츠 화면도 동일하게 getOttSimpleList를 쓴다.
fun showOttList(contentId: String) = viewModelScope.launch {
val otts = (_bookmarkedContentListLoadState.value as? UiState.Success)
?.data
?.contents
?.find { it.id == contentId }
?.getOttSimpleList
.orEmpty()

_homeSideEffect.emit(
HomeSideEffect.ShowOttListBottomSheet(
OttListModel(otts = otts.map { OttModel(name = it.name) }),
),
)
}

companion object {
private const val MAX_SAVED_CONTENT_COUNT = 10
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ data class OnboardingTermsUiState(

enum class NicknameErrorType {
DUPLICATE, // 이미 사용 중인 닉네임
INVALID_FORMAT // 한글, 영문 외 문자 포함
INVALID_FORMAT // 한글, 영문, 숫자 외 문자 포함
}

data class OnboardingProfileUiState(
Expand All @@ -29,7 +29,7 @@ data class OnboardingProfileUiState(
companion object {
const val MAX_LENGTH = 8
const val MIN_LENGTH = 2
private val NICKNAME_REGEX = Regex("^[가-힣ㄱ-ㅎㅏ-ㅣa-zA-Z]+$")
private val NICKNAME_REGEX = Regex("^[가-힣ㄱ-ㅎㅏ-ㅣa-zA-Z0-9]+$")
private val STANDALONE_KOREAN_REGEX = Regex("[ㄱ-ㅎㅏ-ㅣ]")

fun isValidFormat(nickname: String): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package com.flint.presentation.setting

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
Expand All @@ -27,6 +29,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
Expand Down Expand Up @@ -111,7 +114,20 @@ private fun SettingScreen(
SettingMenuItem(
label = "계정",
trailingContent = {
Row(verticalAlignment = Alignment.CenterVertically) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
uiState.email?.let { email ->
Text(
text = email,
style = FlintTheme.typography.body2R14,
color = FlintTheme.colors.gray100,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f, fill = false),
)
}
Comment on lines +117 to +130

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant file around the reported lines and nearby layout code.
if [ -f "app/src/main/java/com/flint/presentation/setting/SettingScreen.kt" ]; then
  echo "== file exists =="
  wc -l app/src/main/java/com/flint/presentation/setting/SettingScreen.kt
  echo "== relevant lines 100-140 =="
  sed -n '100,140p' app/src/main/java/com/flint/presentation/setting/SettingScreen.kt | nl -ba -v100
  echo "== relevant lines 220-285 =="
  sed -n '220,285p' app/src/main/java/com/flint/presentation/setting/SettingScreen.kt | nl -ba -v220
else
  echo "file not found"
  fd -i 'SettingScreen.kt'
fi

Repository: imflint/Flint-Android

Length of output: 316


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== SettingScreen.kt file =="
wc -l app/src/main/java/com/flint/presentation/setting/SettingScreen.kt

echo "== lines 100-140 =="
sed -n '100,140p' app/src/main/java/com/flint/presentation/setting/SettingScreen.kt

echo "== lines 220-285 =="
sed -n '220,285p' app/src/main/java/com/flint/presentation/setting/SettingScreen.kt

echo "== occurrences of SettingMenuItem / main row usage =="
rg -n "SettingMenuItem|SettingScreenUiState|email|Composable|`@Preview`" app/src/main/java/com/flint/presentation/setting/SettingScreen.kt

Repository: imflint/Flint-Android

Length of output: 4275


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== compose dependencies referencing layout code =="
if [ -f "app/build.gradle.kts" ]; then
  cat app/build.gradle.kts
elif [ -f "app/build.gradle" ]; then
  cat app/build.gradle
fi

echo "== find settings-related test files =="
fd -i 'Setting.*Test.*|.*Setting.*Test.*' . || true
fd -i 'SettingScreen.*Test.*|.*SettingScreen.*Test.*' . || true

echo "== search for long/truncation/compose UI tests =="
rg -n "composeTestRule|createComposeRule|Long|long|overflow|EllipseEllipsis|TextOverflow.Ellipsis|setting|SettingScreen" app/src/test app/src/androidTest app/src/main/java/com/flint/presentation/setting || true

echo "== Compose weight semantics probe (source only if Gradle dependency unavailable) =="
python3 - <<'PY'
print("The code shows outer Row has label Modifier.weight(1f).")
print("The trailing Row has no modifier for its own width.")
print("The email Text has modifier Modifier.weight(1f, fill = false).")
print("In Compose, an unbounded weight in a nested Row with no width constraint cannot determine its measured width.")
print("This matches a potential truncation problem for long email values under fixed-width parent Row.")
PY

Repository: imflint/Flint-Android

Length of output: 6760


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== SettingUiState =="
cat app/src/main/java/com/flint/presentation/setting/SettingUiState.kt

echo "== root version catalog references =="
if [ -f "gradle/libs.versions.toml" ]; then
  sed -n '1,180p' gradle/libs.versions.toml
fi

echo "== all SettingScreen references =="
rg -n "SettingScreen|SettingUiState\\(" .

Repository: imflint/Flint-Android

Length of output: 6549


긴 이메일도 SettingMenuItem의 제한된 폭 안에서 말줄임되도록 처리해 주세요.

emailModifier.weight(1f, fill = false)는 중첩 Row 안에서만 적용됩니다. 이 중첩 Row는 현재 폭 제약을 제공하지 않아 긴 이메일이 말줄임 없이 가로로 확장될 수 있습니다.

중첩 Row에 상위 Row의 가용 폭을 전달하거나, 이메일과 아이콘을 SettingMenuItem의 직접 자식으로 배치해 폭 제한을 두어 주세요.

예시 수정
                     Row(
+                        modifier = Modifier.weight(1f, fill = false),
                         horizontalArrangement = Arrangement.spacedBy(8.dp),
                         verticalAlignment = Alignment.CenterVertically,
                     ) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
uiState.email?.let { email ->
Text(
text = email,
style = FlintTheme.typography.body2R14,
color = FlintTheme.colors.gray100,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f, fill = false),
)
}
Row(
modifier = Modifier.weight(1f, fill = false),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
uiState.email?.let { email ->
Text(
text = email,
style = FlintTheme.typography.body2R14,
color = FlintTheme.colors.gray100,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f, fill = false),
)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/flint/presentation/setting/SettingScreen.kt` around
lines 117 - 130, 중첩된 Row가 SettingMenuItem의 가용 폭을 사용하도록 수정하고, 그 안의 email Text가 남은
공간만 차지하게 하여 TextOverflow.Ellipsis가 동작하도록 하세요. SettingMenuItem의 제한된 폭과 아이콘 영역을
침범하지 않도록 Row의 weight 또는 동등한 폭 제약을 적용하고, Text의 기존 말줄임 설정은 유지하세요.

Image(
painter = painterResource(R.drawable.ic_kakao_full),
contentDescription = null,
Expand Down Expand Up @@ -226,7 +242,7 @@ private fun SettingMenuItem(
modifier: Modifier = Modifier,
verticalPadding: Dp = 18.dp,
onClick: () -> Unit = {},
trailingContent: @Composable () -> Unit = {},
trailingContent: @Composable RowScope.() -> Unit = {},
) {
Row(
modifier = modifier
Expand All @@ -253,6 +269,7 @@ private fun SettingScreenPreview() {
uiState = SettingUiState(
nickname = "한비두비세비",
profileImageUrl = null,
email = "flint@kakao.com",
),
onBackClick = {},
onEditProfileClick = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ package com.flint.presentation.setting
data class SettingUiState(
val nickname: String = "",
val profileImageUrl: String? = null,
val email: String? = null,
val isLogoutDialogVisible: Boolean = false,
)
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ class SettingViewModel @Inject constructor(
viewModelScope.launch {
userRepository.getUserProfile(userId = null)
.onSuccess { profile ->
_uiState.update { it.copy(profileImageUrl = profile.profileImageUrl) }
_uiState.update {
it.copy(
profileImageUrl = profile.profileImageUrl,
email = profile.email,
)
}
}
.onFailure { Timber.e(it) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ data class EditProfileUiState(
companion object {
const val MAX_LENGTH = 8
const val MIN_LENGTH = 2
private val NICKNAME_REGEX = Regex("^[가-힣ㄱ-ㅎㅏ-ㅣa-zA-Z]+$")
private val NICKNAME_REGEX = Regex("^[가-힣ㄱ-ㅎㅏ-ㅣa-zA-Z0-9]+$")
private val STANDALONE_KOREAN_REGEX = Regex("[ㄱ-ㅎㅏ-ㅣ]")

fun isValidFormat(nickname: String): Boolean =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.flint.data.dto.ott

import com.flint.data.dto.base.BaseResponse
import com.flint.data.dto.ott.response.OttListResponseDto
import kotlinx.serialization.json.Json
import org.junit.Assert.assertEquals
import org.junit.Test

/**
* 콘텐츠별 OTT 목록 응답 역직렬화 테스트
*
* QA TC 3-33, 3-34: 홈에서 콘텐츠 카드를 눌러도 바텀시트가 뜨지 않는 문제
*
* GET /api/v1/contents/ott/{contentId} 의 서버 응답 스키마(GetOttResponse)는
* { ottId, name, logoUrl } 세 필드뿐이고 contentUrl 은 존재하지 않는다.
* DTO 가 contentUrl 을 기본값 없는 필수 필드로 선언하면 역직렬화가 실패하고,
* 그 예외가 suspendRunCatching -> onFailure 로 흘러가 조용히 삼켜진다.
*/
class OttListResponseDtoTest {

// NetworkModule 의 Json 설정과 동일하게 맞춘다
private val json = Json {
ignoreUnknownKeys = true
coerceInputValues = true
explicitNulls = false
prettyPrint = true
}

/** 스웨거 GetOttListRes 스키마 그대로 — contentUrl 없음 */
private val serverResponse = """
{
"status": 200,
"message": "OTT리스트 조회 성공",
"data": {
"otts": [
{ "ottId": "1", "name": "넷플릭스", "logoUrl": "https://cdn.flint/netflix.png" },
{ "ottId": "2", "name": "티빙", "logoUrl": "https://cdn.flint/tving.png" }
]
}
}
""".trimIndent()

@Test
fun `contentUrl 이 없는 서버 응답을 역직렬화할 수 있다`() {
val response = json.decodeFromString<BaseResponse<OttListResponseDto>>(serverResponse)

assertEquals(2, response.data.otts.size)
assertEquals("넷플릭스", response.data.otts[0].name)
assertEquals("https://cdn.flint/tving.png", response.data.otts[1].logoUrl)
}

@Test
fun `contentUrl 이 없으면 빈 문자열로 채운다`() {
val response = json.decodeFromString<BaseResponse<OttListResponseDto>>(serverResponse)

assertEquals("", response.data.otts[0].contentUrl)
}

@Test
fun `서버가 contentUrl 을 내려주면 그 값을 사용한다`() {
val withContentUrl = """
{
"otts": [
{
"ottId": "1",
"name": "넷플릭스",
"logoUrl": "https://cdn.flint/netflix.png",
"contentUrl": "https://netflix.com/title/123"
}
]
}
""".trimIndent()

val dto = json.decodeFromString<OttListResponseDto>(withContentUrl)

assertEquals("https://netflix.com/title/123", dto.otts[0].contentUrl)
}

@Test
fun `볼 수 있는 OTT 가 없으면 빈 목록으로 역직렬화된다`() {
val emptyResponse = """{ "otts": [] }"""

val dto = json.decodeFromString<OttListResponseDto>(emptyResponse)

assertEquals(0, dto.otts.size)
}
}
Loading
Loading