feat: complete Tasks 21-28 — backend integration, security hardening, UI tests & CI
- Add Apple Sign-In backend (JWKS verification, account linking, session management) - Implement push notification deep linking with NotificationDeepLinkRouter - Add jailbreak detection, runtime integrity monitoring, secure enclave service - Implement OAuth social login, token refresh, and secure logout flows - Add image caching (memory/disk), optimizer, upload queue, async semaphore - Implement notification analytics, type preferences, and category setup - Expand UI test suite with UITestBase, accessibility, auth flow, performance tests - Add CI pipeline for iOS UI tests (3 device sizes) and performance benchmarks - Restructure Xcode project to manual groups with KordantWidgets target - Add SwiftLint, Swift Collections/Algorithms/GoogleSignIn dependencies - Update project.yml for XcodeGen with new targets and configurations
This commit is contained in:
187
iOS/KordantUITests/ServiceUITests.swift
Normal file
187
iOS/KordantUITests/ServiceUITests.swift
Normal file
@@ -0,0 +1,187 @@
|
||||
import XCTest
|
||||
|
||||
/// UI tests for service screens: DarkWatch, VoicePrint, SpamShield, HomeTitle, RemoveBrokers.
|
||||
final class ServiceUITests: UITestBase {
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
/// Relaunch the app with a specific scenario
|
||||
private func relaunch(scenario: UITestScenario) {
|
||||
app.terminate()
|
||||
app = XCUIApplication()
|
||||
app.launchArguments = ["-UITesting"]
|
||||
app.launchEnvironment["UITestScenario"] = scenario.rawValue
|
||||
app.launch()
|
||||
}
|
||||
|
||||
/// Navigate to the Services tab
|
||||
private func navigateToServicesList() {
|
||||
navigateToTab(.services)
|
||||
XCTAssertTrue(app.navigationBars["Services"].waitForExistence(timeout: 5),
|
||||
"Services list should load")
|
||||
}
|
||||
|
||||
/// Tap a service row by its name in the services list
|
||||
private func tapService(_ name: String) {
|
||||
app.buttons[name].tap()
|
||||
}
|
||||
|
||||
// MARK: - DarkWatch
|
||||
|
||||
/// Verify DarkWatch screen loads with watchlist items
|
||||
func testDarkWatchWatchlistLoads() {
|
||||
relaunch(scenario: .darkWatchPopulated)
|
||||
navigateToServicesList()
|
||||
tapService("DarkWatch")
|
||||
|
||||
XCTAssertTrue(app.navigationBars["DarkWatch"].waitForExistence(timeout: 5),
|
||||
"DarkWatch screen should load")
|
||||
|
||||
// Verify watchlist items are shown
|
||||
let watchlistItem = app.staticTexts["test@kordant.com"]
|
||||
XCTAssertTrue(watchlistItem.waitForExistence(timeout: 3),
|
||||
"Watchlist item should be visible")
|
||||
captureScreen(name: "DarkWatchWatchlist")
|
||||
}
|
||||
|
||||
/// Verify adding a watchlist item opens the add sheet
|
||||
func testDarkWatchAddWatchlistItem() {
|
||||
relaunch(scenario: .darkWatchPopulated)
|
||||
navigateToServicesList()
|
||||
tapService("DarkWatch")
|
||||
XCTAssertTrue(app.navigationBars["DarkWatch"].waitForExistence(timeout: 5))
|
||||
|
||||
// Tap the add button in the toolbar
|
||||
let addButton = app.navigationBars["DarkWatch"].buttons.firstMatch
|
||||
guard addButton.waitForExistence(timeout: 3) else {
|
||||
XCTFail("Add button in DarkWatch navigation bar not found")
|
||||
return
|
||||
}
|
||||
addButton.tap()
|
||||
|
||||
// Wait for add sheet to appear and fill in the form
|
||||
let termField = app.textFields.firstMatch
|
||||
guard termField.waitForExistence(timeout: 3) else {
|
||||
XCTFail("Term field in add sheet not found")
|
||||
return
|
||||
}
|
||||
termField.tap()
|
||||
termField.typeText("new-item@test.com")
|
||||
|
||||
// Tap the Add confirmation button
|
||||
let confirmAdd = app.buttons["Add"]
|
||||
if confirmAdd.waitForExistence(timeout: 2) {
|
||||
confirmAdd.tap()
|
||||
}
|
||||
|
||||
// Verify sheet dismisses
|
||||
let sheetGone = termField.waitForExistence(timeout: 2) == false
|
||||
XCTAssertTrue(sheetGone, "Sheet should dismiss after adding item")
|
||||
captureScreen(name: "DarkWatchAddItem")
|
||||
}
|
||||
|
||||
// MARK: - VoicePrint
|
||||
|
||||
/// Verify VoicePrint screen loads with enrollment information
|
||||
func testVoicePrintEnrollmentScreen() {
|
||||
relaunch(scenario: .voicePrintPopulated)
|
||||
navigateToServicesList()
|
||||
tapService("VoicePrint")
|
||||
|
||||
XCTAssertTrue(app.navigationBars["VoicePrint"].waitForExistence(timeout: 5),
|
||||
"VoicePrint screen should load")
|
||||
|
||||
// Verify enrollment section is shown
|
||||
let enrollmentSection = app.staticTexts["Voice Enrollments"]
|
||||
XCTAssertTrue(enrollmentSection.waitForExistence(timeout: 3),
|
||||
"Voice Enrollments section should be visible")
|
||||
captureScreen(name: "VoicePrintEnrollments")
|
||||
}
|
||||
|
||||
// MARK: - SpamShield
|
||||
|
||||
/// Verify SpamShield screen loads with rules
|
||||
func testSpamShieldRulesList() {
|
||||
relaunch(scenario: .spamShieldPopulated)
|
||||
navigateToServicesList()
|
||||
tapService("SpamShield")
|
||||
|
||||
XCTAssertTrue(app.navigationBars["SpamShield"].waitForExistence(timeout: 5),
|
||||
"SpamShield screen should load")
|
||||
|
||||
// Verify rules are visible
|
||||
let rulePattern = app.staticTexts["+1 (555) 999-9999"]
|
||||
XCTAssertTrue(rulePattern.waitForExistence(timeout: 3),
|
||||
"Spam rule pattern should be visible")
|
||||
captureScreen(name: "SpamShieldRules")
|
||||
}
|
||||
|
||||
// MARK: - HomeTitle
|
||||
|
||||
/// Verify HomeTitle screen loads with property list
|
||||
func testHomeTitlePropertyList() {
|
||||
relaunch(scenario: .homeTitlePopulated)
|
||||
navigateToServicesList()
|
||||
tapService("HomeTitle")
|
||||
|
||||
XCTAssertTrue(app.navigationBars["HomeTitle"].waitForExistence(timeout: 5),
|
||||
"HomeTitle screen should load")
|
||||
|
||||
// Verify properties are visible
|
||||
let propertyAddress = app.staticTexts["123 Main St"]
|
||||
XCTAssertTrue(propertyAddress.waitForExistence(timeout: 3),
|
||||
"Property address should be visible")
|
||||
captureScreen(name: "HomeTitleProperties")
|
||||
}
|
||||
|
||||
// MARK: - RemoveBrokers
|
||||
|
||||
/// Verify RemoveBrokers screen loads with broker listings and removal requests
|
||||
func testRemoveBrokersListingsShown() {
|
||||
relaunch(scenario: .removeBrokersPopulated)
|
||||
navigateToServicesList()
|
||||
tapService("Remove Brokers")
|
||||
|
||||
XCTAssertTrue(app.navigationBars["Remove Brokers"].waitForExistence(timeout: 5),
|
||||
"Remove Brokers screen should load")
|
||||
|
||||
// Verify broker registry section
|
||||
let brokerSection = app.staticTexts["Broker Registry"]
|
||||
XCTAssertTrue(brokerSection.waitForExistence(timeout: 3),
|
||||
"Broker Registry section should be visible")
|
||||
|
||||
// Verify broker names are shown
|
||||
let brokerName = app.staticTexts["DataAggregator Inc"]
|
||||
XCTAssertTrue(brokerName.waitForExistence(timeout: 3),
|
||||
"Broker listing should be visible")
|
||||
captureScreen(name: "RemoveBrokersListings")
|
||||
}
|
||||
|
||||
// MARK: - Back Navigation
|
||||
|
||||
/// Verify back navigation works from a service to the services list
|
||||
func testBackNavigationFromService() {
|
||||
navigateToServicesList()
|
||||
tapService("DarkWatch")
|
||||
XCTAssertTrue(app.navigationBars["DarkWatch"].waitForExistence(timeout: 5))
|
||||
|
||||
// Tap back button
|
||||
app.navigationBars["DarkWatch"].buttons["Services"].tap()
|
||||
XCTAssertTrue(app.navigationBars["Services"].waitForExistence(timeout: 3),
|
||||
"Should navigate back to Services list")
|
||||
}
|
||||
|
||||
// MARK: - Service Row Accessibility
|
||||
|
||||
/// Verify service rows exist and are tappable
|
||||
func testAllServiceRowsAreVisible() {
|
||||
navigateToServicesList()
|
||||
|
||||
let serviceNames = ["DarkWatch", "VoicePrint", "SpamShield", "HomeTitle", "Remove Brokers"]
|
||||
for name in serviceNames {
|
||||
let row = app.buttons[name]
|
||||
XCTAssertTrue(row.waitForExistence(timeout: 3),
|
||||
"Service row '\(name)' should be visible")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user