2d80ade6bc
Ports iOS HoneyDueUITests AccessibilityIdentifiers + PageObjects pattern to Android Compose UI Test. Kotlin AccessibilityIds object mirrors Swift verbatim so scripts/verify_test_tag_parity.sh can gate on divergence. AAA_SeedTests bracketed first alphanumerically; SuiteZZ cleanup to follow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
178 lines
6.6 KiB
Kotlin
178 lines
6.6 KiB
Kotlin
package com.tt.honeyDue
|
|
|
|
import android.content.Context
|
|
import androidx.test.core.app.ApplicationProvider
|
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
import com.tt.honeyDue.data.DataManager
|
|
import com.tt.honeyDue.data.PersistenceManager
|
|
import com.tt.honeyDue.fixtures.TestResidence
|
|
import com.tt.honeyDue.fixtures.TestTask
|
|
import com.tt.honeyDue.fixtures.TestUser
|
|
import com.tt.honeyDue.models.LoginRequest
|
|
import com.tt.honeyDue.models.RegisterRequest
|
|
import com.tt.honeyDue.network.APILayer
|
|
import com.tt.honeyDue.network.ApiResult
|
|
import com.tt.honeyDue.storage.TaskCacheManager
|
|
import com.tt.honeyDue.storage.TaskCacheStorage
|
|
import com.tt.honeyDue.storage.ThemeStorageManager
|
|
import com.tt.honeyDue.storage.TokenManager
|
|
import kotlinx.coroutines.runBlocking
|
|
import org.junit.Assert.assertNotNull
|
|
import org.junit.Assert.assertTrue
|
|
import org.junit.Before
|
|
import org.junit.FixMethodOrder
|
|
import org.junit.Test
|
|
import org.junit.runner.RunWith
|
|
import org.junit.runners.MethodSorters
|
|
|
|
/**
|
|
* Phase 1 — Seed tests run sequentially before the parallel suites.
|
|
*
|
|
* Ports `iosApp/HoneyDueUITests/AAA_SeedTests.swift`. The AAA prefix keeps
|
|
* these tests alphabetically first under JUnit's default sorter so seed
|
|
* state (a verified test user, a residence, a task) exists before
|
|
* `Suite*` tests run in parallel. `SuiteZZ_CleanupTests` (future) removes
|
|
* the leftover data at the end of a run.
|
|
*
|
|
* These hit the real dev backend configured in `ApiConfig.CURRENT_ENV`.
|
|
* If the backend is unreachable the tests fail fast — no silent skip.
|
|
*/
|
|
@RunWith(AndroidJUnit4::class)
|
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
|
class AAA_SeedTests {
|
|
|
|
private val testUser: TestUser = TestUser.seededTestUser()
|
|
private val adminUser: TestUser = TestUser.seededAdminUser()
|
|
|
|
@Before
|
|
fun setUp() {
|
|
val context = ApplicationProvider.getApplicationContext<Context>()
|
|
if (!DataManager.isInitializedValue()) {
|
|
// Mirror MainActivity.onCreate minus UI deps so APILayer has
|
|
// everything it needs to persist the auth token.
|
|
DataManager.initialize(
|
|
tokenMgr = TokenManager.getInstance(context),
|
|
themeMgr = ThemeStorageManager.getInstance(context),
|
|
persistenceMgr = PersistenceManager.getInstance(context),
|
|
)
|
|
}
|
|
// Task cache is consulted during prefetchAllData — initialize to
|
|
// avoid NPEs inside the APILayer success path.
|
|
TaskCacheStorage.initialize(TaskCacheManager.getInstance(context))
|
|
}
|
|
|
|
@Test
|
|
fun a01_seedTestUserCreated() = runBlocking {
|
|
// Try logging in first; account may already exist on the dev backend.
|
|
val loginResult = APILayer.login(
|
|
LoginRequest(username = testUser.username, password = testUser.password),
|
|
)
|
|
if (loginResult is ApiResult.Success) {
|
|
assertNotNull("Auth token must be populated after login", loginResult.data.token)
|
|
return@runBlocking
|
|
}
|
|
|
|
val registerResult = APILayer.register(
|
|
RegisterRequest(
|
|
username = testUser.username,
|
|
email = testUser.email,
|
|
password = testUser.password,
|
|
firstName = testUser.firstName,
|
|
lastName = testUser.lastName,
|
|
),
|
|
)
|
|
assertTrue(
|
|
"Expected to create seed testuser; got $registerResult",
|
|
registerResult is ApiResult.Success,
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun a02_seedAdminUserExists() = runBlocking {
|
|
val loginResult = APILayer.login(
|
|
LoginRequest(username = adminUser.username, password = adminUser.password),
|
|
)
|
|
if (loginResult is ApiResult.Success) {
|
|
assertNotNull("Auth token populated for admin login", loginResult.data.token)
|
|
return@runBlocking
|
|
}
|
|
val registerResult = APILayer.register(
|
|
RegisterRequest(
|
|
username = adminUser.username,
|
|
email = adminUser.email,
|
|
password = adminUser.password,
|
|
firstName = adminUser.firstName,
|
|
lastName = adminUser.lastName,
|
|
),
|
|
)
|
|
assertTrue(
|
|
"Expected to create seed admin; got $registerResult",
|
|
registerResult is ApiResult.Success,
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun a03_seedResidenceCreated() = runBlocking {
|
|
// Ensure we have a session for the test user.
|
|
val loginResult = APILayer.login(
|
|
LoginRequest(username = testUser.username, password = testUser.password),
|
|
)
|
|
assertTrue(
|
|
"Must be logged in as testuser before creating residence",
|
|
loginResult is ApiResult.Success,
|
|
)
|
|
|
|
val residenceResult = APILayer.createResidence(
|
|
TestResidence.house().toCreateRequest(),
|
|
)
|
|
assertTrue(
|
|
"Expected to create seed residence; got $residenceResult",
|
|
residenceResult is ApiResult.Success,
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun a04_seedTaskCreatedOnResidence() = runBlocking {
|
|
val loginResult = APILayer.login(
|
|
LoginRequest(username = testUser.username, password = testUser.password),
|
|
)
|
|
assertTrue(
|
|
"Must be logged in as testuser before creating task",
|
|
loginResult is ApiResult.Success,
|
|
)
|
|
|
|
// Use the first residence that comes back from `prefetchAllData`, which
|
|
// APILayer.login already kicked off. Fall back to creating one.
|
|
val residences = DataManager.residences.value
|
|
val residenceId = residences.firstOrNull()?.id
|
|
?: run {
|
|
val create = APILayer.createResidence(TestResidence.house().toCreateRequest())
|
|
(create as? ApiResult.Success)?.data?.id
|
|
?: error("Cannot create residence for task seed: $create")
|
|
}
|
|
|
|
val taskResult = APILayer.createTask(
|
|
TestTask.basic(residenceId = residenceId).toCreateRequest(),
|
|
)
|
|
assertTrue(
|
|
"Expected to create seed task; got $taskResult",
|
|
taskResult is ApiResult.Success,
|
|
)
|
|
}
|
|
|
|
// ---- Helpers ----
|
|
|
|
private fun DataManager.isInitializedValue(): Boolean {
|
|
// DataManager exposes `isInitialized` as a StateFlow<Boolean>.
|
|
return try {
|
|
val field = DataManager::class.java.getDeclaredField("_isInitialized")
|
|
field.isAccessible = true
|
|
@Suppress("UNCHECKED_CAST")
|
|
val flow = field.get(DataManager) as kotlinx.coroutines.flow.MutableStateFlow<Boolean>
|
|
flow.value
|
|
} catch (e: Throwable) {
|
|
false
|
|
}
|
|
}
|
|
}
|