import XCTest import WebKit final class WorkspaceTests: XCTestCase { func testServerValidationAndOriginBoundary() throws { let url = try XCTUnwrap(WorkspaceServer.normalize(" HTTPS://Example.com:443/kanban ")) XCTAssertEqual(url.absoluteString, "https://example.com/kanban/") let server = WorkspaceServer(url: url, profile: UUID()) for value in ["http://example.com/", "https://user:secret@example.com/", "https://example.com/?token=1", "https://example.com/#x", "https://exa\nmple.com", "https://example.com\\evil", "file:///etc/passwd", "javascript:alert(1)", "https://example.com:99999/"] { XCTAssertNil(WorkspaceServer.normalize(value), value) } for value in ["https://example.com/login", "https://example.com/two-factor-challenge", "https://example.com/projects/1?ticket=54#details"] { XCTAssertTrue(server.allows(try XCTUnwrap(URL(string: value))), value) } for value in ["http://example.com/", "https://example.com:8443/", "https://evil.example.com/", "https://user@example.com/", "file:///tmp/index.html", "tauri://localhost/action/open"] { XCTAssertFalse(server.allows(try XCTUnwrap(URL(string: value))), value) } } func testExternalLinksStayOutsideTheSelectedServer() throws { let server = WorkspaceServer(url: URL(string: "https://example.com/")!, profile: UUID()) for value in ["https://git.example.com/org/repo/pulls/1#diff", "http://git.example.com/repo", "https://example.com:8443/other"] { let url = try XCTUnwrap(URL(string: value)) XCTAssertTrue(WorkspaceServer.allowsExternal(url)) XCTAssertFalse(server.allows(url)) } for value in ["https://user:secret@git.example.com/", "https://example.com:99999/", "javascript:alert(1)", "file:///etc/passwd", "tauri://localhost/action/open", "mailto:user@example.com", "data:text/html,test"] { XCTAssertFalse(WorkspaceServer.allowsExternal(try XCTUnwrap(URL(string: value))), value) } } @MainActor func testBackButtonTracksSameDocumentHistory() async throws { let name = "KAN55-history-\(UUID())" let defaults = try XCTUnwrap(UserDefaults(suiteName: name)) defer { defaults.removePersistentDomain(forName: name) } let controller = WorkspaceViewController(defaults: defaults) controller.loadViewIfNeeded() let url = try XCTUnwrap(WorkspaceServer.normalize("https://example.invalid/")) controller.open(url) let webview = try XCTUnwrap(controller.workspace) webview.stopLoading() webview.loadHTMLString("", baseURL: url) var loaded = false for _ in 0..<50 { if let value = try? await webview.evaluateJavaScript("document.getElementById('draft')?.value"), value as? String == "unsaved" { loaded = true; break } try await Task.sleep(for: .milliseconds(100)) } XCTAssertTrue(loaded) _ = try await webview.evaluateJavaScript("history.pushState({}, '', '#ticket')") let toolbar = try XCTUnwrap(controller.view.subviews.compactMap { $0 as? UIToolbar }.first) for _ in 0..<20 { if toolbar.items?.first?.isEnabled == true { break } try await Task.sleep(for: .milliseconds(50)) } XCTAssertTrue(webview.canGoBack) XCTAssertTrue(toolbar.items?.first?.isEnabled == true) webview.goBack() for _ in 0..<20 { if let hash = try? await webview.evaluateJavaScript("location.hash"), hash as? String == "" { break } try await Task.sleep(for: .milliseconds(50)) } let draft = try await webview.evaluateJavaScript("document.getElementById('draft').value") XCTAssertEqual(draft as? String, "unsaved") } func testServerChangesNeverReusePreviousProfile() throws { let firstURL = try XCTUnwrap(WorkspaceServer.normalize("https://example.com/")) let otherURL = try XCTUnwrap(WorkspaceServer.normalize("https://example.com/other/")) let first = WorkspaceServer.selecting(firstURL, current: nil) XCTAssertEqual(WorkspaceServer.selecting(firstURL, current: first), first) let second = WorkspaceServer.selecting(otherURL, current: first) XCTAssertNotEqual(second.profile, first.profile) XCTAssertNotEqual(WorkspaceServer.selecting(firstURL, current: second).profile, first.profile) XCTAssertEqual(try JSONDecoder().decode(WorkspaceServer.self, from: JSONEncoder().encode(first)), first) } func testTimeoutDoesNotDiscardLoadedWorkspaceOrAffectLaterNavigation() { var state = WorkspaceConnection() let first = state.begin() let second = state.begin() XCTAssertFalse(state.fail(first)) state.finish() XCTAssertFalse(state.fail(second)) let third = state.begin() XCTAssertTrue(state.fail(third)) XCTAssertTrue(state.ready) XCTAssertFalse(state.fail(third)) } @MainActor func testWKWebViewPersistsCookiesAcrossViewsAndIsolatesServerChanges() async throws { let profile = UUID() let first = WKWebViewConfiguration() first.websiteDataStore = WKWebsiteDataStore(forIdentifier: profile) let cookie = try XCTUnwrap(HTTPCookie(properties: [ .domain: "example.com", .path: "/", .name: "laravel_session", .value: "test-session", .secure: "TRUE", .expires: Date().addingTimeInterval(3600), ])) await first.websiteDataStore.httpCookieStore.setCookie(cookie) let restored = WKWebViewConfiguration() restored.websiteDataStore = WKWebsiteDataStore(forIdentifier: profile) let restoredCookies = await restored.websiteDataStore.httpCookieStore.allCookies() XCTAssertTrue(restoredCookies.contains { $0.name == "laravel_session" && $0.value == "test-session" }) let other = WKWebsiteDataStore(forIdentifier: UUID()) let otherCookies = await other.httpCookieStore.allCookies() XCTAssertTrue(otherCookies.isEmpty) await restored.websiteDataStore.httpCookieStore.deleteCookie(cookie) let loggedOut = await first.websiteDataStore.httpCookieStore.allCookies() XCTAssertFalse(loggedOut.contains { $0.name == "laravel_session" }) } @MainActor func testControllerRestoresServerAndCreatesIsolatedUnprivilegedWebView() throws { let name = "KAN54-tests-\(UUID())" let defaults = try XCTUnwrap(UserDefaults(suiteName: name)) defer { defaults.removePersistentDomain(forName: name) } let controller = WorkspaceViewController(defaults: defaults) controller.loadViewIfNeeded() let url = try XCTUnwrap(WorkspaceServer.normalize("https://example.invalid/")) controller.open(url) let server = try XCTUnwrap(controller.server) XCTAssertEqual(controller.workspace?.configuration.websiteDataStore.identifier, server.profile) XCTAssertTrue(controller.workspace?.configuration.userContentController.userScripts.isEmpty == true) let restored = WorkspaceViewController(defaults: defaults) restored.loadViewIfNeeded() XCTAssertEqual(restored.server, server) let other = try XCTUnwrap(WorkspaceServer.normalize("https://other.invalid/")) restored.open(other) XCTAssertNotEqual(restored.server?.profile, server.profile) } @MainActor func testConnectionFailureKeepsLoadedFormAndAllowsResume() async throws { let name = "KAN54-form-\(UUID())" let defaults = try XCTUnwrap(UserDefaults(suiteName: name)) defer { defaults.removePersistentDomain(forName: name) } let controller = WorkspaceViewController(defaults: defaults) controller.loadViewIfNeeded() let url = try XCTUnwrap(WorkspaceServer.normalize("https://example.invalid/")) controller.open(url) let webview = try XCTUnwrap(controller.workspace) webview.stopLoading() let navigation = try XCTUnwrap(webview.loadHTMLString("", baseURL: url)) var loaded = false for _ in 0..<50 { if let value = try? await webview.evaluateJavaScript("document.getElementById('draft')?.value"), value as? String == "unsaved ticket" { loaded = true; break } try await Task.sleep(for: .milliseconds(100)) } XCTAssertTrue(loaded) controller.webView(webview, didFinish: navigation) controller.webView(webview, didStartProvisionalNavigation: navigation) controller.webView(webview, didFailProvisionalNavigation: navigation, withError: NSError(domain: NSURLErrorDomain, code: NSURLErrorNotConnectedToInternet)) XCTAssertTrue(webview.isHidden) controller.open(url) XCTAssertTrue(controller.workspace === webview) XCTAssertFalse(webview.isHidden) let value = try await webview.evaluateJavaScript("document.getElementById('draft').value") XCTAssertEqual(value as? String, "unsaved ticket") let bridge = try await webview.evaluateJavaScript("typeof window.__TAURI_INTERNALS__") XCTAssertEqual(bridge as? String, "undefined") } }