DataLireCoder swift package

This commit is contained in:
2025-04-27 12:53:43 +03:00
parent 2cca986016
commit 5aec6ea578
60 changed files with 7144 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
import XCTest
import DataLiteCore
import DLCCommon
final class SQLiteRowTests: XCTestCase {
func testContainsKey() {
var row = SQLiteRow()
row["key1"] = .text("value1")
row["key2"] = .int(42)
XCTAssertTrue(row.contains(DummyKey(stringValue: "key1")))
XCTAssertFalse(row.contains(DummyKey(stringValue: "key3")))
XCTAssertTrue(row.contains(DummyKey(intValue: 0)))
XCTAssertFalse(row.contains(DummyKey(intValue: 2)))
}
func testSubscriptWithKey() {
var row = SQLiteRow()
row["key1"] = .text("value")
XCTAssertEqual(row[DummyKey(stringValue: "key1")], .text("value"))
XCTAssertNil(row[DummyKey(stringValue: "key2")])
XCTAssertEqual(row[DummyKey(intValue: 0)], .text("value"))
row[DummyKey(stringValue: "key1")] = .int(42)
XCTAssertEqual(row[DummyKey(stringValue: "key1")], .int(42))
row[DummyKey(intValue: 0)] = .real(3.14)
XCTAssertEqual(row[DummyKey(intValue: 0)], .real(3.14))
}
}
private extension SQLiteRowTests {
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
}
}
}

View File

@@ -0,0 +1,18 @@
import XCTest
import DLCCommon
final class RowCodingKeyTests: XCTestCase {
func testInitWithStringValue() {
let key = RowCodingKey(stringValue: "testKey")
XCTAssertEqual(key.stringValue, "testKey", "String value does not match the expected value.")
XCTAssertNil(key.intValue, "Integer value should be nil when initialized with a string.")
}
func testInitWithIntValue() {
let key = RowCodingKey(intValue: 5)
XCTAssertEqual(key.stringValue, "Index 5", "String value does not match the expected format.")
XCTAssertEqual(key.intValue, 5, "Integer value does not match the expected value.")
}
}

View 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()
}
}
}

View 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()
}
}
}

View 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
}
}
}

View 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()
}
}
}

View 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()
}
}
}

View 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()
}
}
}

View File

@@ -0,0 +1,29 @@
import XCTest
import DLCCommon
@testable import DLCEncoder
final class FailedEncoderTests: XCTestCase {
func testKeyedContainer() {
let path = [RowCodingKey(intValue: 1), RowCodingKey(intValue: 2)]
let encoder = FailedEncoder(codingPath: path)
let container = encoder.container(keyedBy: RowCodingKey.self)
XCTAssertEqual(container.codingPath as? [RowCodingKey], path)
}
func testUnkeyedContainer() {
let path = [RowCodingKey(intValue: 1), RowCodingKey(intValue: 2)]
let encoder = FailedEncoder(codingPath: path)
let container = encoder.unkeyedContainer()
XCTAssertTrue(container is FailedEncodingContainer<RowCodingKey>)
XCTAssertEqual(container.codingPath as? [RowCodingKey], path)
}
func testSingleValueContainer() {
let path = [RowCodingKey(intValue: 1), RowCodingKey(intValue: 2)]
let encoder = FailedEncoder(codingPath: path)
let container = encoder.singleValueContainer()
XCTAssertTrue(container is FailedEncodingContainer<RowCodingKey>)
XCTAssertEqual(container.codingPath as? [RowCodingKey], path)
}
}

View File

@@ -0,0 +1,153 @@
import XCTest
import DLCCommon
@testable import DLCEncoder
final class FailedEncodingContainerTests: XCTestCase {
func testEncodeNil() {
let path = [RowCodingKey(intValue: 1)]
let container = FailedEncodingContainer<RowCodingKey>(
codingPath: path
)
XCTAssertThrowsError(
try container.encodeNil()
) { error in
guard case let EncodingError.invalidValue(_, context) = error else {
return XCTFail()
}
XCTAssertEqual(context.codingPath as? [RowCodingKey], path)
XCTAssertEqual(
context.debugDescription,
"encodeNil() is not supported for this encoding path."
)
}
}
func testEncodeNilForKey() {
let path = [RowCodingKey(intValue: 1)]
let key = RowCodingKey(intValue: 2)
let container = FailedEncodingContainer<RowCodingKey>(
codingPath: path
)
XCTAssertThrowsError(
try container.encodeNil(forKey: key)
) { error in
guard case let EncodingError.invalidValue(_, context) = error else {
return XCTFail()
}
XCTAssertEqual(context.codingPath as? [RowCodingKey], path + [key])
XCTAssertEqual(
context.debugDescription,
"encodeNil(forKey:) is not supported for this encoding path."
)
}
}
func testEncodeValue() {
let path = [RowCodingKey(intValue: 1)]
let container = FailedEncodingContainer<RowCodingKey>(
codingPath: path
)
XCTAssertThrowsError(
try container.encode(123)
) { error in
guard case let EncodingError.invalidValue(_, context) = error else {
return XCTFail()
}
XCTAssertEqual(context.codingPath as? [RowCodingKey], path)
XCTAssertEqual(
context.debugDescription,
"encode(_:) is not supported for this encoding path."
)
}
}
func testEncodeValueForKey() {
let path = [RowCodingKey(intValue: 1)]
let key = RowCodingKey(intValue: 2)
let container = FailedEncodingContainer<RowCodingKey>(
codingPath: path
)
XCTAssertThrowsError(
try container.encode(123, forKey: key)
) { error in
guard case let EncodingError.invalidValue(_, context) = error else {
return XCTFail()
}
XCTAssertEqual(context.codingPath as? [RowCodingKey], path + [key])
XCTAssertEqual(
context.debugDescription,
"encode(_:forKey:) is not supported for this encoding path."
)
}
}
func testNestedKeyedContainer() {
let path = [RowCodingKey(intValue: 1)]
let container = FailedEncodingContainer<RowCodingKey>(
codingPath: path
)
let nestedContainer = container.nestedContainer(
keyedBy: RowCodingKey.self
)
XCTAssertEqual(nestedContainer.codingPath as? [RowCodingKey], path)
}
func testNestedKeyedContainerForKey() {
let path = [RowCodingKey(intValue: 1)]
let key = RowCodingKey(intValue: 2)
let container = FailedEncodingContainer<RowCodingKey>(
codingPath: path
)
let nestedContainer = container.nestedContainer(
keyedBy: RowCodingKey.self, forKey: key
)
XCTAssertEqual(nestedContainer.codingPath as? [RowCodingKey], path + [key])
}
func testNestedUnkeyedContainer() {
let path = [RowCodingKey(intValue: 1)]
let container = FailedEncodingContainer<RowCodingKey>(
codingPath: path
)
let nestedContainer = container.nestedUnkeyedContainer()
XCTAssertTrue(nestedContainer is FailedEncodingContainer<RowCodingKey>)
XCTAssertEqual(nestedContainer.codingPath as? [RowCodingKey], path)
}
func testNestedUnkeyedContainerForKey() {
let path = [RowCodingKey(intValue: 1)]
let key = RowCodingKey(intValue: 2)
let container = FailedEncodingContainer<RowCodingKey>(
codingPath: path
)
let nestedContainer = container.nestedUnkeyedContainer(forKey: key)
XCTAssertTrue(nestedContainer is FailedEncodingContainer<RowCodingKey>)
XCTAssertEqual(nestedContainer.codingPath as? [RowCodingKey], path + [key])
}
func testSuperEncoder() {
let path = [RowCodingKey(intValue: 1)]
let container = FailedEncodingContainer<RowCodingKey>(
codingPath: path
)
let encoder = container.superEncoder()
XCTAssertTrue(encoder is FailedEncoder)
XCTAssertEqual(encoder.codingPath as? [RowCodingKey], path)
}
func testSuperEncoderForKey() {
let path = [RowCodingKey(intValue: 1)]
let key = RowCodingKey(intValue: 2)
let container = FailedEncodingContainer<RowCodingKey>(
codingPath: path
)
let encoder = container.superEncoder(forKey: key)
XCTAssertTrue(encoder is FailedEncoder)
XCTAssertEqual(encoder.codingPath as? [RowCodingKey], path + [key])
}
}

View File

