|
| 1 | +import Foundation |
| 2 | +@testable import wBlockCoreService |
| 3 | + |
| 4 | +private func require(_ value: Bool) { precondition(value) } |
| 5 | + |
| 6 | +@main |
| 7 | +struct SponsorBlockSettingsTransferTests { |
| 8 | + static func main() async throws { |
| 9 | + typealias Transfer = SponsorBlockSettingsTransfer |
| 10 | + let source = Data(#"{"userID":"PRIVATE-DO-NOT-COPY","payments":{"licenseKey":"LICENSE-SECRET"},"categorySelections":[{"name":"sponsor","option":2},{"name":"intro","option":1},{"name":"outro","option":0},{"name":"poi_highlight","option":1}],"disableSkipping":false,"dontShowNotice":true,"minDuration":3.5,"whitelistedChannels":["UC-example"]}"#.utf8) |
| 11 | + let settings = try Transfer.parse(source) |
| 12 | + require(settings.enabled && !settings.showNotice && settings.minimumDuration == 3.5) |
| 13 | + require(settings.modes["sponsor"] == "auto" && settings.modes["intro"] == "ask") |
| 14 | + require(settings.modes["outro"] == "off" && settings.modes["filler"] == "off") |
| 15 | + require(settings.modes["poi_highlight"] == nil && settings.excludedChannels == ["UC-example"]) |
| 16 | + let exported = try Transfer.exportData(settings) |
| 17 | + let text = String(decoding: exported, as: UTF8.self) |
| 18 | + require(!text.contains("PRIVATE") && !text.contains("LICENSE") && !text.contains("userID")) |
| 19 | + require(try Transfer.parse(exported) == settings) |
| 20 | + let modern = try Transfer.parse(Data(#"{"categorySelections":[]}"#.utf8), current: settings) |
| 21 | + require(modern.modes.values.allSatisfy { $0 == "off" }) |
| 22 | + require(modern.excludedChannels == settings.excludedChannels) |
| 23 | + print("PASS: upstream mapping, omitted categories, native round-trip and credential stripping") |
| 24 | + |
| 25 | + for invalid in [ |
| 26 | + #"{"userID":"SECRET"}"#, #"{"debug":{},"config":{"categorySelections":[]}}"#, |
| 27 | + #"{"categorySelections":[],"minDuration":-1}"#, |
| 28 | + #"{"categorySelections":[],"minDuration":true}"#, |
| 29 | + #"{"categorySelections":[],"disableSkipping":1}"#, |
| 30 | + #"{"categorySelections":[],"whitelistedChannels":[42]}"#, |
| 31 | + #"{"categorySelections":[{"name":"sponsor","option":9}]}"#, |
| 32 | + #"{"categorySelections":[{"name":"sponsor","option":2},{"name":"sponsor","option":1}]}"#, |
| 33 | + #"{"format":"wblock-sponsorblock-settings","version":2,"settings":{}}"#, |
| 34 | + "[]", "null", "not JSON" |
| 35 | + ] { |
| 36 | + do { _ = try Transfer.parse(Data(invalid.utf8)); fatalError("accepted invalid input") } |
| 37 | + catch {} |
| 38 | + } |
| 39 | + do { _ = try Transfer.parse(Data(repeating: 32, count: Transfer.maximumBytes + 1)); fatalError("accepted oversized input") } |
| 40 | + catch {} |
| 41 | + print("PASS: malformed, unsupported, mistyped and oversized backups rejected") |
| 42 | + |
| 43 | + let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) |
| 44 | + defer { try? FileManager.default.removeItem(at: directory) } |
| 45 | + let storage = UserScriptStorageManager(directoryURL: directory) |
| 46 | + let id = UUID() |
| 47 | + require(try await Transfer.settings(scriptID: id, storage: storage) == nil) |
| 48 | + try await Transfer.persist(settings, scriptID: id, storage: storage) |
| 49 | + let snapshot = await storage.snapshot(for: id.uuidString) |
| 50 | + let raw = snapshot[Transfer.storageKey]! |
| 51 | + require(!raw.contains("PRIVATE") && !raw.contains("LICENSE")) |
| 52 | + require(try JSONDecoder().decode(Transfer.Settings.self, from: Data(raw.utf8)) == settings) |
| 53 | + let secondProcess = UserScriptStorageManager(directoryURL: directory) |
| 54 | + require(try await Transfer.settings(scriptID: id, storage: secondProcess) == settings) |
| 55 | + var edited = settings |
| 56 | + edited.modes["intro"] = "auto" |
| 57 | + let pageJSON = String(decoding: try JSONEncoder().encode(edited), as: UTF8.self) |
| 58 | + let result = await secondProcess.setSerializedValue(pageJSON, forKey: Transfer.storageKey, scriptID: id.uuidString) |
| 59 | + require(result.ok) |
| 60 | + let refreshed = try await Transfer.settings(scriptID: id, storage: storage)! |
| 61 | + require(try Transfer.parse(Transfer.exportData(refreshed)) == edited) |
| 62 | + var invalid = edited |
| 63 | + invalid.minimumDuration = -.infinity |
| 64 | + do { try await Transfer.persist(invalid, scriptID: id, storage: storage); fatalError("persisted invalid settings") } |
| 65 | + catch {} |
| 66 | + require(try await Transfer.settings(scriptID: id, storage: storage) == edited) |
| 67 | + print("PASS: native import -> GM snapshot -> player edit -> refreshed native export; failed writes preserve settings") |
| 68 | + if CommandLine.arguments.count == 3, CommandLine.arguments[1] == "--browser-edit" { |
| 69 | + let browserData = try Data(contentsOf: URL(fileURLWithPath: CommandLine.arguments[2])) |
| 70 | + let browserSettings = try Transfer.validated(JSONDecoder().decode(Transfer.Settings.self, from: browserData)) |
| 71 | + let write = await storage.setSerializedValue(String(decoding: browserData, as: UTF8.self), forKey: Transfer.storageKey, scriptID: id.uuidString) |
| 72 | + require(write.ok && browserSettings.modes["intro"] == "auto") |
| 73 | + let native = try await Transfer.settings(scriptID: id, storage: storage)! |
| 74 | + require(try Transfer.parse(Transfer.exportData(native)) == browserSettings) |
| 75 | + print("PASS: real browser GM message persisted and exported back through native code") |
| 76 | + } |
| 77 | + if CommandLine.arguments.count == 2 { |
| 78 | + // A browser probe can consume the native-produced snapshot verbatim. |
| 79 | + try JSONEncoder().encode(snapshot).write(to: URL(fileURLWithPath: CommandLine.arguments[1])) |
| 80 | + } |
| 81 | + } |
| 82 | +} |
0 commit comments