Skip to content

Commit

Permalink
Add serialization fix and tests for sdk inter-op
Browse files Browse the repository at this point in the history
- Parses big list of naughty strings for testing encoding and escaping
  (https://github.com/minimaxir/big-list-of-naughty-strings),
  serializes each using Swift SDK implementation and compares to
  other SDK output for the same values.
- Serialization is not uniform, so tests include known failures.
- Includes fix to Swift serialization implementation using modified
  JSON encoding
  • Loading branch information
Greg Stromire committed Jul 10, 2018
1 parent 92dd676 commit 65f6328
Show file tree
Hide file tree
Showing 8 changed files with 6,725 additions and 1 deletion.
19 changes: 18 additions & 1 deletion E3db/Classes/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,25 @@ extension NSNumber: Signable {
}

extension String: Signable {
/// Encodes the string using the JSONEncoder, with modifications to match other SDKs
/// (e.g. Java's Jackson Object Mapper, and JavaScript's JSON.stringify). Matching other
/// E3db SDK implementations is required for document signature verification.
///
/// The Swift JSONEncoder will escape a forward slash, as shown here:
/// https://github.com/apple/swift-corelibs-foundation/blob/d23f4d7a4151d959ac185eca1c0f14de2c8dc73a/Foundation/JSONSerialization.swift#L407
/// so this function will unescape it, again to match implementations in other languages.
///
/// - Returns: An encoded string, matching other E3db SDK implementations, for signature verification.
public func serialized() -> String {
return "\"\(self)\""
// Encodes the string as part of an array, then removes the first and last bytes
// (representing the opening and closing brackets). This will provide the proper
// escaping for most string values, but it also escapes a forward slash, so we
// replace any occurrences of those.
let array = try? kStaticJsonEncoder.encode([self])
let string = array?.advanced(by: 1).dropLast(1)
let value = string.flatMap { String(bytes: $0, encoding: .utf8) }
let escaped = value?.replacingOccurrences(of: "\\/", with: "/")
return escaped ?? ""
}
}

Expand Down
16 changes: 16 additions & 0 deletions Example/E3db-Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
objects = {

/* Begin PBXBuildFile section */
2C13341720F1412F0000759D /* blns.json in Resources */ = {isa = PBXBuildFile; fileRef = 2C13341620F1412F0000759D /* blns.json */; };
2C13341B20F40D500000759D /* blns-swift.json in Resources */ = {isa = PBXBuildFile; fileRef = 2C13341A20F40D500000759D /* blns-swift.json */; };
2C13341D20F535220000759D /* blns-node.json in Resources */ = {isa = PBXBuildFile; fileRef = 2C13341C20F535210000759D /* blns-node.json */; };
2C13341F20F5357D0000759D /* blns-java.json in Resources */ = {isa = PBXBuildFile; fileRef = 2C13341E20F5357D0000759D /* blns-java.json */; };
2C5AE665208E54F4008F52C5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5AE65D208E54F4008F52C5 /* ViewController.swift */; };
2C5AE666208E54F4008F52C5 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C5AE65E208E54F4008F52C5 /* LaunchScreen.xib */; };
2C5AE667208E54F4008F52C5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2C5AE660208E54F4008F52C5 /* Main.storyboard */; };
Expand Down Expand Up @@ -38,6 +42,10 @@
/* Begin PBXFileReference section */
1AF9C0CA897DE439F2391934 /* Pods_E3db_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_E3db_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
261B9B0E0E7CDF9808E9C883 /* Pods_E3db_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_E3db_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2C13341620F1412F0000759D /* blns.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = blns.json; sourceTree = "<group>"; };
2C13341A20F40D500000759D /* blns-swift.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "blns-swift.json"; sourceTree = "<group>"; };
2C13341C20F535210000759D /* blns-node.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "blns-node.json"; sourceTree = "<group>"; };
2C13341E20F5357D0000759D /* blns-java.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "blns-java.json"; sourceTree = "<group>"; };
2C5AE65D208E54F4008F52C5 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
2C5AE65F208E54F4008F52C5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
2C5AE661208E54F4008F52C5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
Expand Down Expand Up @@ -129,6 +137,10 @@
2CAF7AED2085B1DF0061397F /* IntegrationTests.swift */,
2CAF7AEA2085B1DF0061397F /* PropertyTests.swift */,
2CAF7AEE2085B1E00061397F /* TestUtils.swift */,
2C13341A20F40D500000759D /* blns-swift.json */,
2C13341E20F5357D0000759D /* blns-java.json */,
2C13341C20F535210000759D /* blns-node.json */,
2C13341620F1412F0000759D /* blns.json */,
607FACE91AFB9204008FA782 /* Supporting Files */,
);
path = Tests;
Expand Down Expand Up @@ -275,6 +287,10 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2C13341F20F5357D0000759D /* blns-java.json in Resources */,
2C13341B20F40D500000759D /* blns-swift.json in Resources */,
2C13341D20F535220000759D /* blns-node.json in Resources */,
2C13341720F1412F0000759D /* blns.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
30 changes: 30 additions & 0 deletions Example/Tests/DocumentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -327,4 +327,34 @@ class DocumentTests: XCTestCase, TestUtils {
XCTFail(error.localizedDescription)
}
}

func testBlnsSerializationSwiftMatchesJavaScript() {
let swiftTests = serializeBlns()
let nodeTests = loadBlnsSerializationResults(filename: "blns-node")

// known indexes for tests that fail based on known issues
// 96 and 169 have hidden-whitespace related errors
let knownFailures = ["96", "169"]
let failedTests = zip(swiftTests, nodeTests)
.filter { $0.0["serialized"] != $0.1["serialized"] }
.filter { !knownFailures.contains($0.0["index"]!) }

XCTAssert(failedTests.isEmpty)
}

func testBlnsSerializationSwiftMatchesJava() {
let swiftTests = serializeBlns()
let javaTests = loadBlnsSerializationResults(filename: "blns-java")

// known indexes for tests that fail based on known issues
// 96 and 169 have hidden-whitespace related errors
// 92, 94, 503, 504 have unicode case-sensitivity related errors
let knownFailures = ["92", "94", "96", "169", "503", "504"]
let failedTests = zip(swiftTests, javaTests)
.filter { $0.0["serialized"] != $0.1["serialized"] }
.filter { !knownFailures.contains($0.0["index"]!) }

XCTAssert(failedTests.isEmpty)
}

}
63 changes: 63 additions & 0 deletions Example/Tests/TestUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ protocol TestUtils {
func writeTestRecord(_ e3db: Client, _ contentType: String) -> Record
func deleteRecord(_ record: Record, e3db: Client)
func deleteAllRecords(_ e3db: Client)
func serializeBlns() -> [[String: String]]
func loadBlnsSerializationResults(filename: String) -> [[String: String]]
func writeBlnsTestsToFile(tests: [[String: String]])
static func verboseUrlSession() -> URLSession
static func createClientSync() -> Client
static func createKeySync(client: Client, recordType: String) -> EAKInfo
Expand Down Expand Up @@ -100,6 +103,66 @@ extension TestUtils where Self: XCTestCase {
}
}

func serializeBlns() -> [[String: String]] {
guard let jsonUrl = Bundle(for: type(of: self)).url(forResource: "blns", withExtension: "json") else {
XCTFail("Could not load blns.json")
return []
}

do {
let jsonData = try Data(contentsOf: jsonUrl)
let strings = try JSONDecoder().decode([String].self, from: jsonData)

let tests = strings.enumerated().map { pair -> [String : String] in
let elementIdx = "\(pair.offset)"
let recordData = RecordData(cleartext: [elementIdx: pair.element])
let serialized = recordData.serialized()
let b64Encoded = Data(serialized.utf8).base64EncodedString()
return [
"index": elementIdx,
"element": pair.element,
"serialized": serialized,
"b64Encoded": b64Encoded
]
}
return tests
} catch {
XCTFail(error.localizedDescription)
}
return []
}

func loadBlnsSerializationResults(filename: String) -> [[String: String]] {
guard let jsonUrl = Bundle(for: type(of: self)).url(forResource: filename, withExtension: "json") else {
XCTFail("Could not load \(filename).json")
return []
}

do {
let data = try Data(contentsOf: jsonUrl)
return try JSONDecoder().decode([[String: String]].self, from: data)
} catch {
XCTFail(error.localizedDescription)
}
return []
}

func writeBlnsTestsToFile(tests: [[String: String]]) {
guard let outputUrl = Bundle(for: type(of: self)).url(forResource: "blns-swift", withExtension: "json") else {
return XCTFail("Could not load blns-swift.json")
}

let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
do {
let encodedTests = try jsonEncoder.encode(tests)
try encodedTests.write(to: outputUrl)
print("BLNS tests serialized to \(outputUrl.absoluteString)")
} catch {
XCTFail(error.localizedDescription)
}
}

static func verboseUrlSession() -> URLSession {
let verboseConfig = URLSession.shared.configuration
ResponseDetective.enable(inConfiguration: verboseConfig)
Expand Down
Loading

0 comments on commit 65f6328

Please sign in to comment.