@@ -0,0 +1,505 @@
import XCTest
import DataLiteCore
import DLCCommon
@testable import DLCEncoder
final class KeyedContainerTests: XCTestCase {
func testEncodeNil() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeNil(forKey: .key1)
XCTAssertEqual(encoder.sqliteData.count, 1)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .null)
}
func testEncodeBool() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent(true, forKey: .key1)
try container.encodeIfPresent(false, forKey: .key2)
try container.encodeIfPresent(nil as Bool?, forKey: .key3)
XCTAssertEqual(encoder.sqliteData.count, 3)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .int(1))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .int(0))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key3.stringValue], .null)
}
func testEncodeString() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent("test", forKey: .key1)
try container.encodeIfPresent(nil as String?, forKey: .key2)
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .text("test"))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .null)
}
func testEncodeDouble() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent(Double(3.14), forKey: .key1)
try container.encodeIfPresent(nil as Double?, forKey: .key2)
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .real(3.14))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .null)
}
func testEncodeFloat() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent(Float(3.14), forKey: .key1)
try container.encodeIfPresent(nil as Float?, forKey: .key2)
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .real(Double(Float(3.14))))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .null)
}
func testEncodeInt() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent(Int(42), forKey: .key1)
try container.encodeIfPresent(nil as Int?, forKey: .key2)
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .int(42))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .null)
}
func testEncodeInt8() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent(Int8(8), forKey: .key1)
try container.encodeIfPresent(nil as Int8?, forKey: .key2)
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .int(Int64(8)))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .null)
}
func testEncodeInt16() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent(Int16(16), forKey: .key1)
try container.encodeIfPresent(nil as Int16?, forKey: .key2)
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .int(Int64(16)))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .null)
}
func testEncodeInt32() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent(Int32(32), forKey: .key1)
try container.encodeIfPresent(nil as Int32?, forKey: .key2)
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .int(Int64(32)))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .null)
}
func testEncodeInt64() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent(Int64(64), forKey: .key1)
try container.encodeIfPresent(nil as Int64?, forKey: .key2)
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .int(64))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .null)
}
func testEncodeUInt() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent(UInt(42), forKey: .key1)
try container.encodeIfPresent(nil as UInt?, forKey: .key2)
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .int(42))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .null)
}
func testEncodeUInt8() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent(UInt8(8), forKey: .key1)
try container.encodeIfPresent(nil as UInt8?, forKey: .key2)
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .int(8))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .null)
}
func testEncodeUInt16() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent(UInt16(16), forKey: .key1)
try container.encodeIfPresent(nil as UInt16?, forKey: .key2)
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .int(16))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .null)
}
func testEncodeUInt32() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent(UInt32(32), forKey: .key1)
try container.encodeIfPresent(nil as UInt32?, forKey: .key2)
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .int(32))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .null)
}
func testEncodeUInt64() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent(UInt64(64), forKey: .key1)
try container.encodeIfPresent(nil as UInt64?, forKey: .key2)
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .int(64))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .null)
}
func testEncodeDate() throws {
let date = Date()
let dateString = ISO8601DateFormatter().string(from: date)
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent(date, forKey: .key1)
try container.encodeIfPresent(nil as Date?, forKey: .key2)
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .text(dateString))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .null)
}
func testEncodeRawRepresentable() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent(RawRepresentableModel.test, forKey: .key1)
try container.encodeIfPresent(nil as RawRepresentableModel?, forKey: .key2)
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .text(RawRepresentableModel.test.rawValue))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .null)
}
func testEncodeEncodable() throws {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: []
)
)
try container.encodeIfPresent(EncodableModel.test, forKey: .key1)
try container.encodeIfPresent(nil as EncodableModel?, forKey: .key2)
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1.stringValue], .text(EncodableModel.test.rawValue))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2.stringValue], .null)
}
func testNestedKeyedContainer() {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: [CodingKeys.key1]
)
)
let nestedContainer = container.nestedContainer(
keyedBy: CodingKeys.self, forKey: .key3
)
XCTAssertEqual(nestedContainer.codingPath as? [CodingKeys], [.key1, .key3])
}
func testNestedUnkeyedContainer() {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: [CodingKeys.key1]
)
)
let nestedContainer = container.nestedUnkeyedContainer(forKey: .key3)
XCTAssertTrue(nestedContainer is FailedEncodingContainer<RowCodingKey>)
XCTAssertEqual(nestedContainer.codingPath as? [CodingKeys], [.key1, .key3])
}
func testSuperEncoder() {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: [CodingKeys.key1]
)
)
let superEncoder = container.superEncoder()
XCTAssertTrue(superEncoder is FailedEncoder)
XCTAssertEqual(superEncoder.codingPath as? [CodingKeys], [.key1])
}
func testSuperEncoderForKey() {
let encoder = MockSingleRowEncoder()
var container = KeyedEncodingContainer(
KeyedContainer<MockSingleRowEncoder, CodingKeys>(
encoder: encoder, codingPath: [CodingKeys.key1]
)
)
let superEncoder = container.superEncoder(forKey: .key3)
XCTAssertTrue(superEncoder is FailedEncoder)
XCTAssertEqual(superEncoder.codingPath as? [CodingKeys], [.key1, .key3])
}
}
private extension KeyedContainerTests {
enum CodingKeys: CodingKey {
case key1
case key2
case key3
}
enum RawRepresentableModel: String, Encodable, SQLiteRawRepresentable {
case test
}
enum EncodableModel: String, Encodable {
case test
}
final class MockDateEncoder: DateEncoder {
func encode(_ date: Date, to encoder: any ValueEncoder) throws {
fatalError()
}
func encode(_ date: Date, for key: any CodingKey, to encoder: any RowEncoder) throws {
let formatter = ISO8601DateFormatter()
let dateString = formatter.string(from: date)
try encoder.encode(dateString, for: key)
}
}
final class MockSingleRowEncoder: RowEncoder {
private(set) var sqliteData: SQLiteRow
let dateEncoder: any DateEncoder
let codingPath: [any CodingKey]
let userInfo: [CodingUserInfoKey: Any]
var count: Int { sqliteData.count }
init(
sqliteData: SQLiteRow = SQLiteRow(),
dateEncoder: any DateEncoder = MockDateEncoder(),
codingPath: [any CodingKey] = [],
userInfo: [CodingUserInfoKey: Any] = [:]
) {
self.sqliteData = sqliteData
self.dateEncoder = dateEncoder
self.codingPath = codingPath
self.userInfo = userInfo
}
func set(_ value: Any, for key: any CodingKey) throws {
guard let value = value as? SQLiteRawValue else {
fatalError()
}
sqliteData[key.stringValue] = value
}
func encodeNil(for key: any CodingKey) throws {
sqliteData[key.stringValue] = .null
}
func encodeDate(_ date: Date, for key: any CodingKey) throws {
try dateEncoder.encode(date, for: key, to: self)
}
func encode<T: SQLiteRawBindable>(_ value: T, for key: any CodingKey) throws {
sqliteData[key.stringValue] = value.sqliteRawValue
}
func encoder(for key: any CodingKey) throws -> any Encoder {
MockSingleValueEncoder()
}
func container<Key: CodingKey>(
keyedBy type: Key.Type
) -> KeyedEncodingContainer<Key> {
fatalError()
}
func unkeyedContainer() -> any UnkeyedEncodingContainer {
fatalError()
}
func singleValueContainer() -> any SingleValueEncodingContainer {
fatalError()
}
}
final class MockSingleValueEncoder: ValueEncoder {
private(set) var sqliteData: SQLiteRawValue?
let dateEncoder: any DateEncoder
let codingPath: [any CodingKey]
let userInfo: [CodingUserInfoKey: Any]
init(
sqliteData: SQLiteRawValue? = nil,
dateEncoder: any DateEncoder = MockDateEncoder(),
codingPath: [any CodingKey] = [],
userInfo: [CodingUserInfoKey: Any] = [:]
) {
self.sqliteData = sqliteData
self.dateEncoder = dateEncoder
self.codingPath = codingPath
self.userInfo = userInfo
}
func encodeNil() throws {
sqliteData = .null
}
func encodeDate(_ date: Date) throws {
try dateEncoder.encode(date, to: self)
}
func encode<T: SQLiteRawBindable>(_ value: T) throws {
sqliteData = value.sqliteRawValue
}
func container<Key: CodingKey>(
keyedBy type: Key.Type
) -> KeyedEncodingContainer<Key> {
fatalError()
}
func unkeyedContainer() -> any UnkeyedEncodingContainer {
fatalError()
}
func singleValueContainer() -> any SingleValueEncodingContainer {
MockSingleValueContainer(encoder: self, codingPath: [])
}
}
final class MockSingleValueContainer<Encoder: ValueEncoder>: Container, SingleValueEncodingContainer {
let encoder: Encoder
let codingPath: [any CodingKey]
init(
encoder: Encoder,
codingPath: [any CodingKey]
) {
self.encoder = encoder
self.codingPath = codingPath
}
func encodeNil() throws {
try encoder.encodeNil()
}
func encode<T: Encodable>(_ value: T) throws {
switch value {
case let value as Date:
try encoder.encodeDate(value)
case let value as SQLiteRawRepresentable:
try encoder.encode(value)
default:
try value.encode(to: encoder)
}
}
}
}

View File

