160 lines
5.4 KiB
Swift
160 lines
5.4 KiB
Swift
//
|
|
// Copyright 2023 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
@testable import SignalServiceKit
|
|
import XCTest
|
|
|
|
class FactoryInitializationTests: XCTestCase {
|
|
func testFactoryInitialization_SuccessFromHardcodedData() throws {
|
|
class BaseClass: NeedsFactoryInitializationFromRecordType {
|
|
enum CodingKeys: CodingKey { case recordType; case base; case foo; case bar }
|
|
|
|
static var recordTypeCodingKey: CodingKeys { .recordType }
|
|
|
|
static func classToInitialize(
|
|
forRecordType recordType: UInt
|
|
) -> (FactoryInitializableFromRecordType.Type)? {
|
|
switch recordType {
|
|
case 1:
|
|
return FooClass.self
|
|
case 2:
|
|
return BarClass.self
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
let base: String
|
|
|
|
init(baseClassFromDecoder decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
base = try container.decode(String.self, forKey: .base)
|
|
}
|
|
}
|
|
|
|
class FooClass: BaseClass, FactoryInitializableFromRecordType {
|
|
static var recordType: UInt { 1 }
|
|
|
|
let foo: String
|
|
|
|
required init(forRecordTypeFactoryInitializationFrom decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
foo = try container.decode(String.self, forKey: .foo)
|
|
try super.init(baseClassFromDecoder: container.superDecoder())
|
|
}
|
|
}
|
|
|
|
class BarClass: BaseClass, FactoryInitializableFromRecordType {
|
|
static var recordType: UInt { 2 }
|
|
|
|
let bar: String
|
|
|
|
required init(forRecordTypeFactoryInitializationFrom decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
bar = try container.decode(String.self, forKey: .bar)
|
|
try super.init(baseClassFromDecoder: container.superDecoder())
|
|
}
|
|
}
|
|
|
|
let fooJsonData: Data = """
|
|
{
|
|
"super": {
|
|
"recordType": 1,
|
|
"base": "spaceship"
|
|
},
|
|
"foo": "millenium falcon"
|
|
}
|
|
""".data(using: .utf8)!
|
|
|
|
let barJsonData: Data = """
|
|
{
|
|
"super": {
|
|
"recordType": 2,
|
|
"base": "crystal"
|
|
},
|
|
"bar": "kyber"
|
|
}
|
|
""".data(using: .utf8)!
|
|
|
|
let fooInstance = try JSONDecoder().decode(BaseClass.self, from: fooJsonData)
|
|
let barInstance = try JSONDecoder().decode(BaseClass.self, from: barJsonData)
|
|
|
|
guard let foo = fooInstance as? FooClass else {
|
|
XCTFail("Failed to cast foo instance!")
|
|
return
|
|
}
|
|
|
|
guard let bar = barInstance as? BarClass else {
|
|
XCTFail("Failed to cast bar instance!")
|
|
return
|
|
}
|
|
|
|
XCTAssertEqual(foo.base, "spaceship")
|
|
XCTAssertEqual(foo.foo, "millenium falcon")
|
|
|
|
XCTAssertEqual(bar.base, "crystal")
|
|
XCTAssertEqual(bar.bar, "kyber")
|
|
}
|
|
|
|
func testFactoryInitialization_ThrowsForBadRecordType() throws {
|
|
let invalidJsonData: Data = #"{ "super": { "recordType": 3 } }"#.data(using: .utf8)!
|
|
let fooJsonData: Data = #"{ "super": { "recordType": 1 } }"#.data(using: .utf8)!
|
|
let barJsonData: Data = #"{ "super": { "recordType": 2 } }"#.data(using: .utf8)!
|
|
|
|
func decodeAndCatchDecodingError(fromData data: Data) throws {
|
|
do {
|
|
_ = try JSONDecoder().decode(BaseClassThatMisinterpretsRecordTypes.self, from: data)
|
|
XCTFail("Should have thrown while decoding!")
|
|
} catch let DecodingError.dataCorrupted(context) {
|
|
XCTAssertEqual(
|
|
(context.codingPath.first! as! BaseClassThatMisinterpretsRecordTypes.CodingKeys),
|
|
.recordType
|
|
)
|
|
}
|
|
}
|
|
|
|
try decodeAndCatchDecodingError(fromData: invalidJsonData)
|
|
try decodeAndCatchDecodingError(fromData: fooJsonData)
|
|
try decodeAndCatchDecodingError(fromData: barJsonData)
|
|
}
|
|
}
|
|
|
|
private class BaseClassThatMisinterpretsRecordTypes: NeedsFactoryInitializationFromRecordType {
|
|
enum CodingKeys: CodingKey { case recordType }
|
|
|
|
static var recordTypeCodingKey: CodingKeys { .recordType }
|
|
|
|
static func classToInitialize(
|
|
forRecordType recordType: UInt
|
|
) -> (FactoryInitializableFromRecordType.Type)? {
|
|
switch recordType {
|
|
case 1:
|
|
return BarClass.self
|
|
case 2:
|
|
return FooClass.self
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
private class FooClass: BaseClassThatMisinterpretsRecordTypes, FactoryInitializableFromRecordType {
|
|
static var recordType: UInt { 1 }
|
|
|
|
required init(forRecordTypeFactoryInitializationFrom decoder: Decoder) throws {
|
|
XCTFail("Initializer should never have been called!")
|
|
fatalError("")
|
|
}
|
|
}
|
|
|
|
private class BarClass: BaseClassThatMisinterpretsRecordTypes, FactoryInitializableFromRecordType {
|
|
static var recordType: UInt { 2 }
|
|
|
|
required init(forRecordTypeFactoryInitializationFrom decoder: Decoder) throws {
|
|
XCTFail("Initializer should never have been called!")
|
|
fatalError("")
|
|
}
|
|
}
|