import XCTest @testable import Voice /// Deliberate physical/native transport qualification. No stored credentials, /// microphones, account memory edits, preference mutation or payload logging. final class ChatLiveTests: XCTestCase { func testAuthenticatedNativeDurableChat() async throws { let env = ProcessInfo.processInfo.environment guard env["HOPE_CHAT_LIVE"] == "1" else { throw XCTSkip("Native live chat requires explicit HOPE_CHAT_LIVE=1") } guard let username = env["HOPE_TEST_USERNAME"], let password = env["HOPE_TEST_PASSWORD"], !password.isEmpty else { throw XCTSkip("Protected live credentials were not supplied") } let config = try ServerConfig.parse(env["HOPE_TEST_ORIGIN"] ?? "https://192.168.86.2:9178").get() let auth = AuthClient(config: config) print("HOPE_CHAT_PROBE native authenticated HTTP started; microphone remains off") let login = try await auth.login(username: username, password: password) let api = ChatAPI(config: config, token: login.token) var createdID: String? do { let verified = try await auth.me(token: login.token) XCTAssertEqual(verified.id, login.user.id) let creation = try await api.request("api/conversations", method: "POST", body: ["title": "Native qualification ยท disposable"]) let conversation = try ChatCoding.decoder.decode(ConversationEnvelope.self, from: creation).conversation createdID = conversation.id let sent = try await api.request("api/conversations/\(conversation.id)/messages", method: "POST", body: ["content": "Reply exactly: native chat transport verified", "idempotency_key": UUID().uuidString, "mode": "normal"]) let operation = try ChatCoding.decoder.decode(SendEnvelope.self, from: sent) var completed: RunDetail? for _ in 0..<90 { let bytes = try await api.request("api/runs/\(operation.run.id)") let detail = try ChatCoding.decoder.decode(RunDetail.self, from: bytes) if !detail.run.active { completed = detail; break } try await Task.sleep(for: .seconds(2)) } let result = try XCTUnwrap(completed, "Run did not terminate within qualification deadline") XCTAssertEqual(result.run.status, "completed", "Server run failed; body intentionally not logged") XCTAssertTrue(result.messages.contains { $0.role == "assistant" && !$0.content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }) let recorder = await MainActor.run { LiveEventRecorder() } try await api.events(run: operation.run.id, after: 0) { event in recorder.accept(event) } let replay = await MainActor.run { recorder.ids } XCTAssertFalse(replay.isEmpty) XCTAssertEqual(replay, replay.sorted()) XCTAssertEqual(Set(replay).count, replay.count) let detailBytes = try await api.request("api/conversations/\(conversation.id)") let detail = try ChatCoding.decoder.decode(ConversationDetail.self, from: detailBytes) XCTAssertTrue(detail.messages.contains { $0.role == "user" }) XCTAssertTrue(detail.messages.contains { $0.role == "assistant" }) let pinnedBytes = try await api.request("api/conversations/\(conversation.id)", method: "PATCH", body: ["revision": detail.conversation.revision, "pinned": true]) let pinned = try ChatCoding.decoder.decode(ConversationEnvelope.self, from: pinnedBytes).conversation XCTAssertTrue(pinned.pinned) _ = try await api.request("api/conversations/\(conversation.id)", method: "PATCH", body: ["revision": pinned.revision, "archived": true]) _ = try await api.request("api/conversations/\(conversation.id)", method: "DELETE") createdID = nil try await auth.logout(token: login.token) api.cancel() print("HOPE_CHAT_PROBE native login/me/create/send/completion/replay/pin/archive/delete/logout completed; no audio captured") } catch { if let createdID { _ = try? await api.request("api/conversations/\(createdID)", method: "DELETE") } try? await auth.logout(token: login.token) api.cancel() print("HOPE_CHAT_PROBE cleanup attempted after failure; bodies/credentials withheld") throw error } } } @MainActor private final class LiveEventRecorder { var ids: [Int] = [] func accept(_ event: ChatEvent) { ids.append(event.id) } }