mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-16 00:33:22 +08:00
94 lines
3.9 KiB
Python
94 lines
3.9 KiB
Python
"""Run the production event-driven update checker with a deterministic transport."""
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
|
|
@unittest.skipUnless(sys.platform == "darwin", "requires the Apple Swift runtime")
|
|
class AppUpdateCheckerTest(unittest.TestCase):
|
|
def test_visit_session_end_failures_and_lifecycle(self):
|
|
source = Path(__file__).resolve().parents[1] / "CrossDeskMobile/AppUpdateChecker.swift"
|
|
with tempfile.TemporaryDirectory(prefix="crossdesk-app-update-tests-") as directory:
|
|
harness = Path(directory) / "Tests.swift"
|
|
harness.write_text(r'''
|
|
import Foundation
|
|
|
|
// The actual shared C++ parser is covered by version_checker_test.cpp.
|
|
enum CrossDeskRTCBridge {
|
|
static func checkMobileUpdate(appVersion: String, releaseJSON: Data) -> String? {
|
|
String(data: releaseJSON, encoding: .utf8)
|
|
}
|
|
}
|
|
func expect(_ condition: @autoclosure () -> Bool, _ message: String) {
|
|
if !condition() { fatalError(message) }
|
|
}
|
|
@MainActor
|
|
final class Fixture {
|
|
var replies: [(Data?) -> Void] = []
|
|
var cancellations = 0
|
|
lazy var checker = AppUpdateChecker(currentVersion: "0.0.1", fetch: { completion in
|
|
self.replies.append(completion)
|
|
return { self.cancellations += 1 }
|
|
})
|
|
func reply(_ value: String?) { replies.last!(value.map { Data($0.utf8) }) }
|
|
}
|
|
@main
|
|
enum Tests {
|
|
@MainActor static func main() {
|
|
let f = Fixture()
|
|
f.checker.checkNow()
|
|
expect(f.replies.isEmpty, "No checks without consent and foreground")
|
|
f.checker.setEnabled(true)
|
|
f.checker.checkNow()
|
|
f.checker.setEnabled(true)
|
|
expect(f.replies.count == 1, "Deduplicate concurrent checks")
|
|
f.reply("0.0.2")
|
|
expect(f.checker.updateAvailable && f.checker.status == .available, "Show badge")
|
|
f.checker.setEnabled(true)
|
|
expect(f.replies.count == 1, "Duplicate active callbacks do not recheck after completion")
|
|
f.checker.setEnabled(false)
|
|
f.checker.setEnabled(true)
|
|
expect(f.replies.count == 2, "Every new visit checks even immediately after success")
|
|
f.reply(nil)
|
|
expect(f.checker.status == .failed && f.checker.updateAvailable,
|
|
"Failed checks retain discovered update")
|
|
f.checker.setEnabled(true)
|
|
expect(f.replies.count == 2, "A failure does not restart the check")
|
|
// Session teardown and the About action both explicitly request a check.
|
|
f.checker.checkNow()
|
|
f.checker.checkNow()
|
|
expect(f.replies.count == 3, "Explicit events check again without a cooldown and deduplicate")
|
|
f.reply("")
|
|
expect(!f.checker.updateAvailable && f.checker.status == .upToDate,
|
|
"Successful current-version check clears badge")
|
|
f.checker.checkNow()
|
|
let stale = f.replies.last!
|
|
f.checker.setEnabled(false)
|
|
expect(f.cancellations == 1, "Background/revocation cancels request")
|
|
stale(Data("9.0.0".utf8))
|
|
expect(!f.checker.updateAvailable, "Ignore cancelled response")
|
|
f.checker.setEnabled(true)
|
|
stale(Data("9.0.0".utf8))
|
|
expect(f.checker.status == .checking, "Ignore old response during new request")
|
|
f.reply("0.0.2")
|
|
f.checker.setEnabled(false)
|
|
let beforeForeground = f.replies.count
|
|
f.checker.checkNow()
|
|
expect(f.replies.count == beforeForeground, "Session endings in background do not check")
|
|
f.checker.setEnabled(true)
|
|
expect(f.replies.count == beforeForeground + 1, "Check once on return from background")
|
|
expect(f.checker.updateAvailable, "Retain badge while rechecking")
|
|
f.checker.setEnabled(false)
|
|
}
|
|
}
|
|
''')
|
|
binary = Path(directory) / "app-update-tests"
|
|
subprocess.run(["xcrun", "swiftc", str(source), str(harness), "-o", str(binary)], check=True)
|
|
subprocess.run([str(binary)], check=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|