Skip to content

Commit 14f2568

Browse files
authored
Merge pull request #626 from bounswe/development
release: keep main up to date with development for final release
2 parents 4949676 + 9b92655 commit 14f2568

20 files changed

Lines changed: 1596 additions & 156 deletions

File tree

README.md

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -229,20 +229,23 @@ The signed release APK must be attached to the GitHub Release manually or by the
229229
Final demo data is stored in the normal backend migration flow:
230230

231231
- `backend/migrations/20260512_120000__seed_bogazici_final_demo_data.sql`
232+
- `backend/migrations/20260512_130000__seed_bogazici_final_demo_admin.sql`
232233

233234
It is applied by the normal backend migration runner. In Docker Compose, backend migrations run automatically on startup, so no separate `ENABLE_DEMO_SEED=true npm run seed:demo` command is needed for the Boğaziçi final demo data after migrations run. The old `backend/demo-migrations/` flow is optional legacy demo data only.
234235

235-
Boğaziçi final demo accounts:
236+
Boğaziçi final demo accounts are applied by the normal migration flow. All listed users use password `DemoPass123!`:
236237

237-
| Email | Password |
238-
| --- | --- |
239-
| `bogazici_reserve_1@neph.test` | `DemoPass123!` |
240-
| `bogazici_reserve_2@neph.test` | `DemoPass123!` |
241-
| `bogazici_reserve_3@neph.test` | `DemoPass123!` |
242-
| `bogazici_reserve_4@neph.test` | `DemoPass123!` |
243-
| `bogazici_assigned_1@neph.test` | `DemoPass123!` |
244-
| `bogazici_assigned_2@neph.test` | `DemoPass123!` |
245-
| `bogazici_requester_new_hall@neph.test` | `DemoPass123!` |
238+
| Role | Email | Password | Notes |
239+
| --- | --- | --- | --- |
240+
| Admin | `bogazici_admin@neph.test` | `DemoPass123!` | `SUPER_ADMIN` account for web/admin checks |
241+
| Requester | `bogazici_requester_new_hall@neph.test` | `DemoPass123!` | Active emergency request near New Hall |
242+
| Requester | `bogazici_requester_hisarustu@neph.test` | `DemoPass123!` | Hisarüstü request scenario |
243+
| Volunteer/responder | `bogazici_assigned_1@neph.test` | `DemoPass123!` | Assigned first-aid responder |
244+
| Volunteer/responder | `bogazici_assigned_2@neph.test` | `DemoPass123!` | Assigned logistics responder |
245+
| Reserve volunteer | `bogazici_reserve_1@neph.test` | `DemoPass123!` | Available first-aid volunteer |
246+
| Reserve volunteer | `bogazici_reserve_2@neph.test` | `DemoPass123!` | Available search-and-rescue volunteer |
247+
| Reserve volunteer | `bogazici_reserve_3@neph.test` | `DemoPass123!` | Available food/water volunteer |
248+
| Reserve volunteer | `bogazici_reserve_4@neph.test` | `DemoPass123!` | Available shelter/communications volunteer |
246249

247250
### Optional legacy demo data seed
248251

@@ -330,7 +333,7 @@ The deployed backend API base is `https://api.neph.app/api`. The deployed web UR
330333
- Tag `final-milestone` points to the final `main` commit.
331334
- GitHub Release name is `1.0.0`.
332335
- GitHub Release is official, not a pre-release.
333-
- Release notes include the deployed web URL.
336+
- Release notes include the deployed web URL. A ready-to-copy draft is available at `docs/releases/final-milestone-release-notes.md`.
334337
- Signed APK is attached to the GitHub Release.
335338

336339
## Development notes

android/app/src/androidTest/java/com/neph/e2e/AuthenticatedSessionAndroidE2ETest.kt

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import com.neph.MainActivity
1414
import com.neph.features.auth.data.AuthSessionStore
1515
import com.neph.features.profile.data.ProfileData
1616
import com.neph.features.profile.data.ProfileRepository
17+
import com.neph.features.requesthelp.data.RequestHelpContactSubmission
18+
import com.neph.features.requesthelp.data.RequestHelpLocationSubmission
19+
import com.neph.features.requesthelp.data.RequestHelpRepository
20+
import com.neph.features.requesthelp.data.RequestHelpSubmission
21+
import kotlinx.coroutines.runBlocking
1722
import org.junit.Rule
1823
import org.junit.Test
1924
import org.junit.rules.RuleChain
@@ -117,6 +122,62 @@ class AuthenticatedSessionAndroidE2ETest {
117122
composeRule.onNodeWithText("Welcome back").assertIsDisplayed()
118123
}
119124

