Files
duooomi-ios-sdk/Tests/DuooomiBleSDKTests/PlayModeTests.swift
km2023 bc9f4e0da3 test: lock bind/setPlayMode JSON encoding via semantic assertions
Replace brittle exact-byte equality checks in BindPayloadByteRegressionTests
with substring + dict-based semantic assertions that tolerate Swift JSONEncoder
key-order variance across runtime versions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 10:57:36 +08:00

150 lines
6.4 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import XCTest
@testable import DuooomiBleSDK
final class PlayModeTests: XCTestCase {
func testRawValues() {
XCTAssertEqual(PlayMode.single.rawValue, 0)
XCTAssertEqual(PlayMode.loop.rawValue, 1)
}
func testInitFromRawValue() {
XCTAssertEqual(PlayMode(rawValue: 0), .single)
XCTAssertEqual(PlayMode(rawValue: 1), .loop)
XCTAssertNil(PlayMode(rawValue: 2))
}
func testCodableRoundTrip() throws {
let data = try JSONEncoder().encode(PlayMode.loop)
XCTAssertEqual(String(data: data, encoding: .utf8), "1")
let decoded = try JSONDecoder().decode(PlayMode.self, from: Data("0".utf8))
XCTAssertEqual(decoded, .single)
}
}
final class DeviceInfoLoopTests: XCTestCase {
private func decode(_ json: String) throws -> DeviceInfo {
try JSONDecoder().decode(DeviceInfo.self, from: Data(json.utf8))
}
func testLoopMissingDecodesToNil() throws {
let json = #"{"name":"d1","size":"360","brand":"duomi","powerlevel":80,"allspace":1024,"freespace":512}"#
let info = try decode(json)
XCTAssertNil(info.loop)
}
func testLoopZeroDecodesToSingle() throws {
let json = #"{"name":"d1","size":"360","brand":"duomi","powerlevel":80,"allspace":1024,"freespace":512,"loop":0}"#
let info = try decode(json)
XCTAssertEqual(info.loop, .single)
}
func testLoopOneDecodesToLoop() throws {
let json = #"{"name":"d1","size":"360","brand":"duomi","powerlevel":80,"allspace":1024,"freespace":512,"loop":1}"#
let info = try decode(json)
XCTAssertEqual(info.loop, .loop)
}
func testLoopInvalidValueDecodesToNil() throws {
let json = #"{"name":"d1","size":"360","brand":"duomi","powerlevel":80,"allspace":1024,"freespace":512,"loop":7}"#
let info = try decode(json)
XCTAssertNil(info.loop)
}
}
final class BindPayloadEncodingTests: XCTestCase {
private func encode(_ payload: BindPayload) throws -> [String: Any] {
let data = try JSONEncoder().encode(payload)
return try JSONSerialization.jsonObject(with: data) as? [String: Any] ?? [:]
}
func testBindPayloadWithoutLoopOmitsKey() throws {
let payload = BindPayload(type: 0x0F, userId: "u1", loop: nil)
let dict = try encode(payload)
XCTAssertEqual(dict["type"] as? Int, 0x0F)
XCTAssertEqual(dict["userId"] as? String, "u1")
XCTAssertNil(dict["loop"], "loop key MUST be omitted when nil (not encoded as null)")
XCTAssertEqual(dict.count, 2)
}
func testBindPayloadWithLoopIncludesValue() throws {
let payload = BindPayload(type: 0x0F, userId: "u1", loop: 1)
let dict = try encode(payload)
XCTAssertEqual(dict["type"] as? Int, 0x0F)
XCTAssertEqual(dict["userId"] as? String, "u1")
XCTAssertEqual(dict["loop"] as? Int, 1)
XCTAssertEqual(dict.count, 3)
}
func testBindPayloadWithLoopZeroIncludesValue() throws {
let payload = BindPayload(type: 0x0F, userId: "u1", loop: 0)
let dict = try encode(payload)
XCTAssertEqual(dict["loop"] as? Int, 0)
}
}
final class SetPlayModeGuardTests: XCTestCase {
private func makeSDK() -> DuooomiBleSDK {
DuooomiBleSDK(config: DuooomiBleConfig(apiKey: "test"))
}
func testSetPlayModeFailsWhenNotConnected() {
let sdk = makeSDK()
let exp = expectation(description: "completion")
sdk.setPlayMode(.loop, userId: "u1") { result in
switch result {
case .success:
XCTFail("expected failure when not connected")
case .failure(let error):
XCTAssertTrue("\(error)".lowercased().contains("connect"),
"expected notConnected-style error, got \(error)")
}
exp.fulfill()
}
wait(for: [exp], timeout: 1.0)
}
}
final class BindPayloadByteRegressionTests: XCTestCase {
/// bind / unbind loop=nil "loop" key "loop":null
/// JSONEncoder key Swift substring + dict
func testBindPayloadNilLoopOmitsKeyAndNull() throws {
let payload = BindPayload(type: 0x0F, userId: "abc", loop: nil)
let data = try JSONEncoder().encode(payload)
let json = String(data: data, encoding: .utf8) ?? ""
// JSON "loop" "null"
XCTAssertFalse(json.contains("loop"), "loop key MUST be absent; got \(json)")
XCTAssertFalse(json.contains("null"), "null literal MUST be absent; got \(json)")
// type / userId key
let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] ?? [:]
XCTAssertEqual(dict.count, 2)
XCTAssertEqual(dict["type"] as? Int, 15)
XCTAssertEqual(dict["userId"] as? String, "abc")
XCTAssertNil(dict["loop"])
}
/// setPlayMode loop=1 "1"
func testSetPlayModePayloadIncludesLoopOne() throws {
let payload = BindPayload(type: 0x0F, userId: "abc", loop: 1)
let data = try JSONEncoder().encode(payload)
let json = String(data: data, encoding: .utf8) ?? ""
// "loop":1
XCTAssertTrue(json.contains(#""loop":1"#), "expected literal substring \"loop\":1 in \(json)")
//
let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] ?? [:]
XCTAssertEqual(dict.count, 3)
XCTAssertEqual(dict["type"] as? Int, 15)
XCTAssertEqual(dict["userId"] as? String, "abc")
XCTAssertEqual(dict["loop"] as? Int, 1)
}
/// setPlayMode loop=0 0 "default"
func testSetPlayModePayloadIncludesLoopZero() throws {
let payload = BindPayload(type: 0x0F, userId: "abc", loop: 0)
let data = try JSONEncoder().encode(payload)
let json = String(data: data, encoding: .utf8) ?? ""
XCTAssertTrue(json.contains(#""loop":0"#), "expected literal substring \"loop\":0 in \(json)")
let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] ?? [:]
XCTAssertEqual(dict["loop"] as? Int, 0)
}
}