mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 04:47:03 -06:00
223 lines
8.5 KiB
Swift
223 lines
8.5 KiB
Swift
import Foundation
|
||
import OpenClawProtocol
|
||
import Testing
|
||
@testable import OpenClaw
|
||
|
||
private typealias SnapshotAnyCodable = OpenClaw.AnyCodable
|
||
|
||
private let channelOrder = ["whatsapp", "telegram", "signal", "imessage"]
|
||
private let channelLabels = [
|
||
"whatsapp": "WhatsApp",
|
||
"telegram": "Telegram",
|
||
"signal": "Signal",
|
||
"imessage": "iMessage",
|
||
]
|
||
private let channelDefaultAccountId = [
|
||
"whatsapp": "default",
|
||
"telegram": "default",
|
||
"signal": "default",
|
||
"imessage": "default",
|
||
]
|
||
|
||
@MainActor
|
||
private func makeChannelsStore(
|
||
channels: [String: SnapshotAnyCodable],
|
||
ts: Double = 1_700_000_000_000) -> ChannelsStore
|
||
{
|
||
let store = ChannelsStore(isPreview: true)
|
||
store.snapshot = ChannelsStatusSnapshot(
|
||
ts: ts,
|
||
channelOrder: channelOrder,
|
||
channelLabels: channelLabels,
|
||
channelDetailLabels: nil,
|
||
channelSystemImages: nil,
|
||
channelMeta: nil,
|
||
channels: channels,
|
||
channelAccounts: [:],
|
||
channelDefaultAccountId: channelDefaultAccountId)
|
||
return store
|
||
}
|
||
|
||
@Suite(.serialized)
|
||
@MainActor
|
||
struct ChannelsSettingsSmokeTests {
|
||
@Test func `whatsapp login wait result keeps latest qr until connected`() {
|
||
let store = makeChannelsStore(channels: [:])
|
||
store.whatsappLoginQrDataUrl = "data:image/png;base64,initial"
|
||
|
||
store.applyWhatsAppLoginWaitResult(
|
||
WhatsAppLoginWaitResult(
|
||
connected: false,
|
||
message: "QR refreshed. Scan the latest code in WhatsApp → Linked Devices.",
|
||
qrDataUrl: "data:image/png;base64,rotated"))
|
||
|
||
#expect(store.whatsappLoginQrDataUrl == "data:image/png;base64,rotated")
|
||
#expect(store.whatsappLoginConnected == false)
|
||
|
||
store.applyWhatsAppLoginWaitResult(
|
||
WhatsAppLoginWaitResult(
|
||
connected: false,
|
||
message: "Still waiting for the QR scan. Let me know when you’ve scanned it.",
|
||
qrDataUrl: nil))
|
||
|
||
#expect(store.whatsappLoginQrDataUrl == "data:image/png;base64,rotated")
|
||
|
||
store.applyWhatsAppLoginWaitResult(
|
||
WhatsAppLoginWaitResult(
|
||
connected: true,
|
||
message: "✅ Linked! WhatsApp is ready.",
|
||
qrDataUrl: nil))
|
||
|
||
#expect(store.whatsappLoginQrDataUrl == nil)
|
||
#expect(store.whatsappLoginConnected == true)
|
||
}
|
||
|
||
@Test func `whatsapp login wait budget allows one final poll`() {
|
||
let startedAt = Date(timeIntervalSince1970: 1_700_000_000)
|
||
var didRunFinalWait = false
|
||
|
||
#expect(
|
||
whatsappLoginWaitRequestTimeoutMs(
|
||
startedAt: startedAt,
|
||
timeoutMs: 1000,
|
||
didRunFinalWait: &didRunFinalWait,
|
||
now: Date(timeInterval: 0.25, since: startedAt)) == 750)
|
||
#expect(didRunFinalWait == false)
|
||
|
||
#expect(
|
||
whatsappLoginWaitRequestTimeoutMs(
|
||
startedAt: startedAt,
|
||
timeoutMs: 1000,
|
||
didRunFinalWait: &didRunFinalWait,
|
||
now: Date(timeInterval: 1.25, since: startedAt)) == 1)
|
||
#expect(didRunFinalWait == true)
|
||
|
||
#expect(
|
||
whatsappLoginWaitRequestTimeoutMs(
|
||
startedAt: startedAt,
|
||
timeoutMs: 1000,
|
||
didRunFinalWait: &didRunFinalWait,
|
||
now: Date(timeInterval: 1.5, since: startedAt)) == nil)
|
||
}
|
||
|
||
@Test func `cached config loads return without clearing dirty draft`() async {
|
||
let store = makeChannelsStore(channels: [:])
|
||
store.configSchema = ConfigSchemaNode(raw: ["type": "object"])
|
||
store.configSchemaSourceKey = "source-a"
|
||
store.configLoaded = true
|
||
store.configSourceKey = "source-a"
|
||
store.configDraft = ["channels": ["discord": ["enabled": true]]]
|
||
store.configDirty = true
|
||
|
||
store.resetConfigSchemaCacheIfSourceChanged("source-a")
|
||
store.resetConfigCacheIfSourceChanged("source-a")
|
||
|
||
#expect(store.configSchema != nil)
|
||
#expect(store.configDraft["channels"] != nil)
|
||
#expect(store.configDirty == true)
|
||
}
|
||
|
||
@Test func `config cache clears dirty draft when source changes`() {
|
||
let store = makeChannelsStore(channels: [:])
|
||
store.configSchema = ConfigSchemaNode(raw: ["type": "object"])
|
||
store.configSchemaSourceKey = "source-a"
|
||
store.configUiHints = ["channels.discord.enabled": ConfigUiHint(raw: ["label": "Discord"])]
|
||
store.configLoaded = true
|
||
store.configSourceKey = "source-a"
|
||
store.configRoot = ["channels": ["discord": ["enabled": false]]]
|
||
store.configDraft = ["channels": ["discord": ["enabled": true]]]
|
||
store.configDirty = true
|
||
|
||
store.resetConfigSchemaCacheIfSourceChanged("source-b")
|
||
store.resetConfigCacheIfSourceChanged("source-b")
|
||
|
||
#expect(store.configSchema == nil)
|
||
#expect(store.configUiHints.isEmpty)
|
||
#expect(store.configLoaded == false)
|
||
#expect(store.configRoot.isEmpty)
|
||
#expect(store.configDraft.isEmpty)
|
||
#expect(store.configDirty == false)
|
||
#expect(store.configSchemaSourceKey == "source-b")
|
||
#expect(store.configSourceKey == "source-b")
|
||
}
|
||
|
||
@Test func `schema response is ignored after source changes`() {
|
||
let store = makeChannelsStore(channels: [:])
|
||
store.configSchemaSourceKey = "source-b"
|
||
let res = ConfigSchemaResponse(
|
||
schema: SnapshotAnyCodable(["type": "object", "properties": ["stale": ["type": "string"]]]),
|
||
uihints: ["stale": SnapshotAnyCodable(["label": "Stale"])],
|
||
version: "1",
|
||
generatedat: "now")
|
||
|
||
store.applyConfigSchemaResponse(res, sourceKey: "source-a")
|
||
|
||
#expect(store.configSchema == nil)
|
||
#expect(store.configUiHints.isEmpty)
|
||
#expect(store.configSchemaSourceKey == "source-b")
|
||
}
|
||
|
||
@Test func `non forced config snapshots do not overwrite dirty draft`() {
|
||
let store = makeChannelsStore(channels: [:])
|
||
store.configSourceKey = "source-a"
|
||
store.configLoaded = true
|
||
store.configDraft = ["channels": ["discord": ["enabled": true]]]
|
||
store.configDirty = true
|
||
let snap = ConfigSnapshot(
|
||
path: nil,
|
||
exists: true,
|
||
raw: nil,
|
||
hash: nil,
|
||
parsed: nil,
|
||
valid: true,
|
||
config: ["channels": SnapshotAnyCodable(["discord": ["enabled": false]])],
|
||
issues: nil)
|
||
|
||
store.applyConfigSnapshot(snap, sourceKey: "source-a", force: false)
|
||
|
||
let channels = store.configDraft["channels"] as? [String: Any]
|
||
let discord = channels?["discord"] as? [String: Any]
|
||
#expect(discord?["enabled"] as? Bool == true)
|
||
#expect(store.configDirty == true)
|
||
|
||
store.applyConfigSnapshot(snap, sourceKey: "source-a", force: true)
|
||
|
||
let forcedChannels = store.configDraft["channels"] as? [String: Any]
|
||
let forcedDiscord = forcedChannels?["discord"] as? [String: Any]
|
||
#expect(forcedDiscord?["enabled"] as? Bool == false)
|
||
#expect(store.configDirty == false)
|
||
}
|
||
|
||
@Test func `forced config load queues behind background load`() {
|
||
let store = makeChannelsStore(channels: [:])
|
||
store.configLoading = true
|
||
store.configLoadingSourceKey = "source-a"
|
||
|
||
#expect(store.queueConfigReloadIfLoading(sourceKey: "source-a", force: false) == true)
|
||
#expect(store.configForceReloadPending == false)
|
||
|
||
#expect(store.queueConfigReloadIfLoading(sourceKey: "source-a", force: true) == true)
|
||
#expect(store.configForceReloadPending == true)
|
||
|
||
store.configForceReloadPending = false
|
||
#expect(store.queueConfigReloadIfLoading(sourceKey: "source-b", force: false) == true)
|
||
#expect(store.configForceReloadPending == true)
|
||
}
|
||
|
||
@Test func `schema reload queues behind background load after source changes`() {
|
||
let store = makeChannelsStore(channels: [:])
|
||
store.configSchemaLoading = true
|
||
store.configSchemaLoadingSourceKey = "source-a"
|
||
|
||
#expect(store.queueConfigSchemaReloadIfLoading(sourceKey: "source-a", force: false) == true)
|
||
#expect(store.configSchemaReloadPending == false)
|
||
|
||
#expect(store.queueConfigSchemaReloadIfLoading(sourceKey: "source-a", force: true) == true)
|
||
#expect(store.configSchemaReloadPending == true)
|
||
|
||
store.configSchemaReloadPending = false
|
||
#expect(store.queueConfigSchemaReloadIfLoading(sourceKey: "source-b", force: false) == true)
|
||
#expect(store.configSchemaReloadPending == true)
|
||
}
|
||
}
|