125+
@Test
126+
fun systemBackFromHelpRequestEdit_homeBottomNavReturnsHome() {
127+
waitForText("I need help now")
128+
seedActiveHelpRequest()
129+
130+
waitForClickable("Requests")
131+
clickableNode("Requests").performClick()
132+
waitForClickable("Edit Request")
133+
134+
clickableNode("Edit Request").performClick()
135+
waitForText("Edit help request?")
136+
clickableNode("Edit").performClick()
137+
138+
waitForText("Request Help")
139+
composeRule.activityRule.scenario.onActivity { activity ->
140+
activity.onBackPressedDispatcher.onBackPressed()
141+
}
142+
143+
waitForText("My Help Requests")
144+
waitForClickable("Home")
145+
clickableNode("Home").performClick()
146+
147+
waitForText("I need help now")
148+
composeRule.onNodeWithText("I need help now").assertIsDisplayed()
149+
}
150+
151+
private fun seedActiveHelpRequest() {
152+
RequestHelpRepository.initialize(composeRule.activity.applicationContext)
153+
runBlocking {
154+
RequestHelpRepository.createHelpRequest(
155+
token = "access-token-1",
156+
submission = RequestHelpSubmission(
157+
helpTypes = listOf("Search & Rescue"),
158+
otherHelpText = "",
159+
affectedPeopleCount = 2,
160+
description = "Need help after structural damage.",
161+
riskFlags = listOf("Fire"),
162+
vulnerableGroups = listOf("Children"),
163+
shareProfileHealthInfoWithVolunteer = true,
164+
location = RequestHelpLocationSubmission(
165+
country = "Turkey",
166+
city = "Istanbul",
167+
district = "Kadıköy",
168+
neighborhood = "Bostancı",
169+
extraAddress = "Existing Street 5"
170+
),
171+
contact = RequestHelpContactSubmission(
172+
fullName = "Alex Helper",
173+
phone = 905551112233
174+
),
175+
consentGiven = true
176+
)
177+
)
178+
}
179+
}
180+
120181
private fun clickableNode(text: String) = composeRule.onNode(hasText(text) and hasClickAction())
121182

