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>
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user