@@ -0,0 +1,174 @@
import XCTest
import DataLiteCore
import DLCCommon
@testable import DLCEncoder
final class MultiRowEncoderTests: XCTestCase {
func testSetValueForKey() throws {
let encoder = MultiRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: [],
userInfo: [:]
)
try encoder.set(SQLiteRow(), for: RowCodingKey(intValue: 0))
try encoder.set(SQLiteRow(), for: RowCodingKey(intValue: 1))
XCTAssertEqual(encoder.sqliteData.count, 2)
XCTAssertEqual(encoder.count, encoder.sqliteData.count)
}
func testSetInvalidValueForKey() {
let value = "Test Value"
let path = [RowCodingKey(intValue: 0)]
let key = RowCodingKey(intValue: 1)
let encoder = MultiRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: path,
userInfo: [:]
)
XCTAssertThrowsError(
try encoder.set(value, for: key)
) { error in
guard case let EncodingError.invalidValue(thrownValue, context) = error else {
return XCTFail("Expected EncodingError.invalidValue, got \(error)")
}
XCTAssertEqual(thrownValue as? String, value)
XCTAssertEqual(context.codingPath as? [RowCodingKey], path + [key])
XCTAssertEqual(context.debugDescription, "Expected value of type SQLiteRow")
}
}
func testEncodeNilThrows() {
let key = RowCodingKey(intValue: 1)
let path = [RowCodingKey(intValue: 0)]
let encoder = MultiRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: path,
userInfo: [:]
)
XCTAssertThrowsError(try encoder.encodeNil(for: key)) { error in
guard case let EncodingError.invalidValue(_, context) = error else {
return XCTFail("Expected EncodingError.invalidValue, got \(error)")
}
XCTAssertEqual(context.codingPath as? [RowCodingKey], path + [key])
XCTAssertEqual(context.debugDescription, "Attempted to encode nil, but it's not supported.")
}
}
func testEncodeDateThrows() {
let key = RowCodingKey(intValue: 2)
let path = [RowCodingKey(intValue: 0)]
let encoder = MultiRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: path,
userInfo: [:]
)
let date = Date()
XCTAssertThrowsError(try encoder.encodeDate(date, for: key)) { error in
guard case let EncodingError.invalidValue(thrownValue, context) = error else {
return XCTFail("Expected EncodingError.invalidValue, got \(error)")
}
XCTAssertEqual(thrownValue as? Date, date)
XCTAssertEqual(context.codingPath as? [RowCodingKey], path + [key])
XCTAssertEqual(context.debugDescription, "Attempted to encode Date, but it's not supported.")
}
}
func testEncodeRawBindableThrows() {
let value = "Test Value"
let path = [RowCodingKey(intValue: 0)]
let key = RowCodingKey(intValue: 1)
let encoder = MultiRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: path,
userInfo: [:]
)
XCTAssertThrowsError(try encoder.encode(value, for: key)) { error in
guard case let EncodingError.invalidValue(thrownValue, context) = error else {
return XCTFail("Expected EncodingError.invalidValue, got \(error)")
}
XCTAssertEqual(thrownValue as? String, value)
XCTAssertEqual(context.codingPath as? [RowCodingKey], path + [key])
XCTAssertEqual(
context.debugDescription,
"Attempted to encode \(type(of: value)), but it's not supported."
)
}
}
func testEncoderForKey() throws {
let path = [RowCodingKey(intValue: 0)]
let key = RowCodingKey(intValue: 1)
let encoder = MultiRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: path,
userInfo: [:]
)
let nestedEncoder = try encoder.encoder(for: key)
XCTAssertTrue(nestedEncoder is SingleRowEncoder)
XCTAssertEqual(nestedEncoder.codingPath as? [RowCodingKey], path + [key])
}
func testKeyedContainer() {
let path = [RowCodingKey(intValue: 0)]
let encoder = MultiRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: path,
userInfo: [:]
)
let container = encoder.container(keyedBy: RowCodingKey.self)
XCTAssertEqual(container.codingPath as? [RowCodingKey], path)
}
func testUnkeyedContainer() {
let path = [RowCodingKey(intValue: 0)]
let encoder = MultiRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: path,
userInfo: [:]
)
let container = encoder.unkeyedContainer()
XCTAssertTrue(container is UnkeyedContainer<MultiRowEncoder>)
XCTAssertEqual(container.codingPath as? [RowCodingKey], path)
}
func testSingleValueContainer() {
let path = [RowCodingKey(intValue: 0)]
let encoder = MultiRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: path,
userInfo: [:]
)
let container = encoder.singleValueContainer()
XCTAssertTrue(container is FailedEncodingContainer<RowCodingKey>)
XCTAssertEqual(container.codingPath as? [RowCodingKey], path)
}
}
private extension MultiRowEncoderTests {
final class MockDateEncoder: DateEncoder {
private(set) var didCallEncode = false
func encode(
_ date: Date,
to encoder: any ValueEncoder
) throws {
fatalError()
}
func encode(
_ date: Date,
for key: any CodingKey,
to encoder: any RowEncoder
) throws {
fatalError()
}
}
}

View File

@@ -0,0 +1,164 @@
import XCTest
import DataLiteCore
import DLCCommon
@testable import DLCEncoder
final class SingleRowEncoderTests: XCTestCase {
func testSetValueForKey() throws {
let encoder = SingleRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: [],
userInfo: [:]
)
try encoder.set(SQLiteRawValue.int(42), for: CodingKeys.key1)
try encoder.set(SQLiteRawValue.real(3.14), for: CodingKeys.key2)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1], .int(42))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2], .real(3.14))
XCTAssertEqual(encoder.count, encoder.sqliteData.count)
}
func testSetInvalidValueForKey() {
let value = "Test Value"
let path = [CodingKeys.key1]
let encoder = SingleRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: path,
userInfo: [:]
)
XCTAssertThrowsError(
try encoder.set(value, for: CodingKeys.key2)
) { error in
guard case let EncodingError.invalidValue(thrownValue, context) = error else {
return XCTFail("Expected EncodingError.invalidValue, got \(error)")
}
XCTAssertEqual(thrownValue as? String, value)
XCTAssertEqual(context.codingPath as? [CodingKeys], path + [.key2])
XCTAssertEqual(context.debugDescription, "The value does not match SQLiteRawValue")
}
}
func testEncodeNilForKey() throws {
let encoder = SingleRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: [],
userInfo: [:]
)
try encoder.encodeNil(for: CodingKeys.key1)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1], .null)
XCTAssertEqual(encoder.count, encoder.sqliteData.count)
}
func testEncodeDateForKey() throws {
let date = Date()
let mockDateEncoder = MockDateEncoder()
let encoder = SingleRowEncoder(
dateEncoder: mockDateEncoder,
codingPath: [],
userInfo: [:]
)
try encoder.encodeDate(date, for: CodingKeys.key1)
XCTAssertTrue(mockDateEncoder.didCallEncode)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1], date.sqliteRawValue)
XCTAssertEqual(encoder.count, encoder.sqliteData.count)
}
func testEncodeValueForKey() throws {
let encoder = SingleRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: [],
userInfo: [:]
)
try encoder.encode(123, for: CodingKeys.key1)
try encoder.encode(3.14, for: CodingKeys.key2)
try encoder.encode("Hello", for: CodingKeys.key3)
XCTAssertEqual(encoder.sqliteData[CodingKeys.key1], .int(123))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key2], .real(3.14))
XCTAssertEqual(encoder.sqliteData[CodingKeys.key3], .text("Hello"))
XCTAssertEqual(encoder.count, encoder.sqliteData.count)
}
func testEncoderForKey() throws {
let path = [CodingKeys.key1]
let encoder = SingleRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: path,
userInfo: [:]
)
let key = CodingKeys.key2
let nestedEncoder = try encoder.encoder(for: key)
XCTAssertTrue(nestedEncoder is SingleValueEncoder)
XCTAssertEqual(nestedEncoder.codingPath as? [CodingKeys], path + [key])
}
func testKeyedContainer() {
let path = [CodingKeys.key1]
let encoder = SingleRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: path,
userInfo: [:]
)
let container = encoder.container(keyedBy: CodingKeys.self)
XCTAssertEqual(container.codingPath as? [CodingKeys], path)
}
func testUnkeyedContainer() {
let path = [CodingKeys.key1]
let encoder = SingleRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: path,
userInfo: [:]
)
let container = encoder.unkeyedContainer()
XCTAssertTrue(container is FailedEncodingContainer<RowCodingKey>)
XCTAssertEqual(container.codingPath as? [CodingKeys], path)
}
func testSingleValueContainer() {
let path = [CodingKeys.key1]
let encoder = SingleRowEncoder(
dateEncoder: MockDateEncoder(),
codingPath: path,
userInfo: [:]
)
let container = encoder.singleValueContainer()
XCTAssertTrue(container is FailedEncodingContainer<RowCodingKey>)
XCTAssertEqual(container.codingPath as? [CodingKeys], path)
}
}
private extension SingleRowEncoderTests {
enum CodingKeys: CodingKey {
case key1
case key2
case key3
}
final class MockDateEncoder: DateEncoder {
private(set) var didCallEncode = false
func encode(
_ date: Date,
to encoder: any ValueEncoder
) throws {
fatalError()
}
func encode(
_ date: Date,
for key: any CodingKey,
to encoder: any RowEncoder
) throws {
didCallEncode = true
try encoder.encode(date, for: key)
}
}
}

View File

