DataLireCoder swift package
This commit is contained in:
488
Tests/DLCDecoderTests/KeyedContainerTests.swift
Normal file
488
Tests/DLCDecoderTests/KeyedContainerTests.swift
Normal file
@@ -0,0 +1,488 @@
|
||||
import XCTest
|
||||
import DataLiteCore
|
||||
|
||||
@testable import DLCDecoder
|
||||
|
||||
final class KeyedContainerTests: XCTestCase {
|
||||
func testContains() {
|
||||
var row = SQLiteRow()
|
||||
row[CodingKeys.key1.stringValue] = .int(0)
|
||||
|
||||
let container = KeyedContainer(
|
||||
decoder: MockKeyedDecoder(sqliteData: row),
|
||||
codingPath: [],
|
||||
allKeys: [CodingKeys.key1]
|
||||
)
|
||||
|
||||
XCTAssertTrue(container.contains(.key1))
|
||||
XCTAssertFalse(container.contains(.key2))
|
||||
}
|
||||
|
||||
func testDecodeNil() {
|
||||
var row = SQLiteRow()
|
||||
row[CodingKeys.key1.stringValue] = .int(0)
|
||||
row[CodingKeys.key2.stringValue] = .null
|
||||
|
||||
let container = KeyedContainer(
|
||||
decoder: MockKeyedDecoder(sqliteData: row),
|
||||
codingPath: [],
|
||||
allKeys: [CodingKeys.key1, .key2]
|
||||
)
|
||||
|
||||
XCTAssertFalse(try container.decodeNil(forKey: .key1))
|
||||
XCTAssertTrue(try container.decodeNil(forKey: .key2))
|
||||
}
|
||||
|
||||
func testDecodeBool() {
|
||||
var row = SQLiteRow()
|
||||
row[CodingKeys.key1.stringValue] = .int(0)
|
||||
row[CodingKeys.key2.stringValue] = .int(1)
|
||||
|
||||
let container = KeyedContainer(
|
||||
decoder: MockKeyedDecoder(sqliteData: row),
|
||||
codingPath: [],
|
||||
allKeys: [CodingKeys.key1, .key2]
|
||||
)
|
||||
|
||||
XCTAssertFalse(try container.decode(Bool.self, forKey: .key1))
|
||||
XCTAssertTrue(try container.decode(Bool.self, forKey: .key2))
|
||||
XCTAssertNil(try container.decodeIfPresent(Bool.self, forKey: .key3))
|
||||
}
|
||||
|
||||
func testDecodeString() {
|
||||
let string = "Test string"
|
||||
var row = SQLiteRow()
|
||||
row[CodingKeys.key1.stringValue] = .text(string)
|
||||
|
||||
let container = KeyedContainer(
|
||||
decoder: MockKeyedDecoder(sqliteData: row),
|
||||
codingPath: [],
|
||||
allKeys: [CodingKeys.key1]
|
||||
)
|
||||
|
||||
XCTAssertEqual(try container.decode(String.self, forKey: .key1), string)
|
||||
XCTAssertNil(try container.decodeIfPresent(String.self, forKey: .key2))
|
||||
}
|
||||
|
||||
func testDecodeRealNumber() {
|
||||
var row = SQLiteRow()
|
||||
row[CodingKeys.key1.stringValue] = .real(3.14)
|
||||
row[CodingKeys.key2.stringValue] = .real(2.55)
|
||||
|
||||
let container = KeyedContainer(
|
||||
decoder: MockKeyedDecoder(sqliteData: row),
|
||||
codingPath: [],
|
||||
allKeys: [CodingKeys.key1, .key2]
|
||||
)
|
||||
|
||||
XCTAssertEqual(try container.decode(Double.self, forKey: .key1), 3.14)
|
||||
XCTAssertEqual(try container.decode(Float.self, forKey: .key2), 2.55)
|
||||
XCTAssertNil(try container.decodeIfPresent(Double.self, forKey: .key3))
|
||||
XCTAssertNil(try container.decodeIfPresent(Float.self, forKey: .key3))
|
||||
}
|
||||
|
||||
func testDecodeIntNumber() {
|
||||
var row = SQLiteRow()
|
||||
row[CodingKeys.key1.stringValue] = .int(42)
|
||||
|
||||
let container = KeyedContainer(
|
||||
decoder: MockKeyedDecoder(sqliteData: row),
|
||||
codingPath: [],
|
||||
allKeys: [CodingKeys.key1]
|
||||
)
|
||||
|
||||
XCTAssertEqual(try container.decode(Int.self, forKey: .key1), 42)
|
||||
XCTAssertEqual(try container.decode(Int8.self, forKey: .key1), 42)
|
||||
XCTAssertEqual(try container.decode(Int16.self, forKey: .key1), 42)
|
||||
XCTAssertEqual(try container.decode(Int32.self, forKey: .key1), 42)
|
||||
XCTAssertEqual(try container.decode(Int64.self, forKey: .key1), 42)
|
||||
XCTAssertEqual(try container.decode(UInt.self, forKey: .key1), 42)
|
||||
XCTAssertEqual(try container.decode(UInt8.self, forKey: .key1), 42)
|
||||
XCTAssertEqual(try container.decode(UInt16.self, forKey: .key1), 42)
|
||||
XCTAssertEqual(try container.decode(UInt32.self, forKey: .key1), 42)
|
||||
XCTAssertEqual(try container.decode(UInt64.self, forKey: .key1), 42)
|
||||
XCTAssertNil(try container.decodeIfPresent(Int.self, forKey: .key2))
|
||||
}
|
||||
|
||||
func testDecodeDate() {
|
||||
let date = Date(timeIntervalSince1970: 123456789)
|
||||
var row = SQLiteRow()
|
||||
row[CodingKeys.key1.stringValue] = .real(date.timeIntervalSince1970)
|
||||
|
||||
let container = KeyedContainer(
|
||||
decoder: MockKeyedDecoder(sqliteData: row),
|
||||
codingPath: [],
|
||||
allKeys: [CodingKeys.key1]
|
||||
)
|
||||
|
||||
XCTAssertEqual(try container.decode(Date.self, forKey: .key1), date)
|
||||
XCTAssertNil(try container.decodeIfPresent(Date.self, forKey: .key2))
|
||||
}
|
||||
|
||||
func testDecodeRawRepresentable() {
|
||||
let `case` = RawRepresentableEnum.test
|
||||
var row = SQLiteRow()
|
||||
row[CodingKeys.key1.stringValue] = .text(`case`.rawValue)
|
||||
|
||||
let container = KeyedContainer(
|
||||
decoder: MockKeyedDecoder(sqliteData: row),
|
||||
codingPath: [],
|
||||
allKeys: [CodingKeys.key1]
|
||||
)
|
||||
|
||||
XCTAssertEqual(try container.decode(RawRepresentableEnum.self, forKey: .key1), `case`)
|
||||
XCTAssertNil(try container.decodeIfPresent(RawRepresentableEnum.self, forKey: .key2))
|
||||
}
|
||||
|
||||
func testDecodeDecodable() {
|
||||
let `case` = DecodableEnum.test
|
||||
var row = SQLiteRow()
|
||||
row[CodingKeys.key1.stringValue] = .text(`case`.rawValue)
|
||||
|
||||
let container = KeyedContainer(
|
||||
decoder: MockKeyedDecoder(sqliteData: row),
|
||||
codingPath: [],
|
||||
allKeys: [CodingKeys.key1]
|
||||
)
|
||||
|
||||
XCTAssertEqual(try container.decode(DecodableEnum.self, forKey: .key1), `case`)
|
||||
XCTAssertNil(try container.decodeIfPresent(DecodableEnum.self, forKey: .key2))
|
||||
}
|
||||
|
||||
func testNestedContainerKeyedBy() {
|
||||
let container = KeyedContainer(
|
||||
decoder: MockKeyedDecoder(sqliteData: .init()),
|
||||
codingPath: [CodingKeys.key1],
|
||||
allKeys: [CodingKeys]()
|
||||
)
|
||||
|
||||
XCTAssertThrowsError(
|
||||
try container.nestedContainer(
|
||||
keyedBy: CodingKeys.self,
|
||||
forKey: .key2
|
||||
)
|
||||
) { error in
|
||||
guard case let DecodingError.typeMismatch(type, context) = error else {
|
||||
XCTFail("Expected DecodingError.typeMismatch, but got: \(error).")
|
||||
return
|
||||
}
|
||||
XCTAssertTrue(
|
||||
type == KeyedDecodingContainer<CodingKeys>.self,
|
||||
"Mismatched type in decoding error. Expected \(KeyedDecodingContainer<CodingKeys>.self)."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
context.codingPath as? [CodingKeys], [.key1, .key2],
|
||||
"Incorrect coding path in decoding error."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
context.debugDescription,
|
||||
"""
|
||||
Attempted to decode a nested keyed container for key '\(CodingKeys.key2.stringValue)',
|
||||
but the value cannot be represented as a keyed container.
|
||||
""",
|
||||
"Unexpected debug description in decoding error."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testNestedUnkeyedContainer() {
|
||||
let container = KeyedContainer(
|
||||
decoder: MockKeyedDecoder(sqliteData: .init()),
|
||||
codingPath: [CodingKeys.key1],
|
||||
allKeys: [CodingKeys]()
|
||||
)
|
||||
|
||||
XCTAssertThrowsError(
|
||||
try container.nestedUnkeyedContainer(forKey: .key2)
|
||||
) { error in
|
||||
guard case let DecodingError.typeMismatch(type, context) = error else {
|
||||
XCTFail("Expected DecodingError.typeMismatch, but got: \(error).")
|
||||
return
|
||||
}
|
||||
XCTAssertTrue(
|
||||
type == UnkeyedDecodingContainer.self,
|
||||
"Mismatched type in decoding error. Expected \(UnkeyedDecodingContainer.self)."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
context.codingPath as? [CodingKeys], [.key1, .key2],
|
||||
"Incorrect coding path in decoding error."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
context.debugDescription,
|
||||
"""
|
||||
Attempted to decode a nested unkeyed container for key '\(CodingKeys.key2.stringValue)',
|
||||
but the value cannot be represented as an unkeyed container.
|
||||
""",
|
||||
"Unexpected debug description in decoding error."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testSuperDecoder() {
|
||||
let container = KeyedContainer(
|
||||
decoder: MockKeyedDecoder(sqliteData: .init()),
|
||||
codingPath: [CodingKeys.key1],
|
||||
allKeys: [CodingKeys]()
|
||||
)
|
||||
|
||||
XCTAssertThrowsError(
|
||||
try container.superDecoder()
|
||||
) { error in
|
||||
guard case let DecodingError.typeMismatch(type, context) = error else {
|
||||
XCTFail("Expected DecodingError.typeMismatch, but got: \(error).")
|
||||
return
|
||||
}
|
||||
XCTAssertTrue(
|
||||
type == Swift.Decoder.self,
|
||||
"Mismatched type in decoding error. Expected \(Swift.Decoder.self)."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
context.codingPath as? [CodingKeys], [.key1],
|
||||
"Incorrect coding path in decoding error."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
context.debugDescription,
|
||||
"""
|
||||
Attempted to get a superDecoder,
|
||||
but SQLiteRowDecoder does not support superDecoding.
|
||||
""",
|
||||
"Unexpected debug description in decoding error."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testSuperDecoderForKey() {
|
||||
let container = KeyedContainer(
|
||||
decoder: MockKeyedDecoder(sqliteData: .init()),
|
||||
codingPath: [CodingKeys.key1],
|
||||
allKeys: [CodingKeys]()
|
||||
)
|
||||
|
||||
XCTAssertThrowsError(
|
||||
try container.superDecoder(forKey: .key2)
|
||||
) { error in
|
||||
guard case let DecodingError.typeMismatch(type, context) = error else {
|
||||
XCTFail("Expected DecodingError.typeMismatch, but got: \(error).")
|
||||
return
|
||||
}
|
||||
XCTAssertTrue(
|
||||
type == Swift.Decoder.self,
|
||||
"Unexpected type in decoding error. Expected \(Swift.Decoder.self)."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
context.codingPath as? [CodingKeys], [.key1, .key2],
|
||||
"Incorrect coding path in decoding error."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
context.debugDescription,
|
||||
"""
|
||||
Attempted to get a superDecoder for key '\(CodingKeys.key2.stringValue)',
|
||||
but SQLiteRowDecoder does not support nested structures.
|
||||
""",
|
||||
"Unexpected debug description in decoding error."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension KeyedContainerTests {
|
||||
enum CodingKeys: CodingKey {
|
||||
case key1
|
||||
case key2
|
||||
case key3
|
||||
}
|
||||
|
||||
enum RawRepresentableEnum: String, Decodable, SQLiteRawRepresentable {
|
||||
case test
|
||||
}
|
||||
|
||||
enum DecodableEnum: String, Decodable {
|
||||
case test
|
||||
}
|
||||
|
||||
final class MockDateDecoder: DateDecoder {
|
||||
func decode(
|
||||
from decoder: any ValueDecoder
|
||||
) throws -> Date {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(
|
||||
from decoder: any RowDecoder,
|
||||
for key: any CodingKey
|
||||
) throws -> Date {
|
||||
try decoder.decode(Date.self, for: key)
|
||||
}
|
||||
}
|
||||
|
||||
final class MockKeyedDecoder: RowDecoder, KeyCheckingDecoder {
|
||||
typealias SQLiteData = SQLiteRow
|
||||
|
||||
let sqliteData: SQLiteData
|
||||
let dateDecoder: DateDecoder
|
||||
let codingPath: [any CodingKey]
|
||||
let userInfo: [CodingUserInfoKey: Any]
|
||||
|
||||
var count: Int? { sqliteData.count }
|
||||
|
||||
init(
|
||||
sqliteData: SQLiteData,
|
||||
dateDecoder: DateDecoder = MockDateDecoder(),
|
||||
codingPath: [any CodingKey] = [],
|
||||
userInfo: [CodingUserInfoKey: Any] = [:]
|
||||
) {
|
||||
self.sqliteData = sqliteData
|
||||
self.dateDecoder = dateDecoder
|
||||
self.codingPath = codingPath
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
|
||||
func contains(_ key: any CodingKey) -> Bool {
|
||||
sqliteData.contains(key.stringValue)
|
||||
}
|
||||
|
||||
func decodeNil(
|
||||
for key: any CodingKey
|
||||
) throws -> Bool {
|
||||
sqliteData[key.stringValue] == .null
|
||||
}
|
||||
|
||||
func decodeDate(for key: any CodingKey) throws -> Date {
|
||||
try dateDecoder.decode(from: self, for: key)
|
||||
}
|
||||
|
||||
func decode<T: SQLiteRawRepresentable>(
|
||||
_ type: T.Type,
|
||||
for key: any CodingKey
|
||||
) throws -> T {
|
||||
type.init(sqliteData[key.stringValue]!)!
|
||||
}
|
||||
|
||||
func decoder(for key: any CodingKey) -> any Decoder {
|
||||
MockValueDecoder(sqliteData: sqliteData[key.stringValue]!)
|
||||
}
|
||||
|
||||
func container<Key: CodingKey>(
|
||||
keyedBy type: Key.Type
|
||||
) throws -> KeyedDecodingContainer<Key> {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func unkeyedContainer() throws -> any UnkeyedDecodingContainer {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func singleValueContainer() throws -> any SingleValueDecodingContainer {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
final class MockValueDecoder: ValueDecoder, SingleValueDecodingContainer {
|
||||
typealias SQLiteData = SQLiteRawValue
|
||||
|
||||
let sqliteData: SQLiteData
|
||||
var dateDecoder: DateDecoder
|
||||
var codingPath: [any CodingKey]
|
||||
var userInfo: [CodingUserInfoKey: Any]
|
||||
|
||||
init(
|
||||
sqliteData: SQLiteData,
|
||||
dateDecoder: DateDecoder = MockDateDecoder(),
|
||||
codingPath: [any CodingKey] = [],
|
||||
userInfo: [CodingUserInfoKey: Any] = [:]
|
||||
) {
|
||||
self.sqliteData = sqliteData
|
||||
self.dateDecoder = dateDecoder
|
||||
self.codingPath = codingPath
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
|
||||
func decodeNil() -> Bool {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decodeDate() throws -> Date {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode<T: SQLiteRawRepresentable>(
|
||||
_ type: T.Type
|
||||
) throws -> T {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func container<Key: CodingKey>(
|
||||
keyedBy type: Key.Type
|
||||
) throws -> KeyedDecodingContainer<Key> {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func unkeyedContainer() throws -> any UnkeyedDecodingContainer {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func singleValueContainer() throws -> any SingleValueDecodingContainer {
|
||||
self
|
||||
}
|
||||
|
||||
func decode(_ type: Bool.Type) throws -> Bool {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: String.Type) throws -> String {
|
||||
type.init(sqliteData)!
|
||||
}
|
||||
|
||||
func decode(_ type: Double.Type) throws -> Double {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Float.Type) throws -> Float {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Int.Type) throws -> Int {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Int8.Type) throws -> Int8 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Int16.Type) throws -> Int16 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Int32.Type) throws -> Int32 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Int64.Type) throws -> Int64 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: UInt.Type) throws -> UInt {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: UInt8.Type) throws -> UInt8 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: UInt16.Type) throws -> UInt16 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: UInt32.Type) throws -> UInt32 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: UInt64.Type) throws -> UInt64 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode<T: Decodable>(_ type: T.Type) throws -> T {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
}
|
||||
193
Tests/DLCDecoderTests/MultiRowDecoderTests.swift
Normal file
193
Tests/DLCDecoderTests/MultiRowDecoderTests.swift
Normal file
@@ -0,0 +1,193 @@
|
||||
import XCTest
|
||||
import DataLiteCore
|
||||
|
||||
@testable import DLCDecoder
|
||||
|
||||
final class MultiRowDecoderTests: XCTestCase {
|
||||
func testCount() {
|
||||
let rows = [SQLiteRow](repeating: .init(), count: 5)
|
||||
let decoder = decoder(sqliteData: rows)
|
||||
XCTAssertEqual(decoder.count, rows.count)
|
||||
}
|
||||
|
||||
func testDecodeNil() {
|
||||
let path = [DummyKey(stringValue: "rootKey")]
|
||||
let testKey = DummyKey(intValue: 0)
|
||||
let decoder = decoder(sqliteData: [], codingPath: path)
|
||||
|
||||
XCTAssertThrowsError(
|
||||
try decoder.decodeNil(for: testKey)
|
||||
) { error in
|
||||
guard case let DecodingError.dataCorrupted(context) = error else {
|
||||
return XCTFail("Expected DecodingError.dataCorrupted, but got: \(error).")
|
||||
}
|
||||
|
||||
XCTAssertEqual(context.codingPath as? [DummyKey], path + [testKey])
|
||||
XCTAssertEqual(context.debugDescription, "Attempted to decode nil, but it's not supported for an array of rows.")
|
||||
}
|
||||
}
|
||||
|
||||
func testDecodeDate() {
|
||||
let path = [DummyKey(stringValue: "root")]
|
||||
let key = DummyKey(stringValue: "date")
|
||||
let decoder = decoder(sqliteData: [], codingPath: path)
|
||||
|
||||
XCTAssertThrowsError(
|
||||
try decoder.decodeDate(for: key)
|
||||
) { error in
|
||||
guard case let DecodingError.typeMismatch(type, context) = error else {
|
||||
return XCTFail("Expected DecodingError.typeMismatch, but got: \(error)")
|
||||
}
|
||||
|
||||
XCTAssert(type == Date.self)
|
||||
XCTAssertEqual(context.codingPath as? [DummyKey], path + [key])
|
||||
XCTAssertEqual(context.debugDescription, "Expected a type of \(Date.self), but found an array of rows.")
|
||||
}
|
||||
}
|
||||
|
||||
func testDecodeForKey() {
|
||||
let path = [DummyKey(stringValue: "user")]
|
||||
let key = DummyKey(stringValue: "id")
|
||||
let decoder = decoder(sqliteData: [], codingPath: path)
|
||||
|
||||
XCTAssertThrowsError(
|
||||
try decoder.decode(String.self, for: key)
|
||||
) { error in
|
||||
guard case let DecodingError.typeMismatch(type, context) = error else {
|
||||
return XCTFail("Expected DecodingError.typeMismatch, but got: \(error)")
|
||||
}
|
||||
|
||||
XCTAssertTrue(type == String.self)
|
||||
XCTAssertEqual(context.codingPath as? [DummyKey], path + [key])
|
||||
XCTAssertEqual(
|
||||
context.debugDescription,
|
||||
"Expected a type of \(String.self), but found an array of rows."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testDecoderForKey() throws {
|
||||
let path = [DummyKey(stringValue: "rootKey")]
|
||||
let key = DummyKey(intValue: 0)
|
||||
let rows = [SQLiteRow()]
|
||||
let decoder = decoder(sqliteData: rows, codingPath: path)
|
||||
let nestedDecoder = try decoder.decoder(for: key)
|
||||
|
||||
guard let rowDecoder = nestedDecoder as? SingleRowDecoder else {
|
||||
return XCTFail("Expected SingleRowDecoder, but got: \(type(of: nestedDecoder)).")
|
||||
}
|
||||
|
||||
XCTAssertTrue(rowDecoder.dateDecoder as? MockDateDecoder === decoder.dateDecoder as? MockDateDecoder)
|
||||
XCTAssertEqual(rowDecoder.codingPath as? [DummyKey], path + [key])
|
||||
}
|
||||
|
||||
func testDecoderWithInvalidKey() {
|
||||
let path = [DummyKey(stringValue: "rootKey")]
|
||||
let testKey = DummyKey(stringValue: "invalidKey")
|
||||
let decoder = decoder(sqliteData: [], codingPath: path)
|
||||
|
||||
XCTAssertThrowsError(
|
||||
try decoder.decoder(for: testKey)
|
||||
) { error in
|
||||
guard case let DecodingError.keyNotFound(key, context) = error else {
|
||||
return XCTFail("Expected DecodingError.keyNotFound, but got: \(error).")
|
||||
}
|
||||
|
||||
XCTAssertEqual(key as? DummyKey, testKey)
|
||||
XCTAssertEqual(context.codingPath as? [DummyKey], path + [testKey])
|
||||
XCTAssertEqual(context.debugDescription, "Expected an integer key, but found a non-integer key.")
|
||||
}
|
||||
}
|
||||
|
||||
func testKeyedContainer() {
|
||||
let path: [DummyKey] = [DummyKey(stringValue: "root")]
|
||||
let decoder = decoder(sqliteData: [], codingPath: path)
|
||||
|
||||
XCTAssertThrowsError(try decoder.container(keyedBy: DummyKey.self)) { error in
|
||||
guard case let DecodingError.typeMismatch(expectedType, context) = error else {
|
||||
return XCTFail("Expected DecodingError.typeMismatch")
|
||||
}
|
||||
|
||||
XCTAssertTrue(expectedType == KeyedDecodingContainer<DummyKey>.self)
|
||||
XCTAssertEqual(context.codingPath as? [DummyKey], path)
|
||||
XCTAssertEqual(context.debugDescription, "Expected a keyed container, but found an array of rows.")
|
||||
}
|
||||
}
|
||||
|
||||
func testUnkeyedContainer() throws {
|
||||
let path = [DummyKey(stringValue: "rootKey")]
|
||||
let decoder = decoder(sqliteData: [SQLiteRow()], codingPath: path)
|
||||
|
||||
let container = try decoder.unkeyedContainer()
|
||||
|
||||
guard let unkeyed = container as? UnkeyedContainer<MultiRowDecoder> else {
|
||||
return XCTFail("Expected UnkeyedContainer, got: \(type(of: container))")
|
||||
}
|
||||
|
||||
XCTAssertTrue(unkeyed.decoder === decoder)
|
||||
XCTAssertEqual(unkeyed.codingPath as? [DummyKey], path)
|
||||
}
|
||||
|
||||
func testSingleValueContainer() {
|
||||
let path = [DummyKey(stringValue: "rootKey")]
|
||||
let decoder = decoder(sqliteData: [], codingPath: path)
|
||||
|
||||
XCTAssertThrowsError(
|
||||
try decoder.singleValueContainer()
|
||||
) { error in
|
||||
guard case let DecodingError.typeMismatch(type, context) = error else {
|
||||
return XCTFail("Expected DecodingError.typeMismatch, but got: \(error).")
|
||||
}
|
||||
|
||||
XCTAssertTrue(type == SingleValueDecodingContainer.self)
|
||||
XCTAssertEqual(context.codingPath as? [DummyKey], path)
|
||||
XCTAssertEqual(context.debugDescription, "Expected a single value container, but found an array of rows.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension MultiRowDecoderTests {
|
||||
func decoder(
|
||||
sqliteData: [SQLiteRow],
|
||||
codingPath: [any CodingKey] = []
|
||||
) -> MultiRowDecoder {
|
||||
MultiRowDecoder(
|
||||
dateDecoder: MockDateDecoder(),
|
||||
sqliteData: sqliteData,
|
||||
codingPath: codingPath,
|
||||
userInfo: [:]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private extension MultiRowDecoderTests {
|
||||
struct DummyKey: CodingKey, Equatable {
|
||||
var stringValue: String
|
||||
var intValue: Int?
|
||||
|
||||
init(stringValue: String) {
|
||||
self.stringValue = stringValue
|
||||
self.intValue = nil
|
||||
}
|
||||
|
||||
init(intValue: Int) {
|
||||
self.stringValue = "\(intValue)"
|
||||
self.intValue = intValue
|
||||
}
|
||||
}
|
||||
|
||||
final class MockDateDecoder: DateDecoder {
|
||||
func decode(
|
||||
from decoder: any ValueDecoder
|
||||
) throws -> Date {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(
|
||||
from decoder: any RowDecoder,
|
||||
for key: any CodingKey
|
||||
) throws -> Date {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
}
|
||||
255
Tests/DLCDecoderTests/SingleRowDecoderTests.swift
Normal file
255
Tests/DLCDecoderTests/SingleRowDecoderTests.swift
Normal file
@@ -0,0 +1,255 @@
|
||||
import XCTest
|
||||
import DataLiteCore
|
||||
|
||||
@testable import DLCDecoder
|
||||
|
||||
final class SingleRowDecoderTests: XCTestCase {
|
||||
func testCount() {
|
||||
var row = SQLiteRow()
|
||||
row["key1"] = .null
|
||||
row["key2"] = .int(1)
|
||||
row["key3"] = .text("")
|
||||
let decoder = decoder(sqliteData: row)
|
||||
XCTAssertEqual(decoder.count, row.count)
|
||||
}
|
||||
|
||||
func testContains() {
|
||||
var row = SQLiteRow()
|
||||
row["key1"] = .int(123)
|
||||
let decoder = decoder(sqliteData: row)
|
||||
XCTAssertTrue(decoder.contains(DummyKey(stringValue: "key1")))
|
||||
XCTAssertFalse(decoder.contains(DummyKey(stringValue: "key2")))
|
||||
}
|
||||
|
||||
func testDecodeNil() {
|
||||
var row = SQLiteRow()
|
||||
row["key1"] = .null
|
||||
row["key2"] = .int(0)
|
||||
let decoder = decoder(sqliteData: row)
|
||||
XCTAssertTrue(try decoder.decodeNil(for: DummyKey(stringValue: "key1")))
|
||||
XCTAssertFalse(try decoder.decodeNil(for: DummyKey(stringValue: "key2")))
|
||||
}
|
||||
|
||||
func testDecodeNilKeyNotFound() {
|
||||
let path = [DummyKey(stringValue: "rootKey")]
|
||||
let decoder = decoder(sqliteData: SQLiteRow(), codingPath: path)
|
||||
XCTAssertThrowsError(
|
||||
try decoder.decodeNil(for: DummyKey(stringValue: "key"))
|
||||
) { error in
|
||||
guard case let DecodingError.keyNotFound(key, context) = error else {
|
||||
return XCTFail("Expected DecodingError.keyNotFound, but got: \(error).")
|
||||
}
|
||||
XCTAssertEqual(key as? DummyKey, DummyKey(stringValue: "key"))
|
||||
XCTAssertEqual(context.codingPath as? [DummyKey], path + [DummyKey(stringValue: "key")])
|
||||
XCTAssertEqual(context.debugDescription, "No value associated with key \(DummyKey(stringValue: "key")).")
|
||||
}
|
||||
}
|
||||
|
||||
func testDecodeDate() {
|
||||
let expected = Date()
|
||||
let dateDecoder = MockDateDecoder(expectedDate: expected)
|
||||
let decoder = decoder(dateDecoder: dateDecoder, sqliteData: SQLiteRow())
|
||||
|
||||
XCTAssertEqual(try decoder.decodeDate(for: DummyKey(stringValue: "key")), expected)
|
||||
XCTAssertTrue(dateDecoder.didCallDecode)
|
||||
}
|
||||
|
||||
func testDecodeKeyNotFound() {
|
||||
let path = [DummyKey(stringValue: "rootKey")]
|
||||
let testKey = DummyKey(stringValue: "testKey")
|
||||
let decoder = decoder(sqliteData: SQLiteRow(), codingPath: path)
|
||||
XCTAssertThrowsError(
|
||||
try decoder.decode(Int.self, for: testKey)
|
||||
) { error in
|
||||
guard case let DecodingError.keyNotFound(key, context) = error else {
|
||||
return XCTFail("Expected DecodingError.keyNotFound, but got: \(error).")
|
||||
}
|
||||
XCTAssertEqual(key as? DummyKey, testKey)
|
||||
XCTAssertEqual(context.codingPath as? [DummyKey], path + [testKey])
|
||||
XCTAssertEqual(context.debugDescription, "No value associated with key \(testKey).")
|
||||
}
|
||||
}
|
||||
|
||||
func testDecodeWithNullValue() {
|
||||
let path = [DummyKey(stringValue: "rootKey")]
|
||||
let testKey = DummyKey(stringValue: "testKey")
|
||||
var row = SQLiteRow()
|
||||
row[testKey.stringValue] = .null
|
||||
let decoder = decoder(sqliteData: row, codingPath: path)
|
||||
XCTAssertThrowsError(
|
||||
try decoder.decode(Int.self, for: testKey)
|
||||
) { error in
|
||||
guard case let DecodingError.valueNotFound(type, context) = error else {
|
||||
return XCTFail("Expected DecodingError.valueNotFound, but got: \(error).")
|
||||
}
|
||||
XCTAssertTrue(type == Int.self)
|
||||
XCTAssertEqual(context.codingPath as? [DummyKey], path + [testKey])
|
||||
XCTAssertEqual(context.debugDescription, "Cannot get value of type Int, found null value instead.")
|
||||
}
|
||||
}
|
||||
|
||||
func testDecodeWithTypeMismatch() {
|
||||
let path = [DummyKey(stringValue: "rootKey")]
|
||||
let testKey = DummyKey(stringValue: "testKey")
|
||||
var row = SQLiteRow()
|
||||
row[testKey.stringValue] = .int(0)
|
||||
let decoder = decoder(sqliteData: row, codingPath: path)
|
||||
XCTAssertThrowsError(
|
||||
try decoder.decode(Data.self, for: testKey)
|
||||
) { error in
|
||||
guard case let DecodingError.typeMismatch(type, context) = error else {
|
||||
return XCTFail("Expected DecodingError.typeMismatch, but got: \(error).")
|
||||
}
|
||||
XCTAssertTrue(type == Data.self)
|
||||
XCTAssertEqual(context.codingPath as? [DummyKey], path + [testKey])
|
||||
XCTAssertEqual(context.debugDescription, "Expected to decode Data but found an \(SQLiteRawValue.int(0)) instead.")
|
||||
}
|
||||
}
|
||||
|
||||
func testDecodeWithCorrectValue() {
|
||||
let testKey = DummyKey(stringValue: "testKey")
|
||||
var row = SQLiteRow()
|
||||
row[testKey.stringValue] = .text("test")
|
||||
let decoder = decoder(sqliteData: row)
|
||||
XCTAssertEqual(try decoder.decode(String.self, for: testKey), "test")
|
||||
}
|
||||
|
||||
func testDecoderKeyNotFound() {
|
||||
let path = [DummyKey(stringValue: "rootKey")]
|
||||
let testKey = DummyKey(stringValue: "testKey")
|
||||
let decoder = decoder(sqliteData: SQLiteRow(), codingPath: path)
|
||||
|
||||
XCTAssertThrowsError(
|
||||
try decoder.decoder(for: testKey)
|
||||
) { error in
|
||||
guard case let DecodingError.keyNotFound(key, context) = error else {
|
||||
return XCTFail("Expected DecodingError.keyNotFound, but got: \(error).")
|
||||
}
|
||||
XCTAssertEqual(key as? DummyKey, testKey)
|
||||
XCTAssertEqual(context.codingPath as? [DummyKey], path + [testKey])
|
||||
XCTAssertEqual(context.debugDescription, "No value associated with key \(testKey).")
|
||||
}
|
||||
}
|
||||
|
||||
func testDecoderWithValidData() throws {
|
||||
var row = SQLiteRow()
|
||||
let testKey = DummyKey(stringValue: "testKey")
|
||||
row[testKey.stringValue] = .int(42)
|
||||
|
||||
let decoder = decoder(sqliteData: row)
|
||||
let valueDecoder = try decoder.decoder(for: testKey)
|
||||
|
||||
guard let singleValueDecoder = valueDecoder as? SingleValueDecoder else {
|
||||
return XCTFail("Expected SingleValueDecoder, but got: \(type(of: valueDecoder)).")
|
||||
}
|
||||
|
||||
XCTAssertEqual(singleValueDecoder.sqliteData, .int(42))
|
||||
XCTAssertEqual(singleValueDecoder.codingPath as? [DummyKey], [testKey])
|
||||
}
|
||||
|
||||
func testKeyedContainer() throws {
|
||||
let path = [DummyKey(stringValue: "rootKey")]
|
||||
let expectedKeys = [
|
||||
DummyKey(stringValue: "key1"),
|
||||
DummyKey(stringValue: "key2"),
|
||||
DummyKey(stringValue: "key3")
|
||||
]
|
||||
|
||||
var row = SQLiteRow()
|
||||
row[expectedKeys[0].stringValue] = .int(123)
|
||||
row[expectedKeys[1].stringValue] = .text("str")
|
||||
row[expectedKeys[2].stringValue] = .null
|
||||
|
||||
let decoder = decoder(sqliteData: row, codingPath: path)
|
||||
let container = try decoder.container(keyedBy: DummyKey.self)
|
||||
|
||||
XCTAssertEqual(container.codingPath as? [DummyKey], decoder.codingPath as? [DummyKey])
|
||||
XCTAssertEqual(container.allKeys, expectedKeys)
|
||||
}
|
||||
|
||||
func testUnkeyedContainer() throws {
|
||||
let path = [DummyKey(stringValue: "rootKey")]
|
||||
let decoder = decoder(sqliteData: SQLiteRow(), codingPath: path)
|
||||
|
||||
let container = try decoder.unkeyedContainer()
|
||||
|
||||
guard let unkeyed = container as? UnkeyedContainer<SingleRowDecoder> else {
|
||||
return XCTFail("Expected UnkeyedContainer, got: \(type(of: container))")
|
||||
}
|
||||
|
||||
XCTAssertTrue(unkeyed.decoder === decoder)
|
||||
XCTAssertEqual(unkeyed.codingPath as? [DummyKey], path)
|
||||
}
|
||||
|
||||
func testSingleValueContainer() {
|
||||
let path = [DummyKey(stringValue: "rootKey")]
|
||||
let decoder = decoder(sqliteData: SQLiteRow(), codingPath: path)
|
||||
|
||||
XCTAssertThrowsError(
|
||||
try decoder.singleValueContainer()
|
||||
) { error in
|
||||
guard case let DecodingError.typeMismatch(type, context) = error else {
|
||||
return XCTFail("Expected DecodingError.typeMismatch, but got: \(error).")
|
||||
}
|
||||
|
||||
XCTAssertTrue(type == SingleValueDecodingContainer.self)
|
||||
XCTAssertEqual(context.codingPath as? [DummyKey], path)
|
||||
XCTAssertEqual(context.debugDescription, "Expected a single value container, but found a row value.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension SingleRowDecoderTests {
|
||||
func decoder(
|
||||
dateDecoder: DateDecoder = MockDateDecoder(),
|
||||
sqliteData: SQLiteRow,
|
||||
codingPath: [any CodingKey] = []
|
||||
) -> SingleRowDecoder {
|
||||
SingleRowDecoder(
|
||||
dateDecoder: dateDecoder,
|
||||
sqliteData: sqliteData,
|
||||
codingPath: codingPath,
|
||||
userInfo: [:]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private extension SingleRowDecoderTests {
|
||||
struct DummyKey: CodingKey, Equatable {
|
||||
var stringValue: String
|
||||
var intValue: Int?
|
||||
|
||||
init(stringValue: String) {
|
||||
self.stringValue = stringValue
|
||||
self.intValue = nil
|
||||
}
|
||||
|
||||
init(intValue: Int) {
|
||||
self.stringValue = "\(intValue)"
|
||||
self.intValue = intValue
|
||||
}
|
||||
}
|
||||
|
||||
final class MockDateDecoder: DateDecoder {
|
||||
let expectedDate: Date
|
||||
private(set) var didCallDecode = false
|
||||
|
||||
init(expectedDate: Date = Date()) {
|
||||
self.expectedDate = expectedDate
|
||||
}
|
||||
|
||||
func decode(
|
||||
from decoder: any ValueDecoder
|
||||
) throws -> Date {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(
|
||||
from decoder: any RowDecoder,
|
||||
for key: any CodingKey
|
||||
) throws -> Date {
|
||||
didCallDecode = true
|
||||
return expectedDate
|
||||
}
|
||||
}
|
||||
}
|
||||
285
Tests/DLCDecoderTests/SingleValueContainerTests.swift
Normal file
285
Tests/DLCDecoderTests/SingleValueContainerTests.swift
Normal file
@@ -0,0 +1,285 @@
|
||||
import XCTest
|
||||
import DataLiteCore
|
||||
|
||||
@testable import DLCDecoder
|
||||
|
||||
final class SingleValueContainerTests: XCTestCase {
|
||||
func testDecodeNil() {
|
||||
let nullContainer = SingleValueContainer(
|
||||
decoder: MockSingleValueDecoder(sqliteData: .null),
|
||||
codingPath: []
|
||||
)
|
||||
XCTAssertTrue(
|
||||
nullContainer.decodeNil(),
|
||||
"Expected decodeNil to return true for null value."
|
||||
)
|
||||
|
||||
let nonNullContainer = SingleValueContainer(
|
||||
decoder: MockSingleValueDecoder(sqliteData: .int(42)),
|
||||
codingPath: []
|
||||
)
|
||||
XCTAssertFalse(
|
||||
nonNullContainer.decodeNil(),
|
||||
"Expected decodeNil to return false for non-null value."
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeBool() {
|
||||
let container = SingleValueContainer(
|
||||
decoder: MockSingleValueDecoder(sqliteData: .int(1)),
|
||||
codingPath: []
|
||||
)
|
||||
XCTAssertTrue(
|
||||
try container.decode(Bool.self),
|
||||
"Expected decoded Bool to be true."
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeString() {
|
||||
let container = SingleValueContainer(
|
||||
decoder: MockSingleValueDecoder(sqliteData: .text("Hello")),
|
||||
codingPath: []
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(String.self), "Hello",
|
||||
"Decoded String does not match expected value."
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeDouble() {
|
||||
let container = SingleValueContainer(
|
||||
decoder: MockSingleValueDecoder(sqliteData: .real(3.14)),
|
||||
codingPath: []
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(Double.self), 3.14,
|
||||
"Decoded Double does not match expected value."
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeFloat() {
|
||||
let container = SingleValueContainer(
|
||||
decoder: MockSingleValueDecoder(sqliteData: .real(2.5)),
|
||||
codingPath: []
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(Float.self), 2.5,
|
||||
"Decoded Float does not match expected value."
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeInt() {
|
||||
let container = SingleValueContainer(
|
||||
decoder: MockSingleValueDecoder(sqliteData: .int(42)),
|
||||
codingPath: []
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(Int.self), 42,
|
||||
"Decoded Int does not match expected value."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(Int8.self), 42,
|
||||
"Decoded Int8 does not match expected value."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(Int16.self), 42,
|
||||
"Decoded Int16 does not match expected value."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(Int32.self), 42,
|
||||
"Decoded Int32 does not match expected value."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(Int64.self), 42,
|
||||
"Decoded Int64 does not match expected value."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(UInt.self), 42,
|
||||
"Decoded UInt does not match expected value."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(UInt8.self), 42,
|
||||
"Decoded UInt8 does not match expected value."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(UInt16.self), 42,
|
||||
"Decoded UInt16 does not match expected value."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(UInt32.self), 42,
|
||||
"Decoded UInt32 does not match expected value."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(UInt64.self), 42,
|
||||
"Decoded UInt64 does not match expected value."
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeDate() {
|
||||
let date = Date(timeIntervalSince1970: 123456789)
|
||||
let container = SingleValueContainer(
|
||||
decoder: MockSingleValueDecoder(sqliteData: .real(date.timeIntervalSince1970)),
|
||||
codingPath: []
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(Date.self), date,
|
||||
"Decoded Date does not match expected value."
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeRawRepresentable() {
|
||||
let `case` = RawRepresentableEnum.test
|
||||
let container = SingleValueContainer(
|
||||
decoder: MockSingleValueDecoder(sqliteData: .text(`case`.rawValue)),
|
||||
codingPath: []
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(RawRepresentableEnum.self), `case`,
|
||||
"Decoded RawRepresentableEnum does not match expected value."
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeDecodable() {
|
||||
let `case` = DecodableEnum.test
|
||||
let container = SingleValueContainer(
|
||||
decoder: MockSingleValueDecoder(sqliteData: .text(`case`.rawValue)),
|
||||
codingPath: []
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(DecodableEnum.self), `case`,
|
||||
"Decoded DecodableEnum does not match expected value."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private extension SingleValueContainerTests {
|
||||
enum RawRepresentableEnum: String, Decodable, SQLiteRawRepresentable {
|
||||
case test
|
||||
}
|
||||
|
||||
enum DecodableEnum: String, Decodable {
|
||||
case test
|
||||
}
|
||||
|
||||
final class MockDateDecoder: DateDecoder {
|
||||
func decode(
|
||||
from decoder: any ValueDecoder
|
||||
) throws -> Date {
|
||||
try decoder.decode(Date.self)
|
||||
}
|
||||
|
||||
func decode(
|
||||
from decoder: any RowDecoder,
|
||||
for key: any CodingKey
|
||||
) throws -> Date {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
final class MockSingleValueDecoder: ValueDecoder, SingleValueDecodingContainer {
|
||||
let sqliteData: SQLiteRawValue
|
||||
let dateDecoder: DateDecoder
|
||||
let codingPath: [any CodingKey]
|
||||
let userInfo: [CodingUserInfoKey: Any]
|
||||
|
||||
init(
|
||||
sqliteData: SQLiteRawValue,
|
||||
dateDecoder: DateDecoder = MockDateDecoder(),
|
||||
codingPath: [any CodingKey] = [],
|
||||
userInfo: [CodingUserInfoKey: Any] = [:]
|
||||
) {
|
||||
self.sqliteData = sqliteData
|
||||
self.dateDecoder = dateDecoder
|
||||
self.codingPath = codingPath
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
|
||||
func decodeNil() -> Bool {
|
||||
sqliteData == .null
|
||||
}
|
||||
|
||||
func decodeDate() throws -> Date {
|
||||
try dateDecoder.decode(from: self)
|
||||
}
|
||||
|
||||
func decode<T: SQLiteRawRepresentable>(
|
||||
_ type: T.Type
|
||||
) throws -> T {
|
||||
type.init(sqliteData)!
|
||||
}
|
||||
|
||||
func container<Key: CodingKey>(
|
||||
keyedBy type: Key.Type
|
||||
) throws -> KeyedDecodingContainer<Key> {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func unkeyedContainer() throws -> any UnkeyedDecodingContainer {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func singleValueContainer() throws -> any SingleValueDecodingContainer {
|
||||
self
|
||||
}
|
||||
|
||||
func decode(_ type: Bool.Type) throws -> Bool {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: String.Type) throws -> String {
|
||||
String(sqliteData)!
|
||||
}
|
||||
|
||||
func decode(_ type: Double.Type) throws -> Double {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Float.Type) throws -> Float {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Int.Type) throws -> Int {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Int8.Type) throws -> Int8 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Int16.Type) throws -> Int16 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Int32.Type) throws -> Int32 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Int64.Type) throws -> Int64 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: UInt.Type) throws -> UInt {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: UInt8.Type) throws -> UInt8 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: UInt16.Type) throws -> UInt16 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: UInt32.Type) throws -> UInt32 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: UInt64.Type) throws -> UInt64 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode<T: Decodable>(_ type: T.Type) throws -> T {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
}
|
||||
152
Tests/DLCDecoderTests/SingleValueDecoderTests.swift
Normal file
152
Tests/DLCDecoderTests/SingleValueDecoderTests.swift
Normal file
@@ -0,0 +1,152 @@
|
||||
import XCTest
|
||||
import DataLiteCore
|
||||
|
||||
@testable import DLCDecoder
|
||||
|
||||
final class SingleValueDecoderTests: XCTestCase {
|
||||
func testDecodeNil() {
|
||||
XCTAssertTrue(decoder(sqliteData: .null).decodeNil())
|
||||
XCTAssertFalse(decoder(sqliteData: .int(0)).decodeNil())
|
||||
}
|
||||
|
||||
func testDecodeDate() {
|
||||
let expected = Date()
|
||||
let dateDecoder = MockDateDecoder(expectedDate: expected)
|
||||
let decoder = decoder(dateDecoder: dateDecoder, sqliteData: .int(0))
|
||||
|
||||
XCTAssertEqual(try decoder.decodeDate(), expected)
|
||||
XCTAssertTrue(dateDecoder.didCallDecode)
|
||||
}
|
||||
|
||||
func testDecodeWithNullValue() {
|
||||
let decoder = decoder(sqliteData: .null, codingPath: [DummyKey(stringValue: "test_key")])
|
||||
XCTAssertThrowsError(
|
||||
try decoder.decode(Int.self)
|
||||
) { error in
|
||||
guard case let DecodingError.valueNotFound(type, context) = error else {
|
||||
return XCTFail("Expected DecodingError.valueNotFound, but got: \(error).")
|
||||
}
|
||||
XCTAssertTrue(type == Int.self)
|
||||
XCTAssertEqual(context.codingPath as? [DummyKey], decoder.codingPath as? [DummyKey])
|
||||
XCTAssertEqual(context.debugDescription, "Cannot get value of type Int, found null value instead.")
|
||||
}
|
||||
}
|
||||
|
||||
func testDecodeWithTypeMismatch() {
|
||||
let decoder = decoder(sqliteData: .int(0), codingPath: [DummyKey(intValue: 1)])
|
||||
XCTAssertThrowsError(
|
||||
try decoder.decode(Data.self)
|
||||
) { error in
|
||||
guard case let DecodingError.typeMismatch(type, context) = error else {
|
||||
return XCTFail("Expected DecodingError.typeMismatch, but got: \(error).")
|
||||
}
|
||||
XCTAssertTrue(type == Data.self)
|
||||
XCTAssertEqual(context.codingPath as? [DummyKey], decoder.codingPath as? [DummyKey])
|
||||
XCTAssertEqual(context.debugDescription, "Expected to decode Data but found an \(SQLiteRawValue.int(0)) instead.")
|
||||
}
|
||||
}
|
||||
|
||||
func testDecodeWithCorrectValue() {
|
||||
let decoder = decoder(sqliteData: .text("test"))
|
||||
XCTAssertEqual(try decoder.decode(String.self), "test")
|
||||
}
|
||||
|
||||
func testKeyedContainer() {
|
||||
let path: [DummyKey] = [DummyKey(stringValue: "root")]
|
||||
let decoder = decoder(sqliteData: .text("value"), codingPath: path)
|
||||
|
||||
XCTAssertThrowsError(try decoder.container(keyedBy: DummyKey.self)) { error in
|
||||
guard case let DecodingError.typeMismatch(expectedType, context) = error else {
|
||||
return XCTFail("Expected DecodingError.typeMismatch")
|
||||
}
|
||||
|
||||
XCTAssertTrue(expectedType == KeyedDecodingContainer<DummyKey>.self)
|
||||
XCTAssertEqual(context.codingPath as? [DummyKey], path)
|
||||
XCTAssertEqual(context.debugDescription, "Expected a keyed container, but found a single value.")
|
||||
}
|
||||
}
|
||||
|
||||
func testUnkeyedContainer() {
|
||||
let path: [DummyKey] = [DummyKey(stringValue: "array_key")]
|
||||
let decoder = decoder(sqliteData: .int(42), codingPath: path)
|
||||
|
||||
XCTAssertThrowsError(try decoder.unkeyedContainer()) { error in
|
||||
guard case let DecodingError.typeMismatch(expectedType, context) = error else {
|
||||
return XCTFail("Expected DecodingError.typeMismatch, but got: \(error)")
|
||||
}
|
||||
|
||||
XCTAssertTrue(expectedType == UnkeyedDecodingContainer.self)
|
||||
XCTAssertEqual(context.codingPath as? [DummyKey], path)
|
||||
XCTAssertEqual(context.debugDescription, "Expected a unkeyed container, but found a single value.")
|
||||
}
|
||||
}
|
||||
|
||||
func testSingleValueContainer() throws {
|
||||
let path: [DummyKey] = [DummyKey(stringValue: "test_key")]
|
||||
let decoder = decoder(sqliteData: .int(0), codingPath: path)
|
||||
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
guard let singleContainer = container as? SingleValueContainer<SingleValueDecoder> else {
|
||||
return XCTFail("Expected SingleValueContainer")
|
||||
}
|
||||
|
||||
XCTAssertTrue(singleContainer.decoder === decoder)
|
||||
XCTAssertEqual(singleContainer.codingPath as? [DummyKey], path)
|
||||
}
|
||||
}
|
||||
|
||||
private extension SingleValueDecoderTests {
|
||||
func decoder(
|
||||
dateDecoder: DateDecoder = MockDateDecoder(),
|
||||
sqliteData: SQLiteRawValue,
|
||||
codingPath: [any CodingKey] = []
|
||||
) -> SingleValueDecoder {
|
||||
SingleValueDecoder(
|
||||
dateDecoder: dateDecoder,
|
||||
sqliteData: sqliteData,
|
||||
codingPath: codingPath,
|
||||
userInfo: [:]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private extension SingleValueDecoderTests {
|
||||
struct DummyKey: CodingKey, Equatable {
|
||||
var stringValue: String
|
||||
var intValue: Int?
|
||||
|
||||
init(stringValue: String) {
|
||||
self.stringValue = stringValue
|
||||
self.intValue = nil
|
||||
}
|
||||
|
||||
init(intValue: Int) {
|
||||
self.stringValue = "\(intValue)"
|
||||
self.intValue = intValue
|
||||
}
|
||||
}
|
||||
|
||||
final class MockDateDecoder: DateDecoder {
|
||||
let expectedDate: Date
|
||||
private(set) var didCallDecode = false
|
||||
|
||||
init(expectedDate: Date = Date()) {
|
||||
self.expectedDate = expectedDate
|
||||
}
|
||||
|
||||
func decode(
|
||||
from decoder: any ValueDecoder
|
||||
) throws -> Date {
|
||||
didCallDecode = true
|
||||
return expectedDate
|
||||
}
|
||||
|
||||
func decode(
|
||||
from decoder: any RowDecoder,
|
||||
for key: any CodingKey
|
||||
) throws -> Date {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
}
|
||||
733
Tests/DLCDecoderTests/UnkeyedContainerTests.swift
Normal file
733
Tests/DLCDecoderTests/UnkeyedContainerTests.swift
Normal file
@@ -0,0 +1,733 @@
|
||||
import XCTest
|
||||
import DataLiteCore
|
||||
import DLCCommon
|
||||
|
||||
@testable import DLCDecoder
|
||||
|
||||
final class UnkeyedContainerTests: XCTestCase {
|
||||
func testDecodeNil() {
|
||||
XCTAssertThrowsError(try container().decodeNil()) {
|
||||
checkIsAtEnd($0, Optional<Any>.self)
|
||||
}
|
||||
|
||||
let nilContainer = container(withData: .null)
|
||||
XCTAssertEqual(
|
||||
nilContainer.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding nil"
|
||||
)
|
||||
XCTAssertTrue(
|
||||
try nilContainer.decodeNil(),
|
||||
"Expected decodeNil() to return true"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
nilContainer.currentIndex, 1,
|
||||
"Expected index to increment after decoding nil"
|
||||
)
|
||||
|
||||
let notNilContainer = container(withData: .text("value"))
|
||||
XCTAssertFalse(
|
||||
try notNilContainer.decodeNil(),
|
||||
"Expected decodeNil() to return false for non-nil value"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
notNilContainer.currentIndex, 0,
|
||||
"Expected index to remain unchanged for non-nil value"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeBool() {
|
||||
XCTAssertThrowsError(try container().decode(Bool.self)) {
|
||||
checkIsAtEnd($0, Bool.self)
|
||||
}
|
||||
|
||||
let trueContainer = container(withData: .int(1))
|
||||
XCTAssertEqual(
|
||||
trueContainer.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding true"
|
||||
)
|
||||
XCTAssertTrue(
|
||||
try trueContainer.decode(Bool.self),
|
||||
"Expected decode(Bool.self) to return true"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
trueContainer.currentIndex, 1,
|
||||
"Expected index to increment after decoding true"
|
||||
)
|
||||
|
||||
let falseContainer = container(withData: .int(0))
|
||||
XCTAssertEqual(
|
||||
falseContainer.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding false"
|
||||
)
|
||||
XCTAssertFalse(
|
||||
try falseContainer.decode(Bool.self),
|
||||
"Expected decode(Bool.self) to return false"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
falseContainer.currentIndex, 1,
|
||||
"Expected index to increment after decoding false"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeString() {
|
||||
XCTAssertThrowsError(try container().decode(String.self)) {
|
||||
checkIsAtEnd($0, String.self)
|
||||
}
|
||||
|
||||
let container = container(withData: .text("Hello, SQLite!"))
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding string"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(String.self), "Hello, SQLite!",
|
||||
"Expected decoded value to match original string"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 1,
|
||||
"Expected index to increment after decoding string"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeDouble() {
|
||||
XCTAssertThrowsError(try container().decode(Double.self)) {
|
||||
checkIsAtEnd($0, Double.self)
|
||||
}
|
||||
|
||||
let container = container(withData: .real(3.14))
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding double"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(Double.self), 3.14,
|
||||
"Expected decoded value to match original double"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 1,
|
||||
"Expected index to increment after decoding double"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeFloat() {
|
||||
XCTAssertThrowsError(try container().decode(Float.self)) {
|
||||
checkIsAtEnd($0, Float.self)
|
||||
}
|
||||
|
||||
let container = container(withData: .real(2.71))
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding float"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(Float.self), 2.71,
|
||||
"Expected decoded value to match original float"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 1,
|
||||
"Expected index to increment after decoding float"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeInt() {
|
||||
XCTAssertThrowsError(try container().decode(Int.self)) {
|
||||
checkIsAtEnd($0, Int.self)
|
||||
}
|
||||
|
||||
let container = container(withData: .int(42))
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding Int"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(Int.self), 42,
|
||||
"Expected decoded value to match Int"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 1,
|
||||
"Expected index to increment after decoding Int"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeInt8() {
|
||||
XCTAssertThrowsError(try container().decode(Int8.self)) {
|
||||
checkIsAtEnd($0, Int8.self)
|
||||
}
|
||||
|
||||
let container = container(withData: .int(127))
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding Int8"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(Int8.self), 127,
|
||||
"Expected decoded value to match Int8"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 1,
|
||||
"Expected index to increment after decoding Int8"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeInt16() {
|
||||
XCTAssertThrowsError(try container().decode(Int16.self)) {
|
||||
checkIsAtEnd($0, Int16.self)
|
||||
}
|
||||
|
||||
let container = container(withData: .int(32_000))
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding Int16"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(Int16.self), 32_000,
|
||||
"Expected decoded value to match Int16"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 1,
|
||||
"Expected index to increment after decoding Int16"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeInt32() {
|
||||
XCTAssertThrowsError(try container().decode(Int32.self)) {
|
||||
checkIsAtEnd($0, Int32.self)
|
||||
}
|
||||
|
||||
let container = container(withData: .int(2_147_483_647))
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding Int32"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(Int32.self), 2_147_483_647,
|
||||
"Expected decoded value to match Int32"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 1,
|
||||
"Expected index to increment after decoding Int32"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeInt64() {
|
||||
XCTAssertThrowsError(try container().decode(Int64.self)) {
|
||||
checkIsAtEnd($0, Int64.self)
|
||||
}
|
||||
|
||||
let container = container(withData: .int(9_223_372_036_854_775_807))
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding Int64"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(Int64.self), 9_223_372_036_854_775_807,
|
||||
"Expected decoded value to match Int64"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 1,
|
||||
"Expected index to increment after decoding Int64"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeUInt() {
|
||||
XCTAssertThrowsError(try container().decode(UInt.self)) {
|
||||
checkIsAtEnd($0, UInt.self)
|
||||
}
|
||||
|
||||
let container = container(withData: .int(42))
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding UInt"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(UInt.self), 42,
|
||||
"Expected decoded value to match UInt"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 1,
|
||||
"Expected index to increment after decoding UInt"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeUInt8() {
|
||||
XCTAssertThrowsError(try container().decode(UInt8.self)) {
|
||||
checkIsAtEnd($0, UInt8.self)
|
||||
}
|
||||
|
||||
let container = container(withData: .int(255))
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding UInt8"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(UInt8.self), 255,
|
||||
"Expected decoded value to match UInt8"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 1,
|
||||
"Expected index to increment after decoding UInt8"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeUInt16() {
|
||||
XCTAssertThrowsError(try container().decode(UInt16.self)) {
|
||||
checkIsAtEnd($0, UInt16.self)
|
||||
}
|
||||
|
||||
let container = container(withData: .int(32_000))
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding UInt16"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(UInt16.self), 32_000,
|
||||
"Expected decoded value to match UInt16"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 1,
|
||||
"Expected index to increment after decoding UInt16"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeUInt32() {
|
||||
XCTAssertThrowsError(try container().decode(UInt32.self)) {
|
||||
checkIsAtEnd($0, UInt32.self)
|
||||
}
|
||||
|
||||
let container = container(withData: .int(4_294_967_295))
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding UInt32"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(UInt32.self), 4_294_967_295,
|
||||
"Expected decoded value to match UInt32"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 1,
|
||||
"Expected index to increment after decoding UInt32"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeUInt64() {
|
||||
XCTAssertThrowsError(try container().decode(UInt64.self)) {
|
||||
checkIsAtEnd($0, UInt64.self)
|
||||
}
|
||||
|
||||
let container = container(withData: .int(9_223_372_036_854_775_807))
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding UInt64"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(UInt64.self), 9_223_372_036_854_775_807,
|
||||
"Expected decoded value to match UInt64"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 1,
|
||||
"Expected index to increment after decoding UInt64"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeDate() {
|
||||
XCTAssertThrowsError(try container().decode(Date.self)) {
|
||||
checkIsAtEnd($0, Date.self)
|
||||
}
|
||||
|
||||
let date = Date(timeIntervalSince1970: 12345)
|
||||
let container = container(withData: .real(date.timeIntervalSince1970))
|
||||
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding Date"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(Date.self), date,
|
||||
"Expected decoded value to match Date"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 1,
|
||||
"Expected index to increment after decoding Date"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeRawRepresentable() {
|
||||
XCTAssertThrowsError(try container().decode(RawRepresentableEnum.self)) {
|
||||
checkIsAtEnd($0, RawRepresentableEnum.self)
|
||||
}
|
||||
|
||||
let `case` = RawRepresentableEnum.test
|
||||
let container = container(withData: .text(`case`.rawValue))
|
||||
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding RawRepresentableEnum"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(RawRepresentableEnum.self), `case`,
|
||||
"Expected decoded value to match RawRepresentableEnum"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 1,
|
||||
"Expected index to increment after decoding RawRepresentableEnum"
|
||||
)
|
||||
}
|
||||
|
||||
func testDecodeDecodable() {
|
||||
XCTAssertThrowsError(try container().decode(DecodableEnum.self)) {
|
||||
checkIsAtEnd($0, DecodableEnum.self)
|
||||
}
|
||||
|
||||
let `case` = DecodableEnum.test
|
||||
let container = container(withData: .text(`case`.rawValue))
|
||||
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 0,
|
||||
"Expected index to be 0 before decoding DecodableEnum"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try container.decode(DecodableEnum.self), `case`,
|
||||
"Expected decoded value to match DecodableEnum"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
container.currentIndex, 1,
|
||||
"Expected index to increment after decoding DecodableEnum"
|
||||
)
|
||||
}
|
||||
|
||||
func testNestedContainerKeyedBy() {
|
||||
XCTAssertThrowsError(
|
||||
try container().nestedContainer(keyedBy: RowCodingKey.self)
|
||||
) { error in
|
||||
guard case let DecodingError.typeMismatch(type, context) = error else {
|
||||
XCTFail("Expected .typeMismatch, but got: \(error).")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertTrue(
|
||||
type == KeyedDecodingContainer<RowCodingKey>.self,
|
||||
"Expected KeyedDecodingContainer type."
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
context.codingPath as? [RowCodingKey], [.init(intValue: 0)],
|
||||
"Incorrect coding path in decoding error."
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
context.debugDescription,
|
||||
"""
|
||||
Attempted to decode a nested keyed container,
|
||||
but the value cannot be represented as a keyed container.
|
||||
""",
|
||||
"Unexpected debug description in decoding error."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testNestedUnkeyedContainer() {
|
||||
XCTAssertThrowsError(
|
||||
try container().nestedUnkeyedContainer()
|
||||
) { error in
|
||||
guard case let DecodingError.typeMismatch(type, context) = error else {
|
||||
XCTFail("Expected .typeMismatch, but got: \(error).")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertTrue(
|
||||
type == UnkeyedDecodingContainer.self,
|
||||
"Expected UnkeyedDecodingContainer type."
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
context.codingPath as? [RowCodingKey], [.init(intValue: 0)],
|
||||
"Incorrect coding path in decoding error."
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
context.debugDescription,
|
||||
"""
|
||||
Attempted to decode a nested unkeyed container,
|
||||
but the value cannot be represented as an unkeyed container.
|
||||
""",
|
||||
"Unexpected debug description in decoding error."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testSuperDecoder() {
|
||||
XCTAssertThrowsError(
|
||||
try container().superDecoder()
|
||||
) { error in
|
||||
guard case let DecodingError.typeMismatch(type, context) = error else {
|
||||
XCTFail("Expected .typeMismatch, but got: \(error).")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertTrue(
|
||||
type == Swift.Decoder.self,
|
||||
"Expected Swift.Decoder type."
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
context.codingPath as? [RowCodingKey], [.init(intValue: 0)],
|
||||
"Incorrect coding path in decoding error."
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
context.debugDescription,
|
||||
"""
|
||||
Attempted to get a superDecoder,
|
||||
but SQLiteRowDecoder does not support superDecoding.
|
||||
""",
|
||||
"Unexpected debug description in decoding error."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension UnkeyedContainerTests {
|
||||
func container(
|
||||
withData data: SQLiteRawValue,
|
||||
codingPath: [any CodingKey] = []
|
||||
) -> UnkeyedContainer<MockKeyedDecoder> {
|
||||
var row = SQLiteRow()
|
||||
row["key"] = data
|
||||
return container(
|
||||
withData: row,
|
||||
codingPath: codingPath
|
||||
)
|
||||
}
|
||||
|
||||
func container(
|
||||
withData data: SQLiteRow = .init(),
|
||||
codingPath: [any CodingKey] = []
|
||||
) -> UnkeyedContainer<MockKeyedDecoder> {
|
||||
UnkeyedContainer(
|
||||
decoder: MockKeyedDecoder(sqliteData: data),
|
||||
codingPath: codingPath
|
||||
)
|
||||
}
|
||||
|
||||
func checkIsAtEnd(_ error: any Error, _ expectedType: Any.Type) {
|
||||
guard case let DecodingError.valueNotFound(type, context) = error else {
|
||||
XCTFail("Expected .valueNotFound, got: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertTrue(
|
||||
type == expectedType,
|
||||
"Expected type: \(expectedType), got: \(type)"
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
context.codingPath as? [RowCodingKey],
|
||||
[RowCodingKey(intValue: 0)],
|
||||
"Invalid codingPath: \(context.codingPath)"
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
context.debugDescription,
|
||||
"Unkeyed container is at end.",
|
||||
"Invalid debugDescription: \(context.debugDescription)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private extension UnkeyedContainerTests {
|
||||
enum RawRepresentableEnum: String, Decodable, SQLiteRawRepresentable {
|
||||
case test
|
||||
}
|
||||
|
||||
enum DecodableEnum: String, Decodable {
|
||||
case test
|
||||
}
|
||||
|
||||
final class MockDateDecoder: DateDecoder {
|
||||
func decode(
|
||||
from decoder: any ValueDecoder
|
||||
) throws -> Date {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(
|
||||
from decoder: any RowDecoder,
|
||||
for key: any CodingKey
|
||||
) throws -> Date {
|
||||
try decoder.decode(Date.self, for: key)
|
||||
}
|
||||
}
|
||||
|
||||
final class MockKeyedDecoder: RowDecoder {
|
||||
typealias SQLiteData = SQLiteRow
|
||||
|
||||
let sqliteData: SQLiteData
|
||||
let dateDecoder: DateDecoder
|
||||
let codingPath: [any CodingKey]
|
||||
let userInfo: [CodingUserInfoKey: Any]
|
||||
|
||||
var count: Int? {
|
||||
sqliteData.isEmpty ? nil : sqliteData.count
|
||||
}
|
||||
|
||||
init(
|
||||
sqliteData: SQLiteData,
|
||||
dateDecoder: DateDecoder = MockDateDecoder(),
|
||||
codingPath: [any CodingKey] = [],
|
||||
userInfo: [CodingUserInfoKey: Any] = [:]
|
||||
) {
|
||||
self.sqliteData = sqliteData
|
||||
self.dateDecoder = dateDecoder
|
||||
self.codingPath = codingPath
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
|
||||
func contains(_ key: any CodingKey) -> Bool {
|
||||
sqliteData.contains(key.stringValue)
|
||||
}
|
||||
|
||||
func decodeNil(
|
||||
for key: any CodingKey
|
||||
) throws -> Bool {
|
||||
sqliteData[key.intValue!].value == .null
|
||||
}
|
||||
|
||||
func decodeDate(for key: any CodingKey) throws -> Date {
|
||||
try dateDecoder.decode(from: self, for: key)
|
||||
}
|
||||
|
||||
func decode<T: SQLiteRawRepresentable>(
|
||||
_ type: T.Type,
|
||||
for key: any CodingKey
|
||||
) throws -> T {
|
||||
type.init(sqliteData[key.intValue!].value)!
|
||||
}
|
||||
|
||||
func decoder(for key: any CodingKey) -> any Decoder {
|
||||
MockValueDecoder(sqliteData: sqliteData[key.intValue!].value)
|
||||
}
|
||||
|
||||
func container<Key: CodingKey>(
|
||||
keyedBy type: Key.Type
|
||||
) throws -> KeyedDecodingContainer<Key> {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func unkeyedContainer() throws -> any UnkeyedDecodingContainer {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func singleValueContainer() throws -> any SingleValueDecodingContainer {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
final class MockValueDecoder: ValueDecoder, SingleValueDecodingContainer {
|
||||
typealias SQLiteData = SQLiteRawValue
|
||||
|
||||
let sqliteData: SQLiteData
|
||||
var dateDecoder: DateDecoder
|
||||
var codingPath: [any CodingKey]
|
||||
var userInfo: [CodingUserInfoKey: Any]
|
||||
|
||||
init(
|
||||
sqliteData: SQLiteData,
|
||||
dateDecoder: DateDecoder = MockDateDecoder(),
|
||||
codingPath: [any CodingKey] = [],
|
||||
userInfo: [CodingUserInfoKey: Any] = [:]
|
||||
) {
|
||||
self.sqliteData = sqliteData
|
||||
self.dateDecoder = dateDecoder
|
||||
self.codingPath = codingPath
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
|
||||
func decodeNil() -> Bool {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decodeDate() throws -> Date {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode<T: SQLiteRawRepresentable>(
|
||||
_ type: T.Type
|
||||
) throws -> T {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func container<Key: CodingKey>(
|
||||
keyedBy type: Key.Type
|
||||
) throws -> KeyedDecodingContainer<Key> {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func unkeyedContainer() throws -> any UnkeyedDecodingContainer {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func singleValueContainer() throws -> any SingleValueDecodingContainer {
|
||||
self
|
||||
}
|
||||
|
||||
func decode(_ type: Bool.Type) throws -> Bool {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: String.Type) throws -> String {
|
||||
type.init(sqliteData)!
|
||||
}
|
||||
|
||||
func decode(_ type: Double.Type) throws -> Double {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Float.Type) throws -> Float {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Int.Type) throws -> Int {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Int8.Type) throws -> Int8 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Int16.Type) throws -> Int16 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Int32.Type) throws -> Int32 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: Int64.Type) throws -> Int64 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: UInt.Type) throws -> UInt {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: UInt8.Type) throws -> UInt8 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: UInt16.Type) throws -> UInt16 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: UInt32.Type) throws -> UInt32 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode(_ type: UInt64.Type) throws -> UInt64 {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func decode<T: Decodable>(_ type: T.Type) throws -> T {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user