122183
private fun waitForClickable(text: String, timeoutMillis: Long = 15_000) {
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.neph.e2e
2+
3+
import androidx.test.platform.app.InstrumentationRegistry
4+
import com.neph.features.requesthelp.data.RequestHelpContactSubmission
5+
import com.neph.features.requesthelp.data.RequestHelpLocationSubmission
6+
import com.neph.features.requesthelp.data.RequestHelpRepository
7+
import com.neph.features.requesthelp.data.RequestHelpSubmission
8+
import com.neph.features.requesthelp.data.jsonArrayToStringList
9+
import kotlinx.coroutines.runBlocking
10+
import org.junit.Assert.assertEquals
11+
import org.junit.Assert.assertNotNull
12+
import org.junit.Rule
13+
import org.junit.Test
14+
15+
class RequestHelpRepositoryAndroidTest {
16+
private val fakeBackend = FakeNephBackend()
17+
private val environmentRule = NephE2ETestEnvironmentRule(fakeBackend) { context, _ ->
18+
RequestHelpRepository.initialize(context)
19+
}
20+
21+
@get:Rule
22+
val rule = environmentRule
23+
24+
@Test
25+
fun updateHelpRequestPersistsEditedFieldsInLocalDatabase() = runBlocking {
26+
RequestHelpRepository.initialize(InstrumentationRegistry.getInstrumentation().targetContext)
27+
val created = RequestHelpRepository.createHelpRequest(
28+
token = "access-token-1",
29+
submission = sampleSubmission(
30+
helpTypes = listOf("search_rescue"),
31+
description = "Need rescue near the building.",
32+
district = "Kadıköy",
33+
phone = 5551112233L
34+
)
35+
)
36+
37+
RequestHelpRepository.updateHelpRequest(
38+
token = "access-token-1",
39+
localId = created.requestId,
40+
submission = sampleSubmission(
41+
helpTypes = listOf("shelter"),
42+
description = "Need shelter after moving locations.",
43+
district = "Beşiktaş",
44+
phone = 5552223344L
45+
),
46+
preserveExistingCoordinates = false
47+
)
48+
49+
val edited = RequestHelpRepository.getLocalHelpRequest(created.requestId)
50+
assertNotNull(edited)
51+
requireNotNull(edited)
52+
assertEquals(listOf("shelter"), edited.helpTypesJson.jsonArrayToStringList())
53+
assertEquals("Need shelter after moving locations.", edited.description)
54+
assertEquals("Beşiktaş", edited.district)
55+
assertEquals("5552223344", edited.contactPhone)
56+
}
57+
58+
private fun sampleSubmission(
59+
helpTypes: List<String>,
60+
description: String,
61+
district: String,
62+
phone: Long
63+
): RequestHelpSubmission {
64+
return RequestHelpSubmission(
65+
helpTypes = helpTypes,
66+
otherHelpText = "",
67+
affectedPeopleCount = 2,
68+
description = description,
69+
riskFlags = listOf("Fire"),
70+
vulnerableGroups = emptyList(),
71+
shareProfileHealthInfoWithVolunteer = true,
72+
location = RequestHelpLocationSubmission(
73+
country = "Turkey",
74+
city = "Istanbul",
75+
district = district,
76+
neighborhood = "Bostancı",
77+
extraAddress = "Existing Street 5"
78+
),
79+
contact = RequestHelpContactSubmission(
80+
fullName = "Alex Helper",
81+
phone = phone
82+
),
83+
consentGiven = true
84+
)
85+
}
86+
}

android/app/src/main/java/com/neph/core/network/JsonHttpClient.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.json.JSONException
77
import org.json.JSONObject
88
import java.io.IOException
99
import java.net.HttpURLConnection
10+
import java.net.SocketTimeoutException
1011
import java.net.URL
1112
import java.net.UnknownHostException
1213

@@ -19,12 +20,17 @@ object JsonHttpClient {
1920
method: String = "GET",
2021
body: JSONObject? = null,
2122
token: String? = null,
22-
headers: Map<String, String> = emptyMap()
23+
headers: Map<String, String> = emptyMap(),
24+
connectTimeoutMillis: Int = ConnectTimeoutMillis,
25+
readTimeoutMillis: Int = ReadTimeoutMillis,
26+
timeoutMessage: String = "Something went wrong while connecting to the server. Please try again.",
27+
timeoutStatus: Int = 0,
28+
timeoutCode: String = "NETWORK_ERROR"
2329
): JSONObject = withContext(Dispatchers.IO) {
2430
val connection = (URL(buildUrl(path)).openConnection() as HttpURLConnection).apply {
2531
requestMethod = method
26-
connectTimeout = ConnectTimeoutMillis
27-
readTimeout = ReadTimeoutMillis
32+
connectTimeout = connectTimeoutMillis
33+
readTimeout = readTimeoutMillis
2834
doInput = true
2935
setRequestProperty("Accept", "application/json")
3036

@@ -75,6 +81,12 @@ object JsonHttpClient {
7581
}
7682
} catch (error: ApiException) {
7783
throw error
84+
} catch (_: SocketTimeoutException) {
85+
throw ApiException(
86+
message = timeoutMessage,
87+
status = timeoutStatus,
88+
code = timeoutCode
89+
)
7890
} catch (_: UnknownHostException) {
7991
throw ApiException(
8092
message = "Something went wrong while connecting to the server. Please try again.",

android/app/src/main/java/com/neph/features/gatheringareas/data/GatheringAreasRepository.kt

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ object GatheringAreasRepository {
5353
internal const val DefaultLimit = 50
5454
private const val MaxRadiusMeters = 10000
5555
private const val MaxLimit = 50
56-
private const val NearbyRequestTimeoutMillis = 8000L
56+
private const val NearbyRequestTimeoutMillis = 12_000L
57+
private const val NearbyRequestHttpTimeoutMillis = 12_000
58+
private const val NearbyRequestTimeoutMessage = "Gathering areas request timed out."
59+
private const val NearbyRequestTimeoutCode = "OVERPASS_TIMEOUT"
5760
private const val DebugLogTag = "GatheringAreasRepo"
5861

5962
suspend fun fetchNearbyGatheringAreas(
@@ -74,12 +77,17 @@ object GatheringAreasRepository {
7477
longitude,
7578
normalizedRadius,
7679
normalizedLimit
77-
)
80+
),
81+
connectTimeoutMillis = NearbyRequestHttpTimeoutMillis,
82+
readTimeoutMillis = NearbyRequestHttpTimeoutMillis,
83+
timeoutMessage = NearbyRequestTimeoutMessage,
84+
timeoutStatus = 504,
85+
timeoutCode = NearbyRequestTimeoutCode
7886
)
7987
} ?: throw ApiException(
80-
message = "Gathering areas request timed out.",
88+
message = NearbyRequestTimeoutMessage,
8189
status = 504,
82-
code = "OVERPASS_TIMEOUT"
90+
code = NearbyRequestTimeoutCode
8391
)
8492

8593
return parseNearbyGatheringAreasResponse(
@@ -105,12 +113,17 @@ object GatheringAreasRepository {
105113

106114
val response = withTimeoutOrNull(NearbyRequestTimeoutMillis) {
107115
JsonHttpClient.request(
108-
path = "/gathering-areas/viewport?bbox=${urlEncode(bbox)}&limit=$normalizedLimit"
116+
path = "/gathering-areas/viewport?bbox=${urlEncode(bbox)}&limit=$normalizedLimit",
117+
connectTimeoutMillis = NearbyRequestHttpTimeoutMillis,
118+
readTimeoutMillis = NearbyRequestHttpTimeoutMillis,
119+
timeoutMessage = NearbyRequestTimeoutMessage,
120+
timeoutStatus = 504,
121+
timeoutCode = NearbyRequestTimeoutCode
109122
)
110123
} ?: throw ApiException(
111-
message = "Gathering areas request timed out.",
124+
message = NearbyRequestTimeoutMessage,
112125
status = 504,
113-
code = "OVERPASS_TIMEOUT"
126+
code = NearbyRequestTimeoutCode
114127
)
115128

116129
val parsed = parseNearbyGatheringAreasResponse(

0 commit comments

Comments
 (0)