@@ -0,0 +1,205 @@
import XCTest
import DataLiteCore
@testable import DLCEncoder
final class SingleValueContainerTests: XCTestCase {
func testEncodeNil() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encodeNil()
XCTAssertEqual(encoder.sqliteData, .null)
}
func testEncodeBool() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode(true)
XCTAssertEqual(encoder.sqliteData, .int(1))
try container.encode(false)
XCTAssertEqual(encoder.sqliteData, .int(0))
}
func testEncodeString() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode("test string")
XCTAssertEqual(encoder.sqliteData, .text("test string"))
}
func testEncodeDouble() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode(Double(3.14))
XCTAssertEqual(encoder.sqliteData, .real(3.14))
}
func testEncodeFloat() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode(Float(3.14))
XCTAssertEqual(encoder.sqliteData, .real(Double(Float(3.14))))
}
func testEncodeInt() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode(Int(42))
XCTAssertEqual(encoder.sqliteData, .int(42))
}
func testEncodeInt8() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode(Int8(8))
XCTAssertEqual(encoder.sqliteData, .int(Int64(8)))
}
func testEncodeInt16() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode(Int16(16))
XCTAssertEqual(encoder.sqliteData, .int(Int64(16)))
}
func testEncodeInt32() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode(Int32(32))
XCTAssertEqual(encoder.sqliteData, .int(Int64(32)))
}
func testEncodeInt64() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode(Int64(64))
XCTAssertEqual(encoder.sqliteData, .int(64))
}
func testEncodeUInt() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode(UInt(42))
XCTAssertEqual(encoder.sqliteData, .int(42))
}
func testEncodeUInt8() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode(UInt8(8))
XCTAssertEqual(encoder.sqliteData, .int(8))
}
func testEncodeUInt16() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode(UInt16(16))
XCTAssertEqual(encoder.sqliteData, .int(16))
}
func testEncodeUInt32() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode(UInt32(32))
XCTAssertEqual(encoder.sqliteData, .int(32))
}
func testEncodeUInt64() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode(UInt64(64))
XCTAssertEqual(encoder.sqliteData, .int(64))
}
func testEncodeDate() throws {
let date = Date()
let dateString = ISO8601DateFormatter().string(from: date)
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode(date)
XCTAssertEqual(encoder.sqliteData, .text(dateString))
}
func testEncodeRawRepresentable() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode(RawRepresentableModel.test)
XCTAssertEqual(encoder.sqliteData, .text(RawRepresentableModel.test.rawValue))
}
func testEncodeEncodable() throws {
let encoder = MockSingleValueEncoder()
let container = SingleValueContainer(encoder: encoder, codingPath: [])
try container.encode(EncodableModel.test)
XCTAssertEqual(encoder.sqliteData, .text(EncodableModel.test.rawValue))
}
}
private extension SingleValueContainerTests {
enum RawRepresentableModel: String, Encodable, SQLiteRawRepresentable {
case test
}
enum EncodableModel: String, Encodable {
case test
}
final class MockDateEncoder: DateEncoder {
func encode(_ date: Date, to encoder: any ValueEncoder) throws {
let formatter = ISO8601DateFormatter()
let dateString = formatter.string(from: date)
try encoder.encode(dateString)
}
func encode(_ date: Date, for key: any CodingKey, to encoder: any RowEncoder) throws {
fatalError()
}
}
final class MockSingleValueEncoder: ValueEncoder {
private(set) var sqliteData: SQLiteRawValue?
let dateEncoder: any DateEncoder
let codingPath: [any CodingKey]
let userInfo: [CodingUserInfoKey: Any]
init(
sqliteData: SQLiteRawValue? = nil,
dateEncoder: any DateEncoder = MockDateEncoder(),
codingPath: [any CodingKey] = [],
userInfo: [CodingUserInfoKey: Any] = [:]
) {
self.sqliteData = sqliteData
self.dateEncoder = dateEncoder
self.codingPath = codingPath
self.userInfo = userInfo
}
func encodeNil() throws {
sqliteData = .null
}
func encodeDate(_ date: Date) throws {
try dateEncoder.encode(date, to: self)
}
func encode<T: SQLiteRawBindable>(_ value: T) throws {
sqliteData = value.sqliteRawValue
}
func container<Key: CodingKey>(
keyedBy type: Key.Type
) -> KeyedEncodingContainer<Key> {
fatalError()
}
func unkeyedContainer() -> any UnkeyedEncodingContainer {
fatalError()
}
func singleValueContainer() -> any SingleValueEncodingContainer {
SingleValueContainer(encoder: self, codingPath: codingPath)
}
}
}

View File

@@ -0,0 +1,108 @@
import XCTest
import DataLiteCore
import DLCCommon
@testable import DLCEncoder
final class SingleValueEncoderTests: XCTestCase {
func testEncodeNil() throws {
let encoder = SingleValueEncoder(
dateEncoder: MockDateEncoder(),
codingPath: [],
userInfo: [:]
)
try encoder.encodeNil()
XCTAssertEqual(encoder.sqliteData, .null)
}
func testEncodeDate() throws {
let date = Date()
let dateEncoder = MockDateEncoder()
let encoder = SingleValueEncoder(
dateEncoder: dateEncoder,
codingPath: [],
userInfo: [:]
)
try encoder.encodeDate(date)
XCTAssertEqual(encoder.sqliteData, date.sqliteRawValue)
XCTAssertTrue(dateEncoder.didCallEncode)
}
func testEncodeValue() throws {
let encoder = SingleValueEncoder(
dateEncoder: MockDateEncoder(),
codingPath: [],
userInfo: [:]
)
try encoder.encode("Test String")
XCTAssertEqual(encoder.sqliteData, .text("Test String"))
}
func testKeyedContainer() {
let path = [
RowCodingKey(intValue: 1),
RowCodingKey(intValue: 2)
]
let encoder = SingleValueEncoder(
dateEncoder: MockDateEncoder(),
codingPath: path,
userInfo: [:]
)
let container = encoder.container(
keyedBy: RowCodingKey.self
)
XCTAssertEqual(container.codingPath as? [RowCodingKey], path)
}
func testUnkeyedContainer() {
let path = [
RowCodingKey(intValue: 1),
RowCodingKey(intValue: 2)
]
let encoder = SingleValueEncoder(
dateEncoder: MockDateEncoder(),
codingPath: path,
userInfo: [:]
)
let container = encoder.unkeyedContainer()
XCTAssertTrue(container is FailedEncodingContainer<RowCodingKey>)
XCTAssertEqual(container.codingPath as? [RowCodingKey], path)
}
func testSingleValueContainer() {
let path = [
RowCodingKey(intValue: 1),
RowCodingKey(intValue: 2)
]
let encoder = SingleValueEncoder(
dateEncoder: MockDateEncoder(),
codingPath: path,
userInfo: [:]
)
let container = encoder.singleValueContainer()
XCTAssertTrue(container is SingleValueContainer<SingleValueEncoder>)
XCTAssertEqual(container.codingPath as? [RowCodingKey], path)
}
}
private extension SingleValueEncoderTests {
final class MockDateEncoder: DateEncoder {
private(set) var didCallEncode = false
func encode(
_ date: Date,
to encoder: any ValueEncoder
) throws {
didCallEncode = true
try encoder.encode(date)
}
func encode(
_ date: Date,
for key: any CodingKey,
to encoder: any RowEncoder
) throws {
fatalError()
}
}
}

View File

@@ -0,0 +1,296 @@
import XCTest
import DataLiteCore
import DLCCommon
@testable import DLCEncoder
final class UnkeyedContainerTests: XCTestCase {
func testEncodeNil() throws {
let encoder = MockMultiRowEncoder()
let container = UnkeyedContainer<MockMultiRowEncoder>(
encoder: encoder,
codingPath: []
)
try container.encodeNil()
XCTAssertTrue(encoder.sqliteData.isEmpty)
}
func testEncodeModel() throws {
let encoder = MockMultiRowEncoder()
let container = UnkeyedContainer<MockMultiRowEncoder>(
encoder: encoder,
codingPath: []
)
try container.encode(TestModel(id: 1, name: "John"))
XCTAssertEqual(encoder.sqliteData.count, 1)
XCTAssertEqual(encoder.sqliteData.first?.count, 2)
XCTAssertEqual(encoder.sqliteData.first?["id"], .int(1))
XCTAssertEqual(encoder.sqliteData.first?["name"], .text("John"))
}
func testEncodeOptionalModel() throws {
let encoder = MockMultiRowEncoder()
let container = UnkeyedContainer<MockMultiRowEncoder>(
encoder: encoder,
codingPath: []
)
try container.encode(TestModel(id: 1, name: "John") as TestModel?)
XCTAssertEqual(encoder.sqliteData.count, 1)
XCTAssertEqual(encoder.sqliteData.first?.count, 2)
XCTAssertEqual(encoder.sqliteData.first?["id"], .int(1))
XCTAssertEqual(encoder.sqliteData.first?["name"], .text("John"))
}
func testEncodeNilModel() throws {
let encoder = MockMultiRowEncoder()
let container = UnkeyedContainer<MockMultiRowEncoder>(
encoder: encoder,
codingPath: []
)
try container.encode(nil as TestModel?)
XCTAssertTrue(encoder.sqliteData.isEmpty)
}
func testNestedKeyedContainer() {
let path = [RowCodingKey(intValue: 123)]
let encoder = MockMultiRowEncoder()
let container = UnkeyedContainer<MockMultiRowEncoder>(
encoder: encoder,
codingPath: path
)
let nestedContainer = container.nestedContainer(
keyedBy: RowCodingKey.self
)
XCTAssertEqual(nestedContainer.codingPath as? [RowCodingKey], path)
}
func testNestedUnkeyedContainer() {
let path = [RowCodingKey(intValue: 123)]
let encoder = MockMultiRowEncoder()
let container = UnkeyedContainer<MockMultiRowEncoder>(
encoder: encoder,
codingPath: path
)
let nestedContainer = container.nestedUnkeyedContainer()
XCTAssertTrue(nestedContainer is FailedEncodingContainer<RowCodingKey>)
XCTAssertEqual(nestedContainer.codingPath as? [RowCodingKey], path)
}
func testSuperEncoder() {
let path = [RowCodingKey(intValue: 123)]
let encoder = MockMultiRowEncoder()
let container = UnkeyedContainer<MockMultiRowEncoder>(
encoder: encoder,
codingPath: path
)
let superEncoder = container.superEncoder()
XCTAssertTrue(superEncoder is FailedEncoder)
XCTAssertEqual(superEncoder.codingPath as? [RowCodingKey], path)
}
}
private extension UnkeyedContainerTests {
struct TestModel: Encodable {
let id: Int
let name: String
}
final class MockDateEncoder: DateEncoder {
func encode(_ date: Date, to encoder: any ValueEncoder) throws {
fatalError()
}
func encode(_ date: Date, for key: any CodingKey, to encoder: any RowEncoder) throws {
fatalError()
}
}
final class MockMultiRowEncoder: RowEncoder {
private(set) var sqliteData: [SQLiteRow]
let dateEncoder: any DateEncoder
let codingPath: [any CodingKey]
let userInfo: [CodingUserInfoKey: Any]
var count: Int { sqliteData.count }
init(
sqliteData: [SQLiteRow] = [],
dateEncoder: any DateEncoder = MockDateEncoder(),
codingPath: [any CodingKey] = [],
userInfo: [CodingUserInfoKey: Any] = [:]
) {
self.sqliteData = sqliteData
self.dateEncoder = dateEncoder
self.codingPath = codingPath
self.userInfo = userInfo
}
func set(_ value: Any, for key: any CodingKey) throws {
guard let value = value as? SQLiteRow else {
fatalError()
}
sqliteData.append(value)
}
func encodeNil(for key: any CodingKey) throws {
fatalError()
}
func encodeDate(_ date: Date, for key: any CodingKey) throws {
fatalError()
}
func encode<T: SQLiteRawBindable>(_ value: T, for key: any CodingKey) throws {
fatalError()
}
func encoder(for key: any CodingKey) throws -> any Encoder {
MockSingleRowEncoder()
}
func container<Key: CodingKey>(
keyedBy type: Key.Type
) -> KeyedEncodingContainer<Key> {
fatalError()
}
func unkeyedContainer() -> any UnkeyedEncodingContainer {
fatalError()
}
func singleValueContainer() -> any SingleValueEncodingContainer {
fatalError()
}
}
final class MockSingleRowEncoder: RowEncoder {
private(set) var sqliteData: SQLiteRow
let dateEncoder: any DateEncoder
let codingPath: [any CodingKey]
let userInfo: [CodingUserInfoKey: Any]
var count: Int { sqliteData.count }
init(
sqliteData: SQLiteRow = SQLiteRow(),
dateEncoder: any DateEncoder = MockDateEncoder(),
codingPath: [any CodingKey] = [],
userInfo: [CodingUserInfoKey: Any] = [:]
) {
self.sqliteData = sqliteData
self.dateEncoder = dateEncoder
self.codingPath = codingPath
self.userInfo = userInfo
}
func set(_ value: Any, for key: any CodingKey) throws {
guard let value = value as? SQLiteRawValue else {
fatalError()
}
sqliteData[key.stringValue] = value
}
func encodeNil(for key: any CodingKey) throws {
sqliteData[key.stringValue] = .null
}
func encodeDate(_ date: Date, for key: any CodingKey) throws {
try dateEncoder.encode(date, for: key, to: self)
}
func encode<T: SQLiteRawBindable>(_ value: T, for key: any CodingKey) throws {
sqliteData[key.stringValue] = value.sqliteRawValue
}
func encoder(for key: any CodingKey) throws -> any Encoder {
fatalError()
}
func container<Key: CodingKey>(
keyedBy type: Key.Type
) -> KeyedEncodingContainer<Key> {
let container = MockKeyedContainer<MockSingleRowEncoder, Key>(
encoder: self, codingPath: []
)
return KeyedEncodingContainer(container)
}
func unkeyedContainer() -> any UnkeyedEncodingContainer {
fatalError()
}
func singleValueContainer() -> any SingleValueEncodingContainer {
fatalError()
}
}
final class MockKeyedContainer<Encoder: RowEncoder, Key: CodingKey>: Container, KeyedEncodingContainerProtocol {
// MARK: - Properties
let encoder: Encoder
let codingPath: [any CodingKey]
// MARK: - Inits
init(
encoder: Encoder,
codingPath: [any CodingKey]
) {
self.encoder = encoder
self.codingPath = codingPath
}
// MARK: - Container Methods
func encodeNil(forKey key: Key) throws {
try encoder.encodeNil(for: key)
}
func encode<T: Encodable>(_ value: T, forKey key: Key) throws {
switch value {
case let value as Date:
try encoder.encodeDate(value, for: key)
case let value as SQLiteRawRepresentable:
try encoder.encode(value, for: key)
default:
let valueEncoder = try encoder.encoder(for: key)
try value.encode(to: valueEncoder)
try encoder.set(valueEncoder.sqliteData, for: key)
}
}
func encodeIfPresent<T: Encodable>(_ value: T?, forKey key: Key) throws {
switch value {
case .some(let value):
try encode(value, forKey: key)
case .none:
try encodeNil(forKey: key)
}
}
func nestedContainer<NestedKey: CodingKey>(
keyedBy keyType: NestedKey.Type,
forKey key: Key
) -> KeyedEncodingContainer<NestedKey> {
fatalError()
}
func nestedUnkeyedContainer(
forKey key: Key
) -> any UnkeyedEncodingContainer {
fatalError()
}
func superEncoder() -> any Swift.Encoder {
fatalError()
}
func superEncoder(forKey key: Key) -> any Swift.Encoder {
fatalError()
}
}
}

View File

@@ -0,0 +1,241 @@
import XCTest
import DataLiteCore
import DLCDecoder
@testable import DataLiteCoder
final class DateDecoderTests: XCTestCase {
func testDeferredToDate() {
let date = Date(timeIntervalSince1970: 123456789)
let dateDecoder = RowDecoder.DateDecoder(strategy: .deferredToDate)
let singleDecoder = SingleValueDecoder(
sqliteData: .real(date.timeIntervalSince1970)
)
XCTAssertEqual(try dateDecoder.decode(from: singleDecoder), date)
var row = SQLiteRow()
row[CodingKeys.key.stringValue] = .real(date.timeIntervalSince1970)
let keyedDecoder = KeyedDecoder(sqliteData: row)
XCTAssertEqual(try dateDecoder.decode(from: keyedDecoder, for: CodingKeys.key), date)
}
func testISO8601() throws {
let dateDecoder = RowDecoder.DateDecoder(strategy: .iso8601)
let formatter = ISO8601DateFormatter()
let string = "2024-04-18T13:45:00Z"
let date = formatter.date(from: string)!
let single = SingleValueDecoder(sqliteData: .text(string))
XCTAssertEqual(try dateDecoder.decode(from: single), date)
var row = SQLiteRow()
row[CodingKeys.key.stringValue] = .text(string)
let keyed = KeyedDecoder(sqliteData: row)
XCTAssertEqual(try dateDecoder.decode(from: keyed, for: CodingKeys.key), date)
}
func testFormatted() throws {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let string = "2023-10-15 16:30:00"
let date = formatter.date(from: string)!
let dateDecoder = RowDecoder.DateDecoder(strategy: .formatted(formatter))
let single = SingleValueDecoder(sqliteData: .text(string))
XCTAssertEqual(try dateDecoder.decode(from: single), date)
var row = SQLiteRow()
row[CodingKeys.key.stringValue] = .text(string)
let keyed = KeyedDecoder(sqliteData: row)
XCTAssertEqual(try dateDecoder.decode(from: keyed, for: CodingKeys.key), date)
}
func testFormattedInvalidDate() {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let invalidString = "not a date"
let dateDecoder = RowDecoder.DateDecoder(strategy: .formatted(formatter))
let single = SingleValueDecoder(sqliteData: .text(invalidString))
XCTAssertThrowsError(try dateDecoder.decode(from: single))
var row = SQLiteRow()
row[CodingKeys.key.stringValue] = .text(invalidString)
let keyed = KeyedDecoder(sqliteData: row)
XCTAssertThrowsError(try dateDecoder.decode(from: keyed, for: CodingKeys.key))
}
func testMillisecondsSince1970Int() throws {
let millis: Int64 = 1_600_000_000_000
let date = Date(timeIntervalSince1970: Double(millis) / 1000)
let dateDecoder = RowDecoder.DateDecoder(strategy: .millisecondsSince1970Int)
let single = SingleValueDecoder(sqliteData: .int(millis))
XCTAssertEqual(try dateDecoder.decode(from: single), date)
var row = SQLiteRow()
row[CodingKeys.key.stringValue] = .int(millis)
let keyed = KeyedDecoder(sqliteData: row)
XCTAssertEqual(try dateDecoder.decode(from: keyed, for: CodingKeys.key), date)
}
func testMillisecondsSince1970Double() throws {
let millis: Double = 1_600_000_000_000.0
let date = Date(timeIntervalSince1970: millis / 1000)
let dateDecoder = RowDecoder.DateDecoder(strategy: .millisecondsSince1970Double)
let single = SingleValueDecoder(sqliteData: .real(millis))
XCTAssertEqual(try dateDecoder.decode(from: single), date)
var row = SQLiteRow()
row[CodingKeys.key.stringValue] = .real(millis)
let keyed = KeyedDecoder(sqliteData: row)
XCTAssertEqual(try dateDecoder.decode(from: keyed, for: CodingKeys.key), date)
}
func testSecondsSince1970Int() throws {
let seconds: Int64 = 1_600_000_000
let date = Date(timeIntervalSince1970: Double(seconds))
let dateDecoder = RowDecoder.DateDecoder(strategy: .secondsSince1970Int)
let single = SingleValueDecoder(sqliteData: .int(seconds))
XCTAssertEqual(try dateDecoder.decode(from: single), date)
var row = SQLiteRow()
row[CodingKeys.key.stringValue] = .int(seconds)
let keyed = KeyedDecoder(sqliteData: row)
XCTAssertEqual(try dateDecoder.decode(from: keyed, for: CodingKeys.key), date)
}
func testSecondsSince1970Double() throws {
let seconds: Double = 1_600_000_000.789
let date = Date(timeIntervalSince1970: seconds)
let dateDecoder = RowDecoder.DateDecoder(strategy: .secondsSince1970Double)
let single = SingleValueDecoder(sqliteData: .real(seconds))
XCTAssertEqual(try dateDecoder.decode(from: single), date)
var row = SQLiteRow()
row[CodingKeys.key.stringValue] = .real(seconds)
let keyed = KeyedDecoder(sqliteData: row)
XCTAssertEqual(try dateDecoder.decode(from: keyed, for: CodingKeys.key), date)
}
}
private extension DateDecoderTests {
enum CodingKeys: CodingKey {
case key
}
final class SingleValueDecoder: DLCDecoder.ValueDecoder {
let sqliteData: SQLiteRawValue
let dateDecoder: DLCDecoder.DateDecoder
let codingPath: [any CodingKey]
let userInfo: [CodingUserInfoKey: Any]
init(
sqliteData: SQLiteRawValue,
dateDecoder: DLCDecoder.DateDecoder = RowDecoder.DateDecoder(strategy: .deferredToDate),
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 {
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 {
fatalError()
}
}
final class KeyedDecoder: DLCDecoder.RowDecoder {
let sqliteData: SQLiteRow
let dateDecoder: DLCDecoder.DateDecoder
let codingPath: [any CodingKey]
let userInfo: [CodingUserInfoKey: Any]
var count: Int? { sqliteData.count }
init(
sqliteData: SQLiteRow,
dateDecoder: DLCDecoder.DateDecoder = RowDecoder.DateDecoder(strategy: .deferredToDate),
codingPath: [any CodingKey] = [],
userInfo: [CodingUserInfoKey: Any] = [:]
) {
self.sqliteData = sqliteData
self.dateDecoder = dateDecoder
self.codingPath = codingPath
self.userInfo = userInfo
}
func contains(_ key: any CodingKey) -> Bool {
fatalError()
}
func decodeNil(for key: any CodingKey) throws -> Bool {
fatalError()
}
func decodeDate(for key: any CodingKey) throws -> Date {
fatalError()
}
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 {
fatalError()
}
func container<Key: CodingKey>(
keyedBy type: Key.Type
) throws -> KeyedDecodingContainer<Key> {
fatalError()
}
func unkeyedContainer() throws -> any UnkeyedDecodingContainer {
fatalError()
}
func singleValueContainer() throws -> any SingleValueDecodingContainer {
fatalError()
}
}
}

View File

@@ -0,0 +1,222 @@
import XCTest
import DataLiteCore
import DLCEncoder
import DLCCommon
@testable import DataLiteCoder
final class DateEncoderTests: XCTestCase {
func testDeferredToDate() throws {
let date = Date(timeIntervalSince1970: 1234567890)
let dateEncoder = RowEncoder.DateEncoder(strategy: .deferredToDate)
let singleEncoder = SingleValueEncoder(dateEncoder: dateEncoder)
let keyedEncoder = KeyedEncoder(dateEncoder: dateEncoder)
let key = RowCodingKey(stringValue: "key1")
try dateEncoder.encode(date, to: singleEncoder)
try dateEncoder.encode(date, for: key, to: keyedEncoder)
XCTAssertEqual(singleEncoder.sqliteData, date.sqliteRawValue)
XCTAssertEqual(keyedEncoder.sqliteData[key], date.sqliteRawValue)
}
func testISO8601() throws {
let formatter = ISO8601DateFormatter()
let string = "2024-04-18T13:45:00Z"
let date = formatter.date(from: string)!
let dateEncoder = RowEncoder.DateEncoder(strategy: .iso8601)
let singleEncoder = SingleValueEncoder(dateEncoder: dateEncoder)
let keyedEncoder = KeyedEncoder(dateEncoder: dateEncoder)
let key = RowCodingKey(stringValue: "key1")
try dateEncoder.encode(date, to: singleEncoder)
try dateEncoder.encode(date, for: key, to: keyedEncoder)
XCTAssertEqual(singleEncoder.sqliteData, .text(string))
XCTAssertEqual(keyedEncoder.sqliteData[key], .text(string))
}
func testFormatted() throws {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let string = "2023-10-15 16:30:00"
let date = formatter.date(from: string)!
let dateEncoder = RowEncoder.DateEncoder(strategy: .formatted(formatter))
let singleEncoder = SingleValueEncoder(dateEncoder: dateEncoder)
let keyedEncoder = KeyedEncoder(dateEncoder: dateEncoder)
let key = RowCodingKey(stringValue: "key1")
try dateEncoder.encode(date, to: singleEncoder)
try dateEncoder.encode(date, for: key, to: keyedEncoder)
XCTAssertEqual(singleEncoder.sqliteData, .text(string))
XCTAssertEqual(keyedEncoder.sqliteData[key], .text(string))
}
func testMillisecondsSince1970Int() throws {
let millis: Int64 = 1_600_000_000_000
let date = Date(timeIntervalSince1970: Double(millis) / 1000)
let dateEncoder = RowEncoder.DateEncoder(strategy: .millisecondsSince1970Int)
let singleEncoder = SingleValueEncoder(dateEncoder: dateEncoder)
let keyedEncoder = KeyedEncoder(dateEncoder: dateEncoder)
let key = RowCodingKey(stringValue: "key1")
try dateEncoder.encode(date, to: singleEncoder)
try dateEncoder.encode(date, for: key, to: keyedEncoder)
XCTAssertEqual(singleEncoder.sqliteData, .int(millis))
XCTAssertEqual(keyedEncoder.sqliteData[key], .int(millis))
}
func testMillisecondsSince1970Double() throws {
let millis: Double = 1_600_000_000_000
let date = Date(timeIntervalSince1970: millis / 1000)
let dateEncoder = RowEncoder.DateEncoder(strategy: .millisecondsSince1970Double)
let singleEncoder = SingleValueEncoder(dateEncoder: dateEncoder)
let keyedEncoder = KeyedEncoder(dateEncoder: dateEncoder)
let key = RowCodingKey(stringValue: "key1")
try dateEncoder.encode(date, to: singleEncoder)
try dateEncoder.encode(date, for: key, to: keyedEncoder)
XCTAssertEqual(singleEncoder.sqliteData, .real(millis))
XCTAssertEqual(keyedEncoder.sqliteData[key], .real(millis))
}
func testSecondsSince1970Int() throws {
let seconds: Int64 = 1_600_000_000
let date = Date(timeIntervalSince1970: Double(seconds))
let dateEncoder = RowEncoder.DateEncoder(strategy: .secondsSince1970Int)
let singleEncoder = SingleValueEncoder(dateEncoder: dateEncoder)
let keyedEncoder = KeyedEncoder(dateEncoder: dateEncoder)
let key = RowCodingKey(stringValue: "key1")
try dateEncoder.encode(date, to: singleEncoder)
try dateEncoder.encode(date, for: key, to: keyedEncoder)
XCTAssertEqual(singleEncoder.sqliteData, .int(seconds))
XCTAssertEqual(keyedEncoder.sqliteData[key], .int(seconds))
}
func testSecondsSince1970Double() throws {
let seconds: Double = 1_600_000_000
let date = Date(timeIntervalSince1970: seconds)
let dateEncoder = RowEncoder.DateEncoder(strategy: .secondsSince1970Double)
let singleEncoder = SingleValueEncoder(dateEncoder: dateEncoder)
let keyedEncoder = KeyedEncoder(dateEncoder: dateEncoder)
let key = RowCodingKey(stringValue: "key1")
try dateEncoder.encode(date, to: singleEncoder)
try dateEncoder.encode(date, for: key, to: keyedEncoder)
XCTAssertEqual(singleEncoder.sqliteData, .real(seconds))
XCTAssertEqual(keyedEncoder.sqliteData[key], .real(seconds))
}
}
private extension DateEncoderTests {
final class SingleValueEncoder: DLCEncoder.ValueEncoder {
let dateEncoder: any DateEncoder
let codingPath: [any CodingKey]
let userInfo: [CodingUserInfoKey : Any]
private(set) var sqliteData: SQLiteRawValue?
init(
dateEncoder: any DateEncoder,
codingPath: [any CodingKey] = [],
userInfo: [CodingUserInfoKey : Any] = [:]
) {
self.dateEncoder = dateEncoder
self.codingPath = codingPath
self.userInfo = userInfo
}
func encodeNil() throws {
fatalError()
}
func encodeDate(_ date: Date) throws {
fatalError()
}
func encode<T: SQLiteRawBindable>(_ value: T) throws {
sqliteData = value.sqliteRawValue
}
func container<Key: CodingKey>(
keyedBy type: Key.Type
) -> KeyedEncodingContainer<Key> {
fatalError()
}
func unkeyedContainer() -> any UnkeyedEncodingContainer {
fatalError()
}
func singleValueContainer() -> any SingleValueEncodingContainer {
fatalError()
}
}
final class KeyedEncoder: DLCEncoder.RowEncoder {
let dateEncoder: any DateEncoder
let codingPath: [any CodingKey]
let userInfo: [CodingUserInfoKey : Any]
private(set) var sqliteData = SQLiteRow()
var count: Int { fatalError() }
init(
dateEncoder: any DateEncoder,
codingPath: [any CodingKey] = [],
userInfo: [CodingUserInfoKey : Any] = [:]
) {
self.dateEncoder = dateEncoder
self.codingPath = codingPath
self.userInfo = userInfo
}
func set(_ value: Any, for key: any CodingKey) throws {
fatalError()
}
func encodeNil(for key: any CodingKey) throws {
fatalError()
}
func encodeDate(_ date: Date, for key: any CodingKey) throws {
fatalError()
}
func encode<T: SQLiteRawBindable>(_ value: T, for key: any CodingKey) throws {
sqliteData[key] = value.sqliteRawValue
}
func encoder(for key: any CodingKey) throws -> any Encoder {
fatalError()
}
func container<Key: CodingKey>(
keyedBy type: Key.Type
) -> KeyedEncodingContainer<Key> {
fatalError()
}
func unkeyedContainer() -> any UnkeyedEncodingContainer {
fatalError()
}
func singleValueContainer() -> any SingleValueEncodingContainer {
fatalError()
}
}
}

View File

@@ -0,0 +1,337 @@
import XCTest
import DataLiteCore
import DataLiteCoder
final class RowDecoderTests: XCTestCase {
// MARK: - Decode SQLiteRow
func testDecodeRowWithAllTypes() throws {
let decoder = RowDecoder(
userInfo: [:],
dateDecodingStrategy: .deferredToDate
)
let model = StandardModel(
id: 123,
type: .simple,
name: "John Doe",
age: 34,
isActive: true,
score: 3.1415,
createdAt: Date(timeIntervalSince1970: 12345),
payload: "payload".data(using: .utf8)!
)
var row = SQLiteRow()
row["id"] = model.id.sqliteRawValue
row["type"] = model.type.rawValue.sqliteRawValue
row["name"] = model.name.sqliteRawValue
row["age"] = model.age.sqliteRawValue
row["isActive"] = model.isActive.sqliteRawValue
row["score"] = model.score.sqliteRawValue
row["createdAt"] = model.createdAt.sqliteRawValue
row["payload"] = model.payload.sqliteRawValue
let decoded = try decoder.decode(
StandardModel.self,
from: row
)
XCTAssertEqual(decoded, model)
}
func testDecodeRowWithOptionalValues() throws {
let decoder = RowDecoder(
userInfo: [:],
dateDecodingStrategy: .deferredToDate
)
let model = OptionalModel(
id: 123,
type: .multiple,
name: "Jane Doe",
createdAt: Date(timeIntervalSince1970: 11000),
payload: "payload".data(using: .utf8)!
)
var row = SQLiteRow()
row["id"] = model.id!.sqliteRawValue
row["type"] = model.type!.rawValue.sqliteRawValue
row["name"] = model.name!.sqliteRawValue
row["createdAt"] = model.createdAt!.sqliteRawValue
row["payload"] = model.payload!.sqliteRawValue
let decoded = try decoder.decode(
OptionalModel.self,
from: row
)
let empty = try decoder.decode(
OptionalModel.self,
from: SQLiteRow()
)
XCTAssertEqual(decoded, model)
XCTAssertEqual(empty, OptionalModel())
}
func testDecodeRowAsArray() throws {
let decoder = RowDecoder(
userInfo: [:],
dateDecodingStrategy: .deferredToDate
)
let dates = [
Date(timeIntervalSince1970: 1234),
Date(timeIntervalSince1970: 31415),
Date(timeIntervalSince1970: 123456789)
]
var row = SQLiteRow()
row["key0"] = dates[0].sqliteRawValue
row["key1"] = dates[1].sqliteRawValue
row["key2"] = dates[2].sqliteRawValue
let decoded = try decoder.decode([Date].self, from: row)
XCTAssertEqual(decoded, dates)
}
func testDecodeRowMissingRequiredField() {
let decoder = RowDecoder(
userInfo: [:],
dateDecodingStrategy: .deferredToDate
)
var row = SQLiteRow()
row["id"] = 1.sqliteRawValue
XCTAssertThrowsError(
try decoder.decode(SimpleModel.self, from: row)
) { error in
guard case DecodingError.keyNotFound = error else {
return XCTFail("Expected DecodingError.keyNotFound, but got: \(error)")
}
}
}
func testDecodeRowWrongType() {
let decoder = RowDecoder(
userInfo: [:],
dateDecodingStrategy: .deferredToDate
)
var row = SQLiteRow()
row["id"] = "not an int".sqliteRawValue
row["name"] = "test".sqliteRawValue
XCTAssertThrowsError(
try decoder.decode(SimpleModel.self, from: row)
) { error in
guard case DecodingError.typeMismatch = error else {
return XCTFail("Expected DecodingError.typeMismatch, but got: \(error)")
}
}
}
// MARK: - Decode array of SQLiteRow
func testDecodeRowArrayWithAllTypes() throws {
let decoder = RowDecoder(
userInfo: [:],
dateDecodingStrategy: .deferredToDate
)
let models = [
StandardModel(
id: 123,
type: .simple,
name: "John Doe",
age: 34,
isActive: true,
score: 3.1415,
createdAt: Date(timeIntervalSince1970: 12345),
payload: "payload".data(using: .utf8)!
),
StandardModel(
id: 456,
type: .multiple,
name: "Jane Doe",
age: 28,
isActive: false,
score: 2.7182,
createdAt: Date(timeIntervalSince1970: 67890),
payload: "another payload".data(using: .utf8)!
)
]
let rows: [SQLiteRow] = models.map { model in
var row = SQLiteRow()
row["id"] = model.id.sqliteRawValue
row["type"] = model.type.rawValue.sqliteRawValue
row["name"] = model.name.sqliteRawValue
row["age"] = model.age.sqliteRawValue
row["isActive"] = model.isActive.sqliteRawValue
row["score"] = model.score.sqliteRawValue
row["createdAt"] = model.createdAt.sqliteRawValue
row["payload"] = model.payload.sqliteRawValue
return row
}
let decoded = try decoder.decode([StandardModel].self, from: rows)
XCTAssertEqual(decoded, models)
}
func testDecodeRowArrayWithOptionalValues() throws {
let decoder = RowDecoder(
userInfo: [:],
dateDecodingStrategy: .deferredToDate
)
let models = [
OptionalModel(
id: 123,
type: .multiple,
name: "Jane Doe",
createdAt: Date(timeIntervalSince1970: 11000),
payload: "payload".data(using: .utf8)!
),
OptionalModel(
id: nil,
type: nil,
name: "John Doe",
createdAt: nil,
payload: nil
)
]
let rows: [SQLiteRow] = models.map { model in
var row = SQLiteRow()
row["id"] = model.id?.sqliteRawValue
row["type"] = model.type?.rawValue.sqliteRawValue
row["name"] = model.name?.sqliteRawValue
row["createdAt"] = model.createdAt?.sqliteRawValue
row["payload"] = model.payload?.sqliteRawValue
return row
}
let decoded = try decoder.decode([OptionalModel].self, from: rows)
XCTAssertEqual(decoded, models)
}
func testDecodeRowArrayAsDates() throws {
let decoder = RowDecoder(
userInfo: [:],
dateDecodingStrategy: .deferredToDate
)
let dates = [
[
Date(timeIntervalSince1970: 1234),
Date(timeIntervalSince1970: 31415),
Date(timeIntervalSince1970: 12345679)
],
[
Date(timeIntervalSince1970: 1234),
Date(timeIntervalSince1970: 31415),
Date(timeIntervalSince1970: 12345679)
]
]
let rows: [SQLiteRow] = dates.map { dates in
var row = SQLiteRow()
row["key0"] = dates[0].sqliteRawValue
row["key1"] = dates[1].sqliteRawValue
row["key2"] = dates[2].sqliteRawValue
return row
}
let decoded = try decoder.decode([[Date]].self, from: rows)
XCTAssertEqual(decoded, dates)
}
func testDecodeRowArrayMissingRequiredField() {
let decoder = RowDecoder(
userInfo: [:],
dateDecodingStrategy: .deferredToDate
)
var row = SQLiteRow()
row["id"] = 1.sqliteRawValue
XCTAssertThrowsError(
try decoder.decode([SimpleModel].self, from: [row])
) { error in
guard case DecodingError.keyNotFound = error else {
return XCTFail("Expected DecodingError.keyNotFound, but got: \(error)")
}
}
}
func testDecodeRowArrayWrongType() {
let decoder = RowDecoder(
userInfo: [:],
dateDecodingStrategy: .deferredToDate
)
var row = SQLiteRow()
row["id"] = "not an int".sqliteRawValue
row["name"] = "test".sqliteRawValue
XCTAssertThrowsError(
try decoder.decode([SimpleModel].self, from: [row])
) { error in
guard case DecodingError.typeMismatch = error else {
return XCTFail("Expected DecodingError.typeMismatch, but got: \(error)")
}
}
}
}
private extension RowDecoderTests {
enum `Type`: String, Decodable, Equatable {
case simple
case multiple
}
struct StandardModel: Decodable, Equatable {
let id: Int
let type: `Type`
let name: String
let age: Int
let isActive: Bool
let score: Double
let createdAt: Date
let payload: Data
}
struct OptionalModel: Decodable, Equatable {
let id: Int?
let type: `Type`?
let name: String?
let createdAt: Date?
let payload: Data?
init(
id: Int? = nil,
type: `Type`? = nil,
name: String? = nil,
createdAt: Date? = nil,
payload: Data? = nil
) {
self.id = id
self.type = type
self.name = name
self.createdAt = createdAt
self.payload = payload
}
}
struct SimpleModel: Decodable, Equatable {
let id: Int
let name: String
}
}

View File

@@ -0,0 +1,231 @@
import XCTest
import DataLiteCore
import DataLiteCoder
final class RowEncoderTests: XCTestCase {
// MARK: - Encode to SQLiteRow
func testEncodeToRowWithAllTypes() throws {
let createdAt = Date(timeIntervalSince1970: 12345)
let payload = "payload".data(using: .utf8)!
let model = StandardModel(
id: 123456,
type: .simple,
name: "John Doe",
age: 34,
isActive: true,
score: 3.1415,
createdAt: createdAt,
payload: payload
)
let row = try RowEncoder(
userInfo: [:],
dateEncodingStrategy: .deferredToDate
).encode(model)
XCTAssertEqual(row.count, 8)
XCTAssertEqual(row["id"], .int(123456))
XCTAssertEqual(row["type"], .text("simple"))
XCTAssertEqual(row["name"], .text("John Doe"))
XCTAssertEqual(row["age"], .int(34))
XCTAssertEqual(row["isActive"], .int(1))
XCTAssertEqual(row["score"], .real(3.1415))
XCTAssertEqual(row["createdAt"], createdAt.sqliteRawValue)
XCTAssertEqual(row["payload"], .blob(payload))
}
func testEncodeToRowWithOptionalValues() throws {
let createdAt = Date(timeIntervalSince1970: 12345)
let payload = "payload".data(using: .utf8)!
let model = OptionalModel(
id: 123456,
type: .multiple,
name: "Jane Doe",
createdAt: createdAt,
payload: payload
)
let row = try RowEncoder(
userInfo: [:],
dateEncodingStrategy: .deferredToDate
).encode(model)
XCTAssertEqual(row.count, 5)
XCTAssertEqual(row["id"], .int(123456))
XCTAssertEqual(row["type"], .text("multiple"))
XCTAssertEqual(row["name"], .text("Jane Doe"))
XCTAssertEqual(row["createdAt"], createdAt.sqliteRawValue)
XCTAssertEqual(row["payload"], .blob(payload))
}
func testEncodeToRowWithOptionalNilValues() throws {
let row = try RowEncoder(
userInfo: [:],
dateEncodingStrategy: .deferredToDate
).encode(OptionalModel())
XCTAssertEqual(row.count, 5)
XCTAssertEqual(row["id"], .null)
XCTAssertEqual(row["type"], .null)
XCTAssertEqual(row["name"], .null)
XCTAssertEqual(row["createdAt"], .null)
XCTAssertEqual(row["payload"], .null)
}
// MARK: - Encode to Array of SQLiteRow
func testEncodeToRowArrayWithAllTypes() throws {
let createdAt = Date(timeIntervalSince1970: 12345)
let payload = "payload".data(using: .utf8)!
let models = [
StandardModel(
id: 123456,
type: .simple,
name: "John Doe",
age: 34,
isActive: true,
score: 3.1415,
createdAt: createdAt,
payload: payload
),
StandardModel(
id: 456,
type: .multiple,
name: "Jane Doe",
age: 28,
isActive: false,
score: 2.7182,
createdAt: createdAt,
payload: payload
)
]
let rows = try RowEncoder(
userInfo: [:],
dateEncodingStrategy: .deferredToDate
).encode(models)
XCTAssertEqual(rows.count, 2)
XCTAssertEqual(rows[0].count, 8)
XCTAssertEqual(rows[1].count, 8)
XCTAssertEqual(rows[0]["id"], .int(123456))
XCTAssertEqual(rows[0]["type"], .text("simple"))
XCTAssertEqual(rows[0]["name"], .text("John Doe"))
XCTAssertEqual(rows[0]["age"], .int(34))
XCTAssertEqual(rows[0]["isActive"], .int(1))
XCTAssertEqual(rows[0]["score"], .real(3.1415))
XCTAssertEqual(rows[0]["createdAt"], createdAt.sqliteRawValue)
XCTAssertEqual(rows[0]["payload"], .blob(payload))
XCTAssertEqual(rows[1]["id"], .int(456))
XCTAssertEqual(rows[1]["type"], .text("multiple"))
XCTAssertEqual(rows[1]["name"], .text("Jane Doe"))
XCTAssertEqual(rows[1]["age"], .int(28))
XCTAssertEqual(rows[1]["isActive"], .int(0))
XCTAssertEqual(rows[1]["score"], .real(2.7182))
XCTAssertEqual(rows[1]["createdAt"], createdAt.sqliteRawValue)
XCTAssertEqual(rows[1]["payload"], .blob(payload))
}
func testEncodeToRowArrayWithOptionalValues() throws {
let createdAt = Date(timeIntervalSince1970: 12345)
let payload = "payload".data(using: .utf8)!
let models = [
OptionalModel(
id: 123,
type: .multiple,
name: "Jane Doe",
createdAt: createdAt,
payload: payload
),
OptionalModel(
id: nil,
type: nil,
name: "John Doe",
createdAt: nil,
payload: nil
)
]
let rows = try RowEncoder(
userInfo: [:],
dateEncodingStrategy: .deferredToDate
).encode(models)
XCTAssertEqual(rows.count, 2)
XCTAssertEqual(rows[0].count, 5)
XCTAssertEqual(rows[1].count, 5)
XCTAssertEqual(rows[0]["id"], .int(123))
XCTAssertEqual(rows[0]["type"], .text("multiple"))
XCTAssertEqual(rows[0]["name"], .text("Jane Doe"))
XCTAssertEqual(rows[0]["createdAt"], createdAt.sqliteRawValue)
XCTAssertEqual(rows[0]["payload"], .blob(payload))
XCTAssertEqual(rows[1]["id"], .null)
XCTAssertEqual(rows[1]["type"], .null)
XCTAssertEqual(rows[1]["name"], .text("John Doe"))
XCTAssertEqual(rows[1]["createdAt"], .null)
XCTAssertEqual(rows[1]["payload"], .null)
}
}
private extension RowEncoderTests {
enum `Type`: String, Encodable, Equatable {
case simple
case multiple
}
struct StandardModel: Encodable, Equatable {
let id: Int
let type: `Type`
let name: String
let age: Int
let isActive: Bool
let score: Double
let createdAt: Date
let payload: Data
}
struct OptionalModel: Encodable, Equatable {
let id: Int?
let type: `Type`?
let name: String?
let createdAt: Date?
let payload: Data?
init(
id: Int? = nil,
type: `Type`? = nil,
name: String? = nil,
createdAt: Date? = nil,
payload: Data? = nil
) {
self.id = id
self.type = type
self.name = name
self.createdAt = createdAt
self.payload = payload
}
enum CodingKeys: CodingKey {
case id
case type
case name
case createdAt
case payload
}
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(self.id, forKey: .id)
try container.encodeIfPresent(self.type, forKey: .type)
try container.encodeIfPresent(self.name, forKey: .name)
try container.encodeIfPresent(self.createdAt, forKey: .createdAt)
try container.encodeIfPresent(self.payload, forKey: .payload)
}
}
}