EchoUITests/PushPairingUITests.swift
Rama main · réplica de lectura del repositorio original.
import XCTest
/// Notifications from a paired Hermes. The first two tests need nothing but the app: a pairing
/// link is confirmed before anything is sent, and Settings says how to pair. The third runs only
/// from the lab (`scripts/hermes-lab/lab.sh push --app`), which hands it a pairing link from an
/// unmodified Hermes: the app pairs through the lab's relay, and the notification for that
/// Hermes's next reply arrives sealed and is shown with the reply's text.
///
/// The simulator gets these as simulated pushes, which skip the notification extension; the app,
/// in front, opens them itself (`Notifier.presentUnopened`). The extension opening one while the
/// app is closed takes a real push from Apple and a phone.
@MainActor
final class PushPairingUITests: XCTestCase {
/// A well-formed code nobody is waiting on.
private let link = "redde://connect?push=j0DFrbaPJWJK5bIU6nZ6bslNgp09e14a0bpvPiE4KF8"
private let base = ["-echo.demo", "-echo.demoShort"] + XCUIApplication.straightToChat
private let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
private func launch(_ extra: [String]) -> XCUIApplication {
let app = XCUIApplication()
// Port 9: nothing listens, so a test that should send nothing can't reach a relay anyway.
app.launchArguments = base + (extra.contains("-push.relay") ? [] : ["-push.relay", "http://127.0.0.1:9"]) + extra
app.launch()
return app
}
/// A screenshot kept with the result when the test passes too: it is the lab's evidence.
private func keep(_ screenshot: XCUIScreenshot, as name: String) {
let attachment = XCTAttachment(screenshot: screenshot)
attachment.name = name
attachment.lifetime = .keepAlways
add(attachment)
}
func testAPairingLinkAsksBeforeAnythingIsSent() {
continueAfterFailure = false
let app = launch([])
XCTAssertTrue(app.navigationBars.buttons["New conversation"].firstMatch.waitForExistence(timeout: 15), "The app didn't come up")
XCUIDevice.shared.system.open(URL(string: link)!)
let bar = app.navigationBars["Pair with your Hermes"]
XCTAssertTrue(bar.waitForExistence(timeout: 15), "No confirmation for the pairing link")
XCTAssertTrue(app.text(containing: "Get notifications from this Hermes?").exists)
XCTAssertTrue(app.text(containing: "the relay that carries them can't read them").exists)
XCTAssertTrue(app.buttons["Pair"].exists)
XCTAssertFalse(app.navigationBars["Setup code"].exists, "A pairing link was taken for a setup code")
bar.buttons["Cancel"].tap()
XCTAssertTrue(bar.waitForNonExistence(timeout: 5))
}
func testSettingsSayHowToPairAndListNoPairingAtFirst() throws {
continueAfterFailure = false
let app = launch(["-echo.screen", "push"])
XCTAssertTrue(app.navigationBars["When Redde is closed"].waitForExistence(timeout: 15), "The pairing screen didn't open")
try XCTSkipIf(app.text(containing: "Paired ").exists, "This simulator is paired with a Hermes already")
XCTAssertTrue(app.text(containing: "Not paired with a Hermes yet.").exists)
XCTAssertTrue(app.text(containing: "hermes plugins install goosehouse-llc/redde/companion/hermes-plugin/redde-push").exists)
// Signed in to a Dashboard, the app offers to pair in one tap and folds the code away.
let byCode = app.buttons["Pair with a code instead"]
if byCode.exists { byCode.tap() }
XCTAssertTrue(app.text(containing: "hermes redde-push pair").exists)
XCTAssertTrue(app.buttons["Paste the pairing link"].exists)
XCTAssertTrue(app.text(containing: "neither can read it").exists, "The footer says who can read a notification")
}
func testPairingWithTheLabsHermesThenANotification() throws {
continueAfterFailure = false
let environment = ProcessInfo.processInfo.environment
guard let link = environment["PUSH_LINK"], !link.isEmpty else {
throw XCTSkip("Runs from scripts/hermes-lab/lab.sh push --app, which supplies a pairing link")
}
let relay = environment["PUSH_RELAY"] ?? "http://127.0.0.1:18980"
let app = launch(["-push.relay", relay, "-echo.pushLink", link])
let bar = app.navigationBars["Pair with your Hermes"]
XCTAssertTrue(bar.waitForExistence(timeout: 20), "No confirmation for the lab's pairing link")
app.buttons["Pair"].tap()
// The first time, iOS asks whether Redde may send notifications.
let allow = springboard.buttons["Allow"]
if allow.waitForExistence(timeout: 6) { allow.tap() }
// Both ends hold the same key once the plugin's first note has opened here.
XCTAssertTrue(app.text(containing: "Paired with").waitForExistence(timeout: 60),
"The lab's Hermes never confirmed: " + (app.staticTexts.allElementsBoundByIndex.map(\.label).joined(separator: " | ")))
keep(XCUIScreen.main.screenshot(), as: "paired")
app.buttons["Done"].tap()
// The lab now has its Hermes reply (the stub model answers "[A:alpha]"): the plugin's note
// has to arrive and show what the reply says, not the relay's words.
let banner = springboard.staticTexts.containing(NSPredicate(format: "label CONTAINS %@", "[A:alpha]")).firstMatch
XCTAssertTrue(banner.waitForExistence(timeout: 90), "No notification with the reply's text")
XCTAssertFalse(springboard.text(containing: "Open Redde to see what's new").exists, "The relay's words show: the note wasn't opened")
keep(XCUIScreen.main.screenshot(), as: "notified")
// Leave the simulator as it was: remove the pairing (which also removes it at the relay).
let again = launch(["-push.relay", relay, "-echo.screen", "push"])
XCTAssertTrue(again.navigationBars["When Redde is closed"].waitForExistence(timeout: 20))
let row = again.text(containing: "Paired ")
XCTAssertTrue(row.waitForExistence(timeout: 5), "The pairing isn't listed")
row.swipeLeft()
again.buttons["Remove"].tap()
XCTAssertTrue(again.text(containing: "Not paired with a Hermes yet.").waitForExistence(timeout: 10))
}
}