Files
honeyDueKMP/composeApp/src/androidInstrumentedTest/kotlin/com/tt/honeyDue/AAA_SeedTests.kt
T
Trey T 2d80ade6bc Test infra: shared accessibility IDs + PageObjects + AAA_SeedTests
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>
2026-04-18 14:19:37 -05:00

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
}
}
}