hltb example: move game finder logic out of fragment, introduce mockk unit tests
This commit is contained in:
parent
6242c4a0a1
commit
f1c576ffde
|
@ -64,6 +64,8 @@ dependencies {
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.3")
|
androidTestImplementation("androidx.test.ext:junit:1.1.3")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
|
||||||
|
|
||||||
|
testImplementation("io.mockk:mockk:1.12.0")
|
||||||
|
|
||||||
// --- kotlinx extras
|
// --- kotlinx extras
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1")
|
||||||
|
|
|
@ -10,14 +10,14 @@ import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class HLTBClientTest {
|
class HLTBClientImplTest {
|
||||||
|
|
||||||
private lateinit var client: HLTBClient
|
private lateinit var client: HLTBClientImpl
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
client = HLTBClient(appContext)
|
client = HLTBClientImpl(appContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
|
@ -14,14 +14,9 @@ import androidx.core.content.PermissionChecker
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import be.kuleuven.howlongtobeat.cartridges.CartridgeFinderViaDuckDuckGo
|
|
||||||
import be.kuleuven.howlongtobeat.cartridges.CartridgesRepository
|
|
||||||
import be.kuleuven.howlongtobeat.cartridges.CartridgesRepositoryGekkioFi
|
|
||||||
import be.kuleuven.howlongtobeat.cartridges.findFirstCartridgeForRepos
|
|
||||||
import be.kuleuven.howlongtobeat.databinding.FragmentLoadingBinding
|
import be.kuleuven.howlongtobeat.databinding.FragmentLoadingBinding
|
||||||
import be.kuleuven.howlongtobeat.google.GoogleVisionClient
|
|
||||||
import be.kuleuven.howlongtobeat.hltb.HLTBClient
|
|
||||||
import be.kuleuven.howlongtobeat.hltb.HowLongToBeatResult
|
import be.kuleuven.howlongtobeat.hltb.HowLongToBeatResult
|
||||||
|
import be.kuleuven.howlongtobeat.model.GameFinder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -29,10 +24,6 @@ import java.io.File
|
||||||
|
|
||||||
class LoadingFragment : Fragment(R.layout.fragment_loading) {
|
class LoadingFragment : Fragment(R.layout.fragment_loading) {
|
||||||
|
|
||||||
private lateinit var hltbClient: HLTBClient
|
|
||||||
private lateinit var cartRepos: List<CartridgesRepository>
|
|
||||||
private lateinit var imageRecognizer: ImageRecognizer
|
|
||||||
|
|
||||||
private lateinit var cameraPermissionActivityResult: ActivityResultLauncher<String>
|
private lateinit var cameraPermissionActivityResult: ActivityResultLauncher<String>
|
||||||
private lateinit var cameraActivityResult: ActivityResultLauncher<Uri>
|
private lateinit var cameraActivityResult: ActivityResultLauncher<Uri>
|
||||||
private lateinit var main: MainActivity
|
private lateinit var main: MainActivity
|
||||||
|
@ -48,14 +39,6 @@ class LoadingFragment : Fragment(R.layout.fragment_loading) {
|
||||||
binding = FragmentLoadingBinding.inflate(layoutInflater)
|
binding = FragmentLoadingBinding.inflate(layoutInflater)
|
||||||
main = activity as MainActivity
|
main = activity as MainActivity
|
||||||
|
|
||||||
// If we fail to find info in the first repo, it falls back to the second one: a (scraped) DuckDuckGo search.
|
|
||||||
cartRepos = listOf(
|
|
||||||
CartridgesRepositoryGekkioFi.fromAsset(main.applicationContext),
|
|
||||||
CartridgeFinderViaDuckDuckGo(main.applicationContext)
|
|
||||||
)
|
|
||||||
imageRecognizer = GoogleVisionClient()
|
|
||||||
hltbClient = HLTBClient(main.applicationContext)
|
|
||||||
|
|
||||||
cameraActivityResult = registerForActivityResult(ActivityResultContracts.TakePicture(), this::cameraSnapTaken)
|
cameraActivityResult = registerForActivityResult(ActivityResultContracts.TakePicture(), this::cameraSnapTaken)
|
||||||
cameraPermissionActivityResult = registerForActivityResult(ActivityResultContracts.RequestPermission(), this::cameraPermissionAcceptedOrDenied)
|
cameraPermissionActivityResult = registerForActivityResult(ActivityResultContracts.RequestPermission(), this::cameraPermissionAcceptedOrDenied)
|
||||||
binding.btnRetryAfterLoading.setOnClickListener {
|
binding.btnRetryAfterLoading.setOnClickListener {
|
||||||
|
@ -105,19 +88,11 @@ class LoadingFragment : Fragment(R.layout.fragment_loading) {
|
||||||
// Uncomment this line if you want to stub out camera pictures
|
// Uncomment this line if you want to stub out camera pictures
|
||||||
// picToAnalyze = BitmapFactory.decodeResource(resources, R.drawable.sml2)
|
// picToAnalyze = BitmapFactory.decodeResource(resources, R.drawable.sml2)
|
||||||
|
|
||||||
progress("Recognizing game cart from picture...")
|
val hltbResults = GameFinder.default(main.applicationContext).findGameBasedOnCameraSnap(picToAnalyze) {
|
||||||
val cartCode = imageRecognizer.recognizeCartCode(picToAnalyze)
|
progress(it)
|
||||||
?: throw UnableToFindGameException("No cart code in your pic found")
|
}
|
||||||
|
|
||||||
progress("Found cart code $cartCode\nLooking in DBs for matching game...")
|
Snackbar.make(requireView(), "Found ${hltbResults.size} game(s)", Snackbar.LENGTH_LONG).show()
|
||||||
val foundCart = findFirstCartridgeForRepos(cartCode, cartRepos)
|
|
||||||
?: throw UnableToFindGameException("$cartCode is an unknown game cart.")
|
|
||||||
|
|
||||||
progress("Valid cart code: $cartCode\n Looking in HLTB for ${foundCart.title}...")
|
|
||||||
val hltbResults = hltbClient.find(foundCart)
|
|
||||||
?: throw UnableToFindGameException("HLTB does not know ${foundCart.title}")
|
|
||||||
|
|
||||||
Snackbar.make(requireView(), "Found ${hltbResults.size} game(s) for cart ${foundCart.code}", Snackbar.LENGTH_LONG).show()
|
|
||||||
val bundle = bundleOf(
|
val bundle = bundleOf(
|
||||||
HowLongToBeatResult.RESULT to hltbResults,
|
HowLongToBeatResult.RESULT to hltbResults,
|
||||||
HowLongToBeatResult.SNAPSHOT_URI to snapshot.toString()
|
HowLongToBeatResult.SNAPSHOT_URI to snapshot.toString()
|
||||||
|
|
|
@ -8,11 +8,14 @@ object DuckDuckGoResultParser {
|
||||||
".",
|
".",
|
||||||
"-",
|
"-",
|
||||||
"/",
|
"/",
|
||||||
|
"\n",
|
||||||
",",
|
",",
|
||||||
"!",
|
"!",
|
||||||
"Get information and compare prices of",
|
"Get information and compare prices of",
|
||||||
"for Game Boy",
|
"for Game Boy",
|
||||||
"Release Information",
|
"Release Information",
|
||||||
|
"Release Dates",
|
||||||
|
"Mobygames",
|
||||||
"Nintendo",
|
"Nintendo",
|
||||||
"Game Boy Advance",
|
"Game Boy Advance",
|
||||||
"Game Boy color",
|
"Game Boy color",
|
||||||
|
|
|
@ -1,54 +1,7 @@
|
||||||
package be.kuleuven.howlongtobeat.hltb
|
package be.kuleuven.howlongtobeat.hltb
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import be.kuleuven.howlongtobeat.cartridges.Cartridge
|
import be.kuleuven.howlongtobeat.cartridges.Cartridge
|
||||||
import com.android.volley.Response
|
|
||||||
import com.android.volley.toolbox.StringRequest
|
|
||||||
import com.android.volley.toolbox.Volley
|
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
|
|
||||||
class HLTBClient(val context: Context) {
|
interface HLTBClient {
|
||||||
|
suspend fun find(cart: Cartridge): List<HowLongToBeatResult>?
|
||||||
companion object {
|
}
|
||||||
const val DOMAIN = "https://howlongtobeat.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inspired by https://www.npmjs.com/package/howlongtobeat
|
|
||||||
// The API is abysmal, but hey, it works...
|
|
||||||
class HLTBRequest(val query: String, responseListener: Response.Listener<String>) :
|
|
||||||
StringRequest(Method.POST, "$DOMAIN/search_results.php?page=1", responseListener,
|
|
||||||
Response.ErrorListener {
|
|
||||||
println("Something went wrong: ${it.message}")
|
|
||||||
}) {
|
|
||||||
override fun getBodyContentType(): String {
|
|
||||||
return "application/x-www-form-urlencoded"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getParams(): MutableMap<String, String> {
|
|
||||||
return hashMapOf(
|
|
||||||
"queryString" to query,
|
|
||||||
"t" to "games",
|
|
||||||
"sorthead" to "popular",
|
|
||||||
"sortd" to "0",
|
|
||||||
"plat" to "",
|
|
||||||
"length_type" to "main",
|
|
||||||
"length_min" to "",
|
|
||||||
"length_max" to "",
|
|
||||||
"detail" to "0"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getHeaders(): MutableMap<String, String> {
|
|
||||||
return hashMapOf("User-Agent" to "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:90.0) Gecko/20100101 Firefox/90.0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun find(cart: Cartridge): List<HowLongToBeatResult>? = suspendCoroutine { cont ->
|
|
||||||
val queue = Volley.newRequestQueue(context)
|
|
||||||
val req = HLTBRequest(cart.title) {
|
|
||||||
val hltbResults = HowLongToBeatResultParser.parse(it, cart)
|
|
||||||
cont.resumeWith(Result.success(hltbResults))
|
|
||||||
}
|
|
||||||
queue.add(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
package be.kuleuven.howlongtobeat.hltb
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import be.kuleuven.howlongtobeat.cartridges.Cartridge
|
||||||
|
import com.android.volley.Response
|
||||||
|
import com.android.volley.toolbox.StringRequest
|
||||||
|
import com.android.volley.toolbox.Volley
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
class HLTBClientImpl(val context: Context) : HLTBClient {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DOMAIN = "https://howlongtobeat.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspired by https://www.npmjs.com/package/howlongtobeat
|
||||||
|
// The API is abysmal, but hey, it works...
|
||||||
|
class HLTBRequest(val query: String, responseListener: Response.Listener<String>) :
|
||||||
|
StringRequest(Method.POST, "$DOMAIN/search_results.php?page=1", responseListener,
|
||||||
|
Response.ErrorListener {
|
||||||
|
println("Something went wrong: ${it.message}")
|
||||||
|
}) {
|
||||||
|
override fun getBodyContentType(): String {
|
||||||
|
return "application/x-www-form-urlencoded"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getParams(): MutableMap<String, String> {
|
||||||
|
return hashMapOf(
|
||||||
|
"queryString" to query,
|
||||||
|
"t" to "games",
|
||||||
|
"sorthead" to "popular",
|
||||||
|
"sortd" to "0",
|
||||||
|
"plat" to "",
|
||||||
|
"length_type" to "main",
|
||||||
|
"length_min" to "",
|
||||||
|
"length_max" to "",
|
||||||
|
"detail" to "0"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getHeaders(): MutableMap<String, String> {
|
||||||
|
return hashMapOf("User-Agent" to "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:90.0) Gecko/20100101 Firefox/90.0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun find(cart: Cartridge): List<HowLongToBeatResult>? = suspendCoroutine { cont ->
|
||||||
|
val queue = Volley.newRequestQueue(context)
|
||||||
|
val req = HLTBRequest(cart.title) {
|
||||||
|
val hltbResults = HowLongToBeatResultParser.parse(it, cart)
|
||||||
|
cont.resumeWith(Result.success(hltbResults))
|
||||||
|
}
|
||||||
|
queue.add(req)
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ data class HowLongToBeatResult(val title: String, val cartCode: String, val howl
|
||||||
const val SNAPSHOT_URI = "SnapshotUri"
|
const val SNAPSHOT_URI = "SnapshotUri"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasBoxart(): Boolean = boxartUrl.startsWith(HLTBClient.DOMAIN)
|
fun hasBoxart(): Boolean = boxartUrl.startsWith(HLTBClientImpl.DOMAIN)
|
||||||
fun boxartUrl(): URL = URL(boxartUrl)
|
fun boxartUrl(): URL = URL(boxartUrl)
|
||||||
override fun toString(): String = "$title ($howlong hrs)"
|
override fun toString(): String = "$title ($howlong hrs)"
|
||||||
}
|
}
|
|
@ -30,7 +30,7 @@ object HowLongToBeatResultParser {
|
||||||
if(row - 3 >= 0) {
|
if(row - 3 >= 0) {
|
||||||
val matchedBoxArt = boxArtMatcher.find(rows[row - 3])
|
val matchedBoxArt = boxArtMatcher.find(rows[row - 3])
|
||||||
if(matchedBoxArt != null) {
|
if(matchedBoxArt != null) {
|
||||||
return HLTBClient.DOMAIN + matchedBoxArt.groupValues[1]
|
return HLTBClientImpl.DOMAIN + matchedBoxArt.groupValues[1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package be.kuleuven.howlongtobeat.model
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import be.kuleuven.howlongtobeat.ImageRecognizer
|
||||||
|
import be.kuleuven.howlongtobeat.UnableToFindGameException
|
||||||
|
import be.kuleuven.howlongtobeat.cartridges.CartridgeFinderViaDuckDuckGo
|
||||||
|
import be.kuleuven.howlongtobeat.cartridges.CartridgesRepository
|
||||||
|
import be.kuleuven.howlongtobeat.cartridges.CartridgesRepositoryGekkioFi
|
||||||
|
import be.kuleuven.howlongtobeat.cartridges.findFirstCartridgeForRepos
|
||||||
|
import be.kuleuven.howlongtobeat.google.GoogleVisionClient
|
||||||
|
import be.kuleuven.howlongtobeat.hltb.HLTBClient
|
||||||
|
import be.kuleuven.howlongtobeat.hltb.HLTBClientImpl
|
||||||
|
import be.kuleuven.howlongtobeat.hltb.HowLongToBeatResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class separates Android-specific logic with our own domain logic.
|
||||||
|
* WHY? Because of GameFinderTest and mockability!
|
||||||
|
*/
|
||||||
|
class GameFinder(
|
||||||
|
private val hltbClient: HLTBClient,
|
||||||
|
private val cartRepos: List<CartridgesRepository>,
|
||||||
|
private val imageRecognizer: ImageRecognizer) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun default(context: Context): GameFinder {
|
||||||
|
// If we fail to find info in the first repo, it falls back to the second one: a (scraped) DuckDuckGo search.
|
||||||
|
val cartRepos = listOf(
|
||||||
|
CartridgesRepositoryGekkioFi.fromAsset(context),
|
||||||
|
CartridgeFinderViaDuckDuckGo(context)
|
||||||
|
)
|
||||||
|
return GameFinder(HLTBClientImpl(context), cartRepos, GoogleVisionClient())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun findGameBasedOnCameraSnap(picToAnalyze: Bitmap, progress: (msg: String) -> Unit): List<HowLongToBeatResult> {
|
||||||
|
progress("Recognizing game cart from picture...")
|
||||||
|
val cartCode = imageRecognizer.recognizeCartCode(picToAnalyze)
|
||||||
|
?: throw UnableToFindGameException("No cart code in your pic found")
|
||||||
|
|
||||||
|
progress("Found cart code $cartCode\nLooking in DBs for matching game...")
|
||||||
|
val foundCart = findFirstCartridgeForRepos(cartCode, cartRepos)
|
||||||
|
?: throw UnableToFindGameException("$cartCode is an unknown game cart.")
|
||||||
|
|
||||||
|
progress("Valid cart code: $cartCode\n Looking in HLTB for ${foundCart.title}...")
|
||||||
|
val hltbResults = hltbClient.find(foundCart)
|
||||||
|
?: throw UnableToFindGameException("HLTB does not know ${foundCart.title}")
|
||||||
|
|
||||||
|
return hltbResults
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
package be.kuleuven.howlongtobeat.model
|
||||||
|
|
||||||
|
import be.kuleuven.howlongtobeat.ImageRecognizer
|
||||||
|
import be.kuleuven.howlongtobeat.UnableToFindGameException
|
||||||
|
import be.kuleuven.howlongtobeat.cartridges.Cartridge
|
||||||
|
import be.kuleuven.howlongtobeat.cartridges.CartridgesRepository
|
||||||
|
import be.kuleuven.howlongtobeat.hltb.HLTBClient
|
||||||
|
import be.kuleuven.howlongtobeat.hltb.HowLongToBeatResult
|
||||||
|
import io.mockk.MockKAnnotations
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.impl.annotations.MockK
|
||||||
|
import io.mockk.mockk
|
||||||
|
import junit.framework.TestCase.assertEquals
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.test.resetMain
|
||||||
|
import kotlinx.coroutines.test.setMain
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
class GameFinderTest {
|
||||||
|
|
||||||
|
private lateinit var finder: GameFinder
|
||||||
|
@MockK
|
||||||
|
private lateinit var hltbClient: HLTBClient
|
||||||
|
@MockK
|
||||||
|
private lateinit var imageRecognizer: ImageRecognizer
|
||||||
|
@MockK
|
||||||
|
private lateinit var cartridgesRepository: CartridgesRepository
|
||||||
|
|
||||||
|
private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
||||||
|
|
||||||
|
@Before fun setUp() {
|
||||||
|
MockKAnnotations.init(this)
|
||||||
|
finder = GameFinder(hltbClient, listOf(cartridgesRepository), imageRecognizer)
|
||||||
|
Dispatchers.setMain(dispatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After fun tearDown() {
|
||||||
|
Dispatchers.resetMain()
|
||||||
|
dispatcher.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun findGameBasedOnCameraSnap_UnrecognizableAccordingToGoogleVision_fails() = runBlocking {
|
||||||
|
coEvery { imageRecognizer.recognizeCartCode(any()) } returns null
|
||||||
|
|
||||||
|
try {
|
||||||
|
finder.findGameBasedOnCameraSnap(mockk()) {
|
||||||
|
}
|
||||||
|
Assert.fail("Expected exception to occur")
|
||||||
|
} catch(expected: UnableToFindGameException) {
|
||||||
|
Assert.assertEquals("No cart code in your pic found", expected.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun findGameBasedOnCameraSnap_UnknownCartridgeAccordingToDBs_fails() = runBlocking {
|
||||||
|
coEvery { imageRecognizer.recognizeCartCode(any()) } returns "DMG-MQ-EUR"
|
||||||
|
coEvery { cartridgesRepository.find("DMG-MQ-EUR") } returns null
|
||||||
|
|
||||||
|
try {
|
||||||
|
finder.findGameBasedOnCameraSnap(mockk()) {
|
||||||
|
}
|
||||||
|
Assert.fail("Expected exception to occur")
|
||||||
|
} catch(expected: UnableToFindGameException) {
|
||||||
|
Assert.assertEquals("DMG-MQ-EUR is an unknown game cart.", expected.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun findGameBasedOnCameraSnap_UnknownCartridgeAccordingToFirstDBButSecondFindsIt_returnsHltbResults() = runBlocking {
|
||||||
|
val secondCartridgeDb = mockk<CartridgesRepository>()
|
||||||
|
val cart = Cartridge("type", "Mario Land 356", "DMG-MQ-EUR")
|
||||||
|
|
||||||
|
coEvery { imageRecognizer.recognizeCartCode(any()) } returns cart.code
|
||||||
|
coEvery { cartridgesRepository.find(cart.code) } returns null
|
||||||
|
coEvery { secondCartridgeDb.find(cart.code) } returns cart
|
||||||
|
coEvery { hltbClient.find(cart) } returns listOf(HowLongToBeatResult(cart.title, cart.code, 34.5))
|
||||||
|
finder = GameFinder(hltbClient, listOf(cartridgesRepository, secondCartridgeDb), imageRecognizer)
|
||||||
|
|
||||||
|
val foundGames = finder.findGameBasedOnCameraSnap(mockk()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(1, foundGames.size)
|
||||||
|
assertEquals(34.5, foundGames.single().howlong)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun findGameBasedOnCameraSnap_UnknownGameAccordingToHLTB_fails() = runBlocking {
|
||||||
|
val cart = Cartridge("type", "Mario Land 356", "DMG-MQ-EUR")
|
||||||
|
|
||||||
|
coEvery { imageRecognizer.recognizeCartCode(any()) } returns cart.code
|
||||||
|
coEvery { cartridgesRepository.find(cart.code) } returns cart
|
||||||
|
coEvery { hltbClient.find(cart) } returns null
|
||||||
|
|
||||||
|
try {
|
||||||
|
finder.findGameBasedOnCameraSnap(mockk()) {
|
||||||
|
}
|
||||||
|
Assert.fail("Expected exception to occur")
|
||||||
|
} catch(expected: UnableToFindGameException) {
|
||||||
|
Assert.assertEquals("HLTB does not know Mario Land 356", expected.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun findGameBasedOnCameraSnap_validGame_returnsHltbResults() = runBlocking {
|
||||||
|
val cart = Cartridge("type", "Mario Land 356", "DMG-MQ-EUR")
|
||||||
|
|
||||||
|
coEvery { imageRecognizer.recognizeCartCode(any()) } returns cart.code
|
||||||
|
coEvery { cartridgesRepository.find(cart.code) } returns cart
|
||||||
|
coEvery { hltbClient.find(cart) } returns listOf(HowLongToBeatResult(cart.title, cart.code, 34.5))
|
||||||
|
|
||||||
|
val foundGames = finder.findGameBasedOnCameraSnap(mockk()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(1, foundGames.size)
|
||||||
|
assertEquals(34.5, foundGames.single().howlong)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue