DataLireCoder swift package
This commit is contained in:
241
Tests/DataLiteCoderTests/DateDecoderTests.swift
Normal file
241
Tests/DataLiteCoderTests/DateDecoderTests.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
222
Tests/DataLiteCoderTests/DateEncoderTests.swift
Normal file
222
Tests/DataLiteCoderTests/DateEncoderTests.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
337
Tests/DataLiteCoderTests/RowDecoderTests.swift
Normal file
337
Tests/DataLiteCoderTests/RowDecoderTests.swift
Normal 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
|
||||
}
|
||||
}
|
||||
231
Tests/DataLiteCoderTests/RowEncoderTests.swift
Normal file
231
Tests/DataLiteCoderTests/RowEncoderTests.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user