Skip to content

Commit

Permalink
Merge pull request #63 from YAPP-Github/feature/tgyuu/PC-555
Browse files Browse the repository at this point in the history
  • Loading branch information
tgyuuAn authored Feb 11, 2025
2 parents 349cb4b + 401f5ec commit 1d928f2
Show file tree
Hide file tree
Showing 31 changed files with 884 additions and 133 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
package com.puzzle.data.repository

import com.puzzle.common.suspendRunCatching
import com.puzzle.datastore.datasource.matching.LocalMatchingDataSource
import com.puzzle.domain.model.match.MatchInfo
import com.puzzle.domain.model.profile.OpponentProfile
import com.puzzle.domain.model.profile.OpponentProfileBasic
import com.puzzle.domain.model.profile.OpponentValuePick
import com.puzzle.domain.model.profile.OpponentValueTalk
import com.puzzle.domain.repository.MatchingRepository
import com.puzzle.network.model.matching.GetMatchInfoResponse
import com.puzzle.network.model.matching.GetOpponentProfileBasicResponse
import com.puzzle.network.model.matching.GetOpponentProfileImageResponse
import com.puzzle.network.model.matching.GetOpponentValuePicksResponse
import com.puzzle.network.model.matching.GetOpponentValueTalksResponse
import com.puzzle.network.source.matching.MatchingDataSource
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.first
import javax.inject.Inject

