Skip to content

Commit ce5cf5f

Browse files
authored
test(storage): increase code coverage (#650)
* test(storage): increase code coverage * add more tests * add tests * more tests
1 parent faf78f6 commit ce5cf5f

19 files changed

+1494
-65
lines changed

.swiftpm/xcode/xcshareddata/xcschemes/Storage.xcscheme

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
33
LastUpgradeVersion = "1510"
4-
version = "1.7">
4+
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"
77
buildImplicitDependencies = "YES">
@@ -26,8 +26,29 @@
2626
buildConfiguration = "Debug"
2727
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
2828
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29-
shouldUseLaunchSchemeArgsEnv = "YES">
29+
shouldUseLaunchSchemeArgsEnv = "YES"
30+
codeCoverageEnabled = "YES"
31+
onlyGenerateCoverageForSpecifiedTargets = "YES">
32+
<CodeCoverageTargets>
33+
<BuildableReference
34+
BuildableIdentifier = "primary"
35+
BlueprintIdentifier = "Storage"
36+
BuildableName = "Storage"
37+
BlueprintName = "Storage"
38+
ReferencedContainer = "container:">
39+
</BuildableReference>
40+
</CodeCoverageTargets>
3041
<Testables>
42+
<TestableReference
43+
skipped = "NO">
44+
<BuildableReference
45+
BuildableIdentifier = "primary"
46+
BlueprintIdentifier = "StorageTests"
47+
BuildableName = "StorageTests"
48+
BlueprintName = "StorageTests"
49+
ReferencedContainer = "container:">
50+
</BuildableReference>
51+
</TestableReference>
3152
</Testables>
3253
</TestAction>
3354
<LaunchAction

Package.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,13 @@ let package = Package(
156156
.product(name: "CustomDump", package: "swift-custom-dump"),
157157
.product(name: "InlineSnapshotTesting", package: "swift-snapshot-testing"),
158158
.product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
159+
"Mocker",
160+
"TestHelpers",
159161
"Storage",
160162
],
161163
resources: [
162-
.copy("sadcat.jpg")
164+
.copy("sadcat.jpg"),
165+
.process("Fixtures"),
163166
]
164167
),
165168
.target(

Sources/Helpers/Codable.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//
2+
// Codable.swift
3+
// Supabase
4+
//
5+
// Created by Guilherme Souza on 20/01/25.
6+
//
7+
8+
import ConcurrencyExtras
9+
import Foundation
10+
11+
extension JSONDecoder {
12+
private static let supportedDateFormatters: [UncheckedSendable<ISO8601DateFormatter>] = [
13+
ISO8601DateFormatter.iso8601WithFractionalSeconds,
14+
ISO8601DateFormatter.iso8601,
15+
]
16+
17+
/// Default `JSONDecoder` for decoding types from Supabase.
18+
package static let `default`: JSONDecoder = {
19+
let decoder = JSONDecoder()
20+
decoder.dateDecodingStrategy = .custom { decoder in
21+
let container = try decoder.singleValueContainer()
22+
let string = try container.decode(String.self)
23+
24+
for formatter in supportedDateFormatters {
25+
if let date = formatter.value.date(from: string) {
26+
return date
27+
}
28+
}
29+
30+
throw DecodingError.dataCorruptedError(
31+
in: container, debugDescription: "Invalid date format: \(string)"
32+
)
33+
}
34+
return decoder
35+
}()
36+
}

Sources/Helpers/HTTP/HTTPFields.swift

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,70 @@
11
import HTTPTypes
22

3-
package extension HTTPFields {
4-
init(_ dictionary: [String: String]) {
3+
extension HTTPFields {
4+
package init(_ dictionary: [String: String]) {
55
self.init(dictionary.map { .init(name: .init($0.key)!, value: $0.value) })
66
}
7-
8-
var dictionary: [String: String] {
7+
8+
package var dictionary: [String: String] {
99
let keyValues = self.map {
1010
($0.name.rawName, $0.value)
1111
}
12-
12+
1313
return .init(keyValues, uniquingKeysWith: { $1 })
1414
}
15-
16-
mutating func merge(with other: Self) {
15+
16+
package mutating func merge(with other: Self) {
1717
for field in other {
1818
self[field.name] = field.value
1919
}
2020
}
21-
22-
func merging(with other: Self) -> Self {
21+
22+
package func merging(with other: Self) -> Self {
2323
var copy = self
24-
24+
2525
for field in other {
2626
copy[field.name] = field.value
2727
}
2828

2929
return copy
3030
}
31+
32+
/// Append or update a value in header.
33+
///
34+
/// Example:
35+
/// ```swift
36+
/// var headers: HTTPFields = [
37+
/// "Prefer": "count=exact,return=representation"
38+
/// ]
39+
///
40+
/// headers.appendOrUpdate(.prefer, value: "return=minimal")
41+
/// #expect(headers == ["Prefer": "count=exact,return=minimal"]
42+
/// ```
43+
package mutating func appendOrUpdate(
44+
_ name: HTTPField.Name,
45+
value: String,
46+
separator: String = ","
47+
) {
48+
if let currentValue = self[name] {
49+
var components = currentValue.components(separatedBy: separator)
50+
51+
if let key = value.split(separator: "=").first,
52+
let index = components.firstIndex(where: { $0.hasPrefix("\(key)=") })
53+
{
54+
components[index] = value
55+
} else {
56+
components.append(value)
57+
}
58+
59+
self[name] = components.joined(separator: separator)
60+
} else {
61+
self[name] = value
62+
}
63+
}
3164
}
3265

33-
package extension HTTPField.Name {
34-
static let xClientInfo = HTTPField.Name("X-Client-Info")!
35-
static let xRegion = HTTPField.Name("x-region")!
36-
static let xRelayError = HTTPField.Name("x-relay-error")!
66+
extension HTTPField.Name {
67+
package static let xClientInfo = HTTPField.Name("X-Client-Info")!
68+
package static let xRegion = HTTPField.Name("x-region")!
69+
package static let xRelayError = HTTPField.Name("x-relay-error")!
3770
}

Sources/Storage/Codable.swift

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,34 +9,19 @@ import ConcurrencyExtras
99
import Foundation
1010

1111
extension JSONEncoder {
12+
@available(*, deprecated, message: "Access to storage encoder is going to be removed.")
1213
public static let defaultStorageEncoder: JSONEncoder = {
1314
let encoder = JSONEncoder()
1415
encoder.keyEncodingStrategy = .convertToSnakeCase
1516
return encoder
1617
}()
18+
19+
static let unconfiguredEncoder: JSONEncoder = .init()
1720
}
1821

1922
extension JSONDecoder {
20-
public static let defaultStorageDecoder: JSONDecoder = {
21-
let decoder = JSONDecoder()
22-
let formatter = LockIsolated(ISO8601DateFormatter())
23-
formatter.withValue {
24-
$0.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
25-
}
26-
27-
decoder.dateDecodingStrategy = .custom { decoder in
28-
let container = try decoder.singleValueContainer()
29-
let string = try container.decode(String.self)
30-
31-
if let date = formatter.withValue({ $0.date(from: string) }) {
32-
return date
33-
}
34-
35-
throw DecodingError.dataCorruptedError(
36-
in: container, debugDescription: "Invalid date format: \(string)"
37-
)
38-
}
39-
40-
return decoder
41-
}()
23+
@available(*, deprecated, message: "Access to storage decoder is going to be removed.")
24+
public static var defaultStorageDecoder: JSONDecoder {
25+
.default
26+
}
4227
}

Sources/Storage/Deprecated.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ extension StorageClientConfiguration {
1111
@available(
1212
*,
1313
deprecated,
14-
message: "Replace usages of this initializer with new init(url:headers:encoder:decoder:session:logger)"
14+
message:
15+
"Replace usages of this initializer with new init(url:headers:encoder:decoder:session:logger)"
1516
)
1617
public init(
1718
url: URL,
@@ -101,7 +102,8 @@ extension StorageFileApi {
101102
@available(
102103
*,
103104
deprecated,
104-
message: "File was deprecated and it isn't used in the package anymore, if you're using it on your application, consider replacing it as it will be removed on the next major release."
105+
message:
106+
"File was deprecated and it isn't used in the package anymore, if you're using it on your application, consider replacing it as it will be removed on the next major release."
105107
)
106108
public struct File: Hashable, Equatable {
107109
public var name: String
@@ -121,7 +123,8 @@ public struct File: Hashable, Equatable {
121123
*,
122124
deprecated,
123125
renamed: "MultipartFormData",
124-
message: "FormData was deprecated in favor of MultipartFormData, and it isn't used in the package anymore, if you're using it on your application, consider replacing it as it will be removed on the next major release."
126+
message:
127+
"FormData was deprecated in favor of MultipartFormData, and it isn't used in the package anymore, if you're using it on your application, consider replacing it as it will be removed on the next major release."
125128
)
126129
public class FormData {
127130
var files: [File] = []

Sources/Storage/Helpers.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import Helpers
2727
kUTTagClassFilenameExtension, pathExtension as CFString, nil
2828
)?.takeRetainedValue(),
2929
let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?
30-
.takeRetainedValue()
30+
.takeRetainedValue()
3131
{
3232
return contentType as String
3333
}
@@ -43,7 +43,7 @@ import Helpers
4343
kUTTagClassFilenameExtension, pathExtension as CFString, nil
4444
)?.takeRetainedValue(),
4545
let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?
46-
.takeRetainedValue()
46+
.takeRetainedValue()
4747
{
4848
return contentType as String
4949
}
@@ -62,7 +62,7 @@ import Helpers
6262
kUTTagClassFilenameExtension, pathExtension as CFString, nil
6363
)?.takeRetainedValue(),
6464
let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?
65-
.takeRetainedValue()
65+
.takeRetainedValue()
6666
{
6767
return contentType as String
6868
}

Sources/Storage/StorageApi.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Foundation
2-
import Helpers
32
import HTTPTypes
3+
import Helpers
44

55
#if canImport(FoundationNetworking)
66
import FoundationNetworking
@@ -36,7 +36,7 @@ public class StorageApi: @unchecked Sendable {
3636

3737
let response = try await http.send(request)
3838

39-
guard (200 ..< 300).contains(response.statusCode) else {
39+
guard (200..<300).contains(response.statusCode) else {
4040
if let error = try? configuration.decoder.decode(
4141
StorageError.self,
4242
from: response.data

Sources/Storage/StorageError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ public struct StorageError: Error, Decodable, Sendable {
55
public var message: String
66
public var error: String?
77

8-
public init(statusCode: String?, message: String, error: String?) {
8+
public init(statusCode: String? = nil, message: String, error: String? = nil) {
99
self.statusCode = statusCode
1010
self.message = message
1111
self.error = error

0 commit comments

Comments
 (0)