Skip to content
Merged
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
@@ -0,0 +1,86 @@
package com.neph.e2e

import androidx.test.platform.app.InstrumentationRegistry
import com.neph.features.requesthelp.data.RequestHelpContactSubmission
import com.neph.features.requesthelp.data.RequestHelpLocationSubmission
import com.neph.features.requesthelp.data.RequestHelpRepository
import com.neph.features.requesthelp.data.RequestHelpSubmission
import com.neph.features.requesthelp.data.jsonArrayToStringList
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Rule
import org.junit.Test

class RequestHelpRepositoryAndroidTest {
private val fakeBackend = FakeNephBackend()
private val environmentRule = NephE2ETestEnvironmentRule(fakeBackend) { context, _ ->
RequestHelpRepository.initialize(context)
}

@get:Rule
val rule = environmentRule

@Test
fun updateHelpRequestPersistsEditedFieldsInLocalDatabase() = runBlocking {
RequestHelpRepository.initialize(InstrumentationRegistry.getInstrumentation().targetContext)
val created = RequestHelpRepository.createHelpRequest(
token = "access-token-1",
submission = sampleSubmission(
helpTypes = listOf("search_rescue"),
description = "Need rescue near the building.",
district = "Kadıköy",
phone = 5551112233L
)
)

RequestHelpRepository.updateHelpRequest(
token = "access-token-1",
localId = created.requestId,
submission = sampleSubmission(
helpTypes = listOf("shelter"),
description = "Need shelter after moving locations.",
district = "Beşiktaş",
phone = 5552223344L
),
preserveExistingCoordinates = false
)

val edited = RequestHelpRepository.getLocalHelpRequest(created.requestId)
assertNotNull(edited)
requireNotNull(edited)
assertEquals(listOf("shelter"), edited.helpTypesJson.jsonArrayToStringList())
assertEquals("Need shelter after moving locations.", edited.description)
assertEquals("Beşiktaş", edited.district)
assertEquals("5552223344", edited.contactPhone)
}

private fun sampleSubmission(
helpTypes: List<String>,
description: String,
district: String,
phone: Long
): RequestHelpSubmission {
return RequestHelpSubmission(
helpTypes = helpTypes,
otherHelpText = "",
affectedPeopleCount = 2,
description = description,
riskFlags = listOf("Fire"),
vulnerableGroups = emptyList(),
shareProfileHealthInfoWithVolunteer = true,
location = RequestHelpLocationSubmission(
country = "Turkey",
city = "Istanbul",
district = district,
neighborhood = "Bostancı",
extraAddress = "Existing Street 5"
),
contact = RequestHelpContactSubmission(
fullName = "Alex Helper",
phone = phone
),
consentGiven = true
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ private val vulnerableGroupOptions = listOf(
private const val RequestHelpGpsCoordinateSource = "gps"
private const val RequestHelpMapCoordinateSource = "map_selection"

private data class RequestHelpFormState(
internal data class RequestHelpFormState(
val helpTypes: List<String> = emptyList(),
val affectedPeopleCount: String = "",
val riskFlags: List<String> = emptyList(),
Expand Down Expand Up @@ -220,19 +220,35 @@ private fun RequestHelpFormState.toMobileOnboardingPracticeHelpRequest(): Mobile
)
}

private fun HelpRequestEntity.toFormState(): RequestHelpFormState {
val phoneParts = normalizePhoneParts(contactPhone)
internal fun HelpRequestEntity.toFormState(
locations: LocationData = locationData
): RequestHelpFormState {
val phoneParts = normalizeStoredRequestPhoneParts(contactPhone)
val alternativePhoneParts = normalizeStoredRequestPhoneParts(contactAlternativePhone)
val countryValue = resolveCountryFormValue(country, locations)
val cityValue = resolveCityFormValue(countryValue, city, locations)
val districtValue = resolveDistrictFormValue(countryValue, cityValue, district, locations)
val neighborhoodValue = resolveNeighborhoodFormValue(
countryValue = countryValue,
cityValue = cityValue,
districtValue = districtValue,
neighborhood = neighborhood,
locations = locations
)
return RequestHelpFormState(
helpTypes = helpTypesJson.jsonArrayToStringList().mapNotNull { helpTypeLabelsByApiValue[it] },
helpTypes = helpTypesJson.jsonArrayToStringList()
.mapNotNull(::persistedHelpTypeToOption),
affectedPeopleCount = affectedPeopleCount.toString(),
riskFlags = riskFlagsJson.jsonArrayToStringList(),
vulnerableGroups = vulnerableGroupsJson.jsonArrayToStringList(),
riskFlags = riskFlagsJson.jsonArrayToStringList()
.map { persistedOptionToLabel(it, riskFlagOptions) },
vulnerableGroups = vulnerableGroupsJson.jsonArrayToStringList()
.map { persistedOptionToLabel(it, vulnerableGroupOptions) },
situationDescription = description,
shareProfileHealthInfoWithVolunteer = shareProfileHealthInfoWithVolunteer,
country = country,
city = city,
district = district,
neighborhood = neighborhood,
country = countryValue,
city = cityValue,
district = districtValue,
neighborhood = neighborhoodValue,
shortAddress = extraAddress,
latitude = latitude,
longitude = longitude,
Expand All @@ -243,11 +259,120 @@ private fun HelpRequestEntity.toFormState(): RequestHelpFormState {
fullName = contactFullName,
countryCode = phoneParts.countryCode,
phoneNumber = phoneParts.phone,
alternativePhone = contactAlternativePhone.orEmpty(),
alternativePhone = alternativePhoneParts.phone,
confirmationAccepted = true
)
}

private fun persistedHelpTypeToOption(value: String): String? {
val raw = value.trim()
if (raw.isBlank()) return null
helpTypeLabelsByApiValue[raw.lowercase(Locale.ROOT)]?.let { return it }
return persistedOptionToLabel(raw, helpTypeOptions)
}

private fun persistedOptionToLabel(value: String, options: List<String>): String {
val raw = value.trim()
if (raw.isBlank()) return raw

options.firstOrNull { it.equals(raw, ignoreCase = true) }?.let { return it }
val normalizedRaw = normalizePersistedOption(raw)
return options.firstOrNull { normalizePersistedOption(it) == normalizedRaw }
?: raw
}

private fun normalizePersistedOption(value: String): String {
return value
.trim()
.lowercase(Locale.ROOT)
.replace("&", "")
.replace(Regex("[^a-z0-9]+"), "")
}

private fun normalizeStoredRequestPhoneParts(phoneNumber: String?): PhoneParts {
val raw = phoneNumber?.trim().orEmpty()
val digits = raw.filter(Char::isDigit)
if (!raw.startsWith("+") && digits.length == 12 && digits.startsWith("90")) {
return PhoneParts(countryCode = "+90", phone = digits.removePrefix("90").trimStart('0'))
}
return normalizePhoneParts(phoneNumber)
}

private fun resolveCountryFormValue(country: String, locations: LocationData): String {
val raw = country.trim()
if (raw.isBlank()) return ""

if (locations.containsKey(raw)) return raw
val normalizedKey = raw.lowercase(Locale.ROOT)
if (locations.containsKey(normalizedKey)) return normalizedKey

return findCountryKeyByLabel(raw, locations).ifBlank { raw }
}

private fun resolveCityFormValue(
countryValue: String,
city: String,
locations: LocationData
): String {
val raw = city.trim()
if (countryValue.isBlank() || raw.isBlank()) return raw

val cities = locations[countryValue]?.cities ?: return raw
if (cities.containsKey(raw)) return raw
val normalizedKey = raw.lowercase(Locale.ROOT)
if (cities.containsKey(normalizedKey)) return normalizedKey

return findCityKeyByLabel(countryValue, raw, locations).ifBlank { raw }
}

private fun resolveDistrictFormValue(
countryValue: String,
cityValue: String,
district: String,
locations: LocationData
): String {
val raw = district.trim()
if (countryValue.isBlank() || cityValue.isBlank() || raw.isBlank()) return raw

val districts = locations[countryValue]?.cities?.get(cityValue)?.districts ?: return raw
if (districts.containsKey(raw)) return raw
val normalizedKey = raw.lowercase(Locale.ROOT)
if (districts.containsKey(normalizedKey)) return normalizedKey

return findDistrictKeyByLabel(countryValue, cityValue, raw, locations).ifBlank { raw }
}

private fun resolveNeighborhoodFormValue(
countryValue: String,
cityValue: String,
districtValue: String,
neighborhood: String,
locations: LocationData
): String {
val raw = neighborhood.trim()
if (countryValue.isBlank() || cityValue.isBlank() || districtValue.isBlank() || raw.isBlank()) {
return raw
}

val neighborhoods = locations[countryValue]
?.cities
?.get(cityValue)
?.districts
?.get(districtValue)
?.neighborhoods
?: return raw

neighborhoods.firstOrNull { it.value.equals(raw, ignoreCase = true) }?.let { return it.value }

return findNeighborhoodValueByLabel(
countryKey = countryValue,
cityKey = cityValue,
districtKey = districtValue,
label = raw,
locations = locations
).ifBlank { raw }
}

private fun toggleSelection(current: List<String>, option: String): List<String> {
return if (option in current) {
current - option
Expand Down Expand Up @@ -843,7 +968,7 @@ fun RequestHelpScreen(
}
}

LaunchedEffect(sessionToken, activeDraftLocalId) {
LaunchedEffect(sessionToken, activeDraftLocalId, locationLoading) {
val existingDraft = activeDraftLocalId.takeIf { it.isNotBlank() }?.let { localId ->
RequestHelpRepository.getLocalHelpRequest(localId)
}
Expand All @@ -852,7 +977,10 @@ fun RequestHelpScreen(
onNavigateToLogin()
return@LaunchedEffect
}
formState = existingDraft.toFormState()
if (locationLoading) {
return@LaunchedEffect
}
formState = existingDraft.toFormState(availableLocationData)
infoMessage = ""
checkingActiveRequest = false
return@LaunchedEffect
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.neph.features.requesthelp.presentation

import com.neph.core.database.HelpRequestEntity
import com.neph.core.sync.LocalOwnerType
import com.neph.core.sync.SyncStatus
import com.neph.features.profile.data.locationData
import org.json.JSONArray
import org.junit.Assert.assertEquals
import org.junit.Test

class RequestHelpFormStateMappingTest {
@Test
fun editFormHydratesPersistedLabelsIntoSelectableFormValues() {
val formState = helpRequestEntity(
helpTypes = listOf("search_rescue", "Food & Water"),
riskFlags = listOf("gas_leak", "Blocked Access / Debris"),
vulnerableGroups = listOf("elderly", "Chronic Condition"),
country = "Turkey",
city = "Istanbul",
district = "Kadıköy",
neighborhood = "Bostancı",
contactPhone = "905551112233",
contactAlternativePhone = "+905551112244"
).toFormState(locationData)

assertEquals(listOf("Search & Rescue", "Food & Water"), formState.helpTypes)
assertEquals(listOf("Gas Leak", "Blocked Access / Debris"), formState.riskFlags)
assertEquals(listOf("Elderly", "Chronic Condition"), formState.vulnerableGroups)
assertEquals("tr", formState.country)
assertEquals("istanbul", formState.city)
assertEquals("kadikoy", formState.district)
assertEquals("bostanci", formState.neighborhood)
assertEquals("+90", formState.countryCode)
assertEquals("5551112233", formState.phoneNumber)
assertEquals("5551112244", formState.alternativePhone)
assertEquals(true, formState.confirmationAccepted)
}

@Test
fun editFormPreservesUnknownRemoteLocationValuesInsteadOfBlankingThem() {
val formState = helpRequestEntity(
country = "Atlantis",
city = "Poseidon",
district = "Coral",
neighborhood = "Reef"
).toFormState(locationData)

assertEquals("Atlantis", formState.country)
assertEquals("Poseidon", formState.city)
assertEquals("Coral", formState.district)
assertEquals("Reef", formState.neighborhood)
}

private fun helpRequestEntity(
helpTypes: List<String> = listOf("first_aid"),
riskFlags: List<String> = emptyList(),
vulnerableGroups: List<String> = emptyList(),
country: String = "Turkey",
city: String = "Istanbul",
district: String = "Kadıköy",
neighborhood: String = "Bostancı",
contactPhone: String = "5551112233",
contactAlternativePhone: String? = null
): HelpRequestEntity {
return HelpRequestEntity(
localId = "local-edit-test",
remoteId = "remote-edit-test",
ownerType = LocalOwnerType.AUTHENTICATED,
guestAccessToken = null,
helpTypesJson = JSONArray(helpTypes).toString(),
otherHelpText = "",
affectedPeopleCount = 2,
riskFlagsJson = JSONArray(riskFlags).toString(),
vulnerableGroupsJson = JSONArray(vulnerableGroups).toString(),
description = "Need help after structural damage.",
bloodType = "",
shareProfileHealthInfoWithVolunteer = true,
country = country,
city = city,
district = district,
neighborhood = neighborhood,
extraAddress = "Existing Street 5",
contactFullName = "Alex Helper",
contactPhone = contactPhone,
contactAlternativePhone = contactAlternativePhone,
status = "PENDING_SYNC",
helperFirstName = null,
helperLastName = null,
helperPhone = null,
helperProfession = null,
helperExpertise = null,
helpersJson = JSONArray().toString(),
syncStatus = SyncStatus.PENDING_UPDATE,
createdAtEpochMillis = 1_700_000_000_000L,
updatedAtEpochMillis = 1_700_000_000_000L
)
}
}
Loading