Files
orca/mobile/modules/orca-mobile-web-shell/tests/MobileWebShellChecks.swift

112 lines
4.6 KiB
Swift

import Foundation
// Everything the shell decides before WebKit is involved: the session id it will accept, the
// requests it will answer, the map it builds from a manifest, and the policy header. Compiled and
// run without a device:
//
// swiftc -O -o /tmp/mobile-web-shell-checks \
// ios/MobileWebShellOrigin.swift ios/MobileWebShellGeneration.swift ios/MobileWebShellCsp.swift \
// ios/MobileWebShellLoadState.swift ios/MobileWebShellResponseHeaders.swift \
// ios/MobileWebShellBridge.swift ios/MobileWebShellAppliedProps.swift \
// ios/MobileWebShellNavigationPolicy.swift \
// tests/MobileWebShellChecks.swift && /tmp/mobile-web-shell-checks
@main struct MobileWebShellChecks {
static func manifest(
schemaVersion: Int = 1,
entrypoint: String = "index.html",
assets: [[String: Any]] = [
["path": "index.html", "contentType": "text/html; charset=utf-8"],
["path": "assets/aa.js", "contentType": "text/javascript; charset=utf-8"],
["path": "assets/bb.png", "contentType": "image/png"]
]
) -> Data {
let root: [String: Any] = [
"schemaVersion": schemaVersion,
"entrypoint": entrypoint,
"assets": assets
]
return try! JSONSerialization.data(withJSONObject: root)
}
static func generation(_ data: Data) -> MobileWebShellGeneration? {
try? MobileWebShellGeneration.make(
manifestData: data,
directory: URL(fileURLWithPath: "/tmp/generation", isDirectory: true)
)
}
static func checkGenerationMap() {
guard let built = generation(manifest()) else { preconditionFailure("manifest rejected") }
// Only / may serve document bytes with CSP; no entrypoint alias.
precondition(built.entries["/index.html"] == nil)
// The entrypoint must be one of the assets, or "/" would map to a file nobody declared.
precondition(generation(manifest(assets: [
["path": "assets/aa.js", "contentType": "text/javascript; charset=utf-8"]
])) == nil)
precondition(generation(manifest(assets: [
["path": "index.html", "contentType": "text/html; charset=utf-8"],
["path": "../escape.js", "contentType": "text/javascript; charset=utf-8"]
])) == nil)
precondition(generation(manifest(assets: [
["path": "index.html", "contentType": "text/html; charset=utf-8"],
["path": "assets/aa.js", "contentType": "text/javascript\r\nX-Injected: 1"]
])) == nil)
// NSNumber bridges JSON true and 1.0 to Int; neither satisfies the integer-1 contract.
precondition(generation(Data(#"{"schemaVersion":true,"entrypoint":"index.html","assets":[{"path":"index.html","contentType":"text/html"}]}"#.utf8)) == nil)
precondition(generation(Data(#"{"schemaVersion":1.0,"entrypoint":"index.html","assets":[{"path":"index.html","contentType":"text/html"}]}"#.utf8)) == nil)
}
static func checkLoadStateMachine() {
// replaceState rewrites the URL before finish; readiness must depend on commit state.
let unseated = MobileWebShellLoadStateMachine()
_ = unseated.started()
precondition(unseated.finished() == nil)
unseated.committed()
precondition(unseated.finished()?.state == "ready")
// A rule list compiles asynchronously, so it can fail after the generation was already refused.
let refused = MobileWebShellLoadStateMachine()
precondition(refused.failed(.generationUnreadable)?.reason == "generation-unreadable")
precondition(refused.failed(.isolationUnavailable) == nil)
precondition(refused.failed(.renderProcessGone) == nil)
precondition(refused.finished() == nil)
precondition(refused.started() == nil)
refused.reset()
precondition(refused.failed(.generationUnreadable)?.reason == "generation-unreadable")
// A document is heard only between its own commit and the end of that load.
let arming = MobileWebShellLoadStateMachine()
_ = arming.started()
// The previous document is alive and same-origin until the next one commits.
precondition(!arming.hasCommittedDocument)
arming.committed()
precondition(arming.hasCommittedDocument)
// A new prop triple: the committed document is the one being replaced.
arming.reset()
precondition(!arming.hasCommittedDocument)
arming.committed()
arming.documentEnded()
precondition(!arming.hasCommittedDocument)
// A failure ends the document, and nothing after it re-arms: a retry is a remount.
arming.committed()
_ = arming.failed(.renderProcessGone)
precondition(!arming.hasCommittedDocument)
arming.committed()
precondition(!arming.hasCommittedDocument)
}
static func main() {
checkGenerationMap()
checkLoadStateMachine()
print("mobile web shell checks OK")
}
}