class MatchingRepositoryImpl @Inject constructor(
private val localMatchingDataSource: LocalMatchingDataSource,
private val matchingDataSource: MatchingDataSource,
) : MatchingRepository {
override suspend fun reportUser(userId: Int, reason: String): Result<Unit> =
Expand All @@ -16,7 +31,57 @@ class MatchingRepositoryImpl @Inject constructor(
matchingDataSource.blockContacts(phoneNumbers)

override suspend fun getMatchInfo(): Result<MatchInfo> = matchingDataSource.getMatchInfo()
.mapCatching { response -> response.toDomain() }
.mapCatching(GetMatchInfoResponse::toDomain)

override suspend fun retrieveOpponentProfile(): Result<OpponentProfile> = suspendRunCatching {
localMatchingDataSource.opponentProfile.first()
}

override suspend fun loadOpponentProfile(): Result<Unit> = suspendRunCatching {
coroutineScope {
val valueTalksDeferred = async { getOpponentValueTalks() }
val valuePicksDeferred = async { getOpponentValuePicks() }
val profileBasicDeferred = async { getOpponentProfileBasic() }
val profileImageDeferred = async { getOpponentProfileImage() }

val valuePicks = valuePicksDeferred.await().getOrThrow()
val valueTalks = valueTalksDeferred.await().getOrThrow()
val profileBasic = profileBasicDeferred.await().getOrThrow()
val imageUrl = profileImageDeferred.await().getOrThrow()

val result = OpponentProfile(
description = profileBasic.description,
nickname = profileBasic.nickname,
age = profileBasic.age,
birthYear = profileBasic.birthYear,
height = profileBasic.height,
weight = profileBasic.weight,
location = profileBasic.location,
job = profileBasic.job,
smokingStatus = profileBasic.smokingStatus,
valuePicks = valuePicks,
valueTalks = valueTalks,
imageUrl = imageUrl,
)
localMatchingDataSource.setOpponentProfile(result)
}
}

private suspend fun getOpponentValueTalks(): Result<List<OpponentValueTalk>> =
matchingDataSource.getOpponentValueTalks()
.mapCatching(GetOpponentValueTalksResponse::toDomain)

private suspend fun getOpponentValuePicks(): Result<List<OpponentValuePick>> =
matchingDataSource.getOpponentValuePicks()
.mapCatching(GetOpponentValuePicksResponse::toDomain)

private suspend fun getOpponentProfileBasic(): Result<OpponentProfileBasic> =
matchingDataSource.getOpponentProfileBasic()
.mapCatching(GetOpponentProfileBasicResponse::toDomain)

private suspend fun getOpponentProfileImage(): Result<String> =
matchingDataSource.getOpponentProfileImage()
.mapCatching(GetOpponentProfileImageResponse::toDomain)

override suspend fun checkMatchingPiece(): Result<Unit> =
matchingDataSource.checkMatchingPiece()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.puzzle.data.fake.source.matching

import com.puzzle.datastore.datasource.matching.LocalMatchingDataSource
import com.puzzle.domain.model.profile.OpponentProfile
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

class FakeLocalMatchingDataSource : LocalMatchingDataSource {
private var storedOpponentProfile: OpponentProfile? = null

override val opponentProfile: Flow<OpponentProfile> = flow {
storedOpponentProfile?.let { emit(it) }
?: throw NoSuchElementException("No value present in DataStore")
}

override suspend fun setOpponentProfile(profile: OpponentProfile) {
storedOpponentProfile = profile
}

override suspend fun clearOpponentProfile() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.puzzle.data.fake.source.matching

import com.puzzle.network.model.matching.GetMatchInfoResponse
import com.puzzle.network.model.matching.GetOpponentProfileBasicResponse
import com.puzzle.network.model.matching.GetOpponentProfileImageResponse
import com.puzzle.network.model.matching.GetOpponentValuePicksResponse
import com.puzzle.network.model.matching.GetOpponentValueTalksResponse
import com.puzzle.network.source.matching.MatchingDataSource

class FakeMatchingDataSource : MatchingDataSource {
private var opponentProfileBasicData: GetOpponentProfileBasicResponse? = null
private var opponentValuePicksData: GetOpponentValuePicksResponse? = null
private var opponentValueTalksData: GetOpponentValueTalksResponse? = null
private var opponentProfileImageData: GetOpponentProfileImageResponse? = null

fun setOpponentProfileData(
basicData: GetOpponentProfileBasicResponse,
valuePicksData: GetOpponentValuePicksResponse,
valueTalksData: GetOpponentValueTalksResponse,
profileImageData: GetOpponentProfileImageResponse
) {
opponentProfileBasicData = basicData
opponentValuePicksData = valuePicksData
opponentValueTalksData = valueTalksData
opponentProfileImageData = profileImageData
}

override suspend fun getOpponentProfileBasic(): Result<GetOpponentProfileBasicResponse> =
Result.success(
opponentProfileBasicData ?: GetOpponentProfileBasicResponse(
null,
null,
null,
null,
null,
null,
null,
null,
null,
null
)
)

override suspend fun getOpponentValuePicks(): Result<GetOpponentValuePicksResponse> =
Result.success(
opponentValuePicksData ?: GetOpponentValuePicksResponse(
null,
null,
null,
null
)
)

override suspend fun getOpponentValueTalks(): Result<GetOpponentValueTalksResponse> =
Result.success(
opponentValueTalksData ?: GetOpponentValueTalksResponse(
null,
null,
null,
null
)
)

override suspend fun getOpponentProfileImage(): Result<GetOpponentProfileImageResponse> =
Result.success(opponentProfileImageData ?: GetOpponentProfileImageResponse(null))

override suspend fun reportUser(userId: Int, reason: String): Result<Unit> =
Result.success(Unit)

override suspend fun blockUser(userId: Int): Result<Unit> = Result.success(Unit)

override suspend fun blockContacts(phoneNumbers: List<String>): Result<Unit> =
Result.success(Unit)

override suspend fun getMatchInfo(): Result<GetMatchInfoResponse> = Result.success(
GetMatchInfoResponse(
matchId = 1234,
matchStatus = "WAITING",
description = "안녕하세요, 저는 음악과 여행을 좋아하는 사람입니다.",
nickname = "여행하는음악가",
birthYear = "1995",
location = "서울특별시",
job = "음악 프로듀서",
matchedValueCount = 3,
matchedValueList = listOf("음악", "여행", "독서")
)
)

override suspend fun checkMatchingPiece(): Result<Unit> = Result.success(Unit)

override suspend fun acceptMatching(): Result<Unit> = Result.success(Unit)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.puzzle.data.repository

import com.puzzle.data.fake.source.matching.FakeLocalMatchingDataSource
import com.puzzle.data.fake.source.matching.FakeMatchingDataSource
import com.puzzle.domain.model.profile.OpponentProfile
import com.puzzle.domain.model.profile.OpponentValuePick
import com.puzzle.domain.model.profile.OpponentValueTalk
import com.puzzle.network.model.matching.GetOpponentProfileBasicResponse
import com.puzzle.network.model.matching.GetOpponentProfileImageResponse
import com.puzzle.network.model.matching.GetOpponentValuePicksResponse
import com.puzzle.network.model.matching.GetOpponentValueTalksResponse
import com.puzzle.network.model.matching.OpponentValuePickResponse
import com.puzzle.network.model.matching.OpponentValueTalkResponse
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class MatchingRepositoryImplTest {

private lateinit var matchingRepository: MatchingRepositoryImpl
private lateinit var fakeMatchingDataSource: FakeMatchingDataSource
private lateinit var fakeLocalMatchingDataSource: FakeLocalMatchingDataSource

@BeforeEach
fun setUp() {
fakeMatchingDataSource = FakeMatchingDataSource()
fakeLocalMatchingDataSource = FakeLocalMatchingDataSource()
matchingRepository = MatchingRepositoryImpl(
localMatchingDataSource = fakeLocalMatchingDataSource,
matchingDataSource = fakeMatchingDataSource
)
}

@Test
fun `매칭된 상대방 정보는 로컬에 저장한다`() = runTest {
// Given
val expectedOpponentProfile = OpponentProfile(
description = "안녕하세요",
nickname = "테스트",
age = 25,
birthYear = "1998",
height = 170,
weight = 60,
location = "서울",
job = "개발자",
smokingStatus = "비흡연",
valuePicks = listOf(
OpponentValuePick(
category = "취미",
question = "당신의 취미는 무엇인가요?",
isSameWithMe = true,
answerOptions = emptyList(),
selectedAnswer = 1
)
),
valueTalks = listOf(
OpponentValueTalk(
category = "음악",
summary = "좋아하는 음악 장르",
answer = "저는 클래식 음악을 좋아합니다."
)
),
imageUrl = "https://example.com/image.jpg"
)

fakeMatchingDataSource.setOpponentProfileData(
GetOpponentProfileBasicResponse(
matchId = 1,
description = expectedOpponentProfile.description,
nickname = expectedOpponentProfile.nickname,
age = expectedOpponentProfile.age,
birthYear = expectedOpponentProfile.birthYear,
height = expectedOpponentProfile.height,
weight = expectedOpponentProfile.weight,
location = expectedOpponentProfile.location,
job = expectedOpponentProfile.job,
smokingStatus = expectedOpponentProfile.smokingStatus
),
GetOpponentValuePicksResponse(
matchId = 1,
description = null,
nickname = null,
valuePicks = expectedOpponentProfile.valuePicks.map {
OpponentValuePickResponse(
category = it.category,
question = it.question,
isSameWithMe = it.isSameWithMe,
answerOptions = null,
selectedAnswer = it.selectedAnswer
)
}
),
GetOpponentValueTalksResponse(
matchId = 1,
description = null,
nickname = null,
valueTalks = expectedOpponentProfile.valueTalks.map {
OpponentValueTalkResponse(
category = it.category,
summary = it.summary,
answer = it.answer
)
}
),
GetOpponentProfileImageResponse(imageUrl = expectedOpponentProfile.imageUrl)
)

// When
matchingRepository.loadOpponentProfile().getOrThrow()

// Then
val storedProfile = fakeLocalMatchingDataSource.opponentProfile.first()
assertEquals(expectedOpponentProfile, storedProfile)
}
}
3 changes: 3 additions & 0 deletions core/datastore/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ android {
}

dependencies {
implementation(projects.core.domain)

implementation(libs.androidx.datastore)
implementation(libs.gson)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.puzzle.datastore.datasource.matching

import com.puzzle.domain.model.profile.OpponentProfile
import kotlinx.coroutines.flow.Flow

interface LocalMatchingDataSource {
val opponentProfile: Flow<OpponentProfile>
suspend fun setOpponentProfile(opponentProfile: OpponentProfile)
suspend fun clearOpponentProfile()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.puzzle.datastore.datasource.matching

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import com.google.gson.Gson
import com.puzzle.datastore.util.getValue
import com.puzzle.datastore.util.setValue
import com.puzzle.domain.model.profile.OpponentProfile
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Named

class LocalMatchingDataSourceImpl @Inject constructor(
@Named("matching") private val dataStore: DataStore<Preferences>,
) : LocalMatchingDataSource {
private val gson = Gson()
override val opponentProfile: Flow<OpponentProfile> = dataStore.getValue(OPPONENT_PROFILE, "")
.map { gson.fromJson(it, OpponentProfile::class.java) }

override suspend fun setOpponentProfile(opponentProfile: OpponentProfile) {
val jsonString = gson.toJson(opponentProfile)
dataStore.setValue(OPPONENT_PROFILE, jsonString)
}

override suspend fun clearOpponentProfile() {
dataStore.edit { preferences -> preferences.remove(OPPONENT_PROFILE) }
}

companion object {
private val OPPONENT_PROFILE = stringPreferencesKey("OPPONENT_PROFILE")
}
}
Loading

0 comments on commit 1d928f2

Please sign in to comment.