Files
honeyDueKMP/iosApp/HoneyDueUITests/Suite00_SeedTests.swift
treyt 5c360a2796 Rearchitect UI test suite for complete, non-flaky coverage against live API
- Migrate Suite4-10, SmokeTests, NavigationCriticalPathTests to AuthenticatedTestCase
  with seeded admin account and real backend login
- Add 34 accessibility identifiers across 11 app views (task completion, profile,
  notifications, theme, join residence, manage users, forms)
- Create FeatureCoverageTests (14 tests) covering previously untested features:
  profile edit, theme selection, notification prefs, task completion, manage users,
  join residence, task templates
- Create MultiUserSharingTests (18 API tests) and MultiUserSharingUITests (8 XCUI
  tests) for full cross-user residence sharing lifecycle
- Add cleanup infrastructure: SuiteZZ_CleanupTests auto-wipes test data after runs,
  cleanup_test_data.sh script for manual reset via admin API
- Add share code API methods to TestAccountAPIClient (generateShareCode, joinWithCode,
  getShareCode, listResidenceUsers, removeUser)
- Fix app bugs found by tests:
  - ResidencesListView join callback now uses forceRefresh:true
  - APILayer invalidates task cache when residence count changes
  - AllTasksView auto-reloads tasks when residence list changes
- Fix test quality: keyboard focus waits, Save/Add button label matching,
  Documents tab label (Docs), remove API verification from UI tests
- DataLayerTests and PasswordResetTests now verify through UI, not API calls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 17:32:13 -05:00

192 lines
6.7 KiB
Swift

import XCTest
/// Pre-suite backend data seeding.
///
/// Runs before all other suites (alphabetically `Suite00` < `Suite0_`).
/// Makes direct API calls via `TestAccountAPIClient` no app launch needed.
/// Every step is idempotent: existing data is reused, missing data is created.
final class Suite00_SeedTests: XCTestCase {
override func setUpWithError() throws {
try super.setUpWithError()
continueAfterFailure = false
}
// MARK: - 1. Gate Check
func test01_backendIsReachable() throws {
guard TestAccountAPIClient.isBackendReachable() else {
throw XCTSkip("Local backend is not reachable at \(TestAccountAPIClient.baseURL). Start the server and re-run.")
}
}
// MARK: - 2. Seed Test User Account
func test02_seedTestUserAccount() throws {
let u = SeededTestData.TestUser.self
// Try logging in first (account may already exist and be verified)
if let auth = TestAccountAPIClient.login(username: u.username, password: u.password) {
SeededTestData.testUserToken = auth.token
return
}
// Account doesn't exist or password is wrong register + verify + login
guard let session = TestAccountAPIClient.createVerifiedAccount(
username: u.username,
email: u.email,
password: u.password
) else {
XCTFail("Failed to create verified test user account '\(u.username)'")
return
}
SeededTestData.testUserToken = session.token
}
// MARK: - 3. Seed Admin Account
func test03_seedAdminAccount() throws {
let u = SeededTestData.AdminUser.self
if let auth = TestAccountAPIClient.login(username: u.username, password: u.password) {
SeededTestData.adminUserToken = auth.token
return
}
guard let session = TestAccountAPIClient.createVerifiedAccount(
username: u.username,
email: u.email,
password: u.password
) else {
XCTFail("Failed to create verified admin account '\(u.username)'")
return
}
SeededTestData.adminUserToken = session.token
}
// MARK: - 4. Seed Baseline Residence
func test04_seedBaselineResidence() throws {
let token = try requireTestUserToken()
// Check if "Seed Home" already exists
if let residences = TestAccountAPIClient.listResidences(token: token),
let existing = residences.first(where: { $0.name == SeededTestData.Residence.name }) {
SeededTestData.Residence.id = existing.id
return
}
// Create it
guard let residence = TestAccountAPIClient.createResidence(
token: token,
name: SeededTestData.Residence.name
) else {
XCTFail("Failed to create seed residence '\(SeededTestData.Residence.name)'")
return
}
SeededTestData.Residence.id = residence.id
}
// MARK: - 5. Seed Baseline Task
func test05_seedBaselineTask() throws {
let token = try requireTestUserToken()
let residenceId = try requireResidenceId()
// Check if "Seed Task" already exists in the residence
if let tasks = TestAccountAPIClient.listTasksByResidence(token: token, residenceId: residenceId),
let existing = tasks.first(where: { $0.title == SeededTestData.Task.title }) {
SeededTestData.Task.id = existing.id
return
}
guard let task = TestAccountAPIClient.createTask(
token: token,
residenceId: residenceId,
title: SeededTestData.Task.title
) else {
XCTFail("Failed to create seed task '\(SeededTestData.Task.title)'")
return
}
SeededTestData.Task.id = task.id
}
// MARK: - 6. Seed Baseline Contractor
func test06_seedBaselineContractor() throws {
let token = try requireTestUserToken()
if let contractors = TestAccountAPIClient.listContractors(token: token),
let existing = contractors.first(where: { $0.name == SeededTestData.Contractor.name }) {
SeededTestData.Contractor.id = existing.id
return
}
guard let contractor = TestAccountAPIClient.createContractor(
token: token,
name: SeededTestData.Contractor.name
) else {
XCTFail("Failed to create seed contractor '\(SeededTestData.Contractor.name)'")
return
}
SeededTestData.Contractor.id = contractor.id
}
// MARK: - 7. Seed Baseline Document
func test07_seedBaselineDocument() throws {
let token = try requireTestUserToken()
let residenceId = try requireResidenceId()
if let documents = TestAccountAPIClient.listDocuments(token: token),
let existing = documents.first(where: { $0.title == SeededTestData.Document.title }) {
SeededTestData.Document.id = existing.id
return
}
guard let document = TestAccountAPIClient.createDocument(
token: token,
residenceId: residenceId,
title: SeededTestData.Document.title
) else {
XCTFail("Failed to create seed document '\(SeededTestData.Document.title)'")
return
}
SeededTestData.Document.id = document.id
}
// MARK: - 8. Verification
func test08_verifySeedingComplete() {
XCTAssertNotNil(SeededTestData.testUserToken, "testuser token should be set")
XCTAssertNotNil(SeededTestData.adminUserToken, "admin token should be set")
XCTAssertNotEqual(SeededTestData.Residence.id, -1, "Seed residence ID should be populated")
XCTAssertNotEqual(SeededTestData.Task.id, -1, "Seed task ID should be populated")
XCTAssertNotEqual(SeededTestData.Contractor.id, -1, "Seed contractor ID should be populated")
XCTAssertNotEqual(SeededTestData.Document.id, -1, "Seed document ID should be populated")
XCTAssertTrue(SeededTestData.isSeeded, "All seeded data should be present")
}
// MARK: - Helpers
private func requireTestUserToken(file: StaticString = #filePath, line: UInt = #line) throws -> String {
guard let token = SeededTestData.testUserToken else {
throw XCTSkip("testuser token not available — earlier seed step likely failed")
}
return token
}
private func requireResidenceId(file: StaticString = #filePath, line: UInt = #line) throws -> Int {
guard SeededTestData.Residence.id != -1 else {
throw XCTSkip("Seed residence not available — test04 likely failed")
}
return SeededTestData.Residence.id
}
}