Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(storage): increase code coverage #650

Merged
merged 4 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions .swiftpm/xcode/xcshareddata/xcschemes/Storage.xcscheme
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.7">
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
Expand All @@ -26,8 +26,29 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES"
onlyGenerateCoverageForSpecifiedTargets = "YES">
<CodeCoverageTargets>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Storage"
BuildableName = "Storage"
BlueprintName = "Storage"
ReferencedContainer = "container:">
</BuildableReference>
</CodeCoverageTargets>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "StorageTests"
BuildableName = "StorageTests"
BlueprintName = "StorageTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
Expand Down
5 changes: 4 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,13 @@ let package = Package(
.product(name: "CustomDump", package: "swift-custom-dump"),
.product(name: "InlineSnapshotTesting", package: "swift-snapshot-testing"),
.product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
"Mocker",
"TestHelpers",
"Storage",
],
resources: [
.copy("sadcat.jpg")
.copy("sadcat.jpg"),
.process("Fixtures"),
]
),
.target(
Expand Down
36 changes: 36 additions & 0 deletions Sources/Helpers/Codable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// Codable.swift
// Supabase
//
// Created by Guilherme Souza on 20/01/25.
//

import ConcurrencyExtras
import Foundation

extension JSONDecoder {
private static let supportedDateFormatters: [UncheckedSendable<ISO8601DateFormatter>] = [
ISO8601DateFormatter.iso8601WithFractionalSeconds,
ISO8601DateFormatter.iso8601,
]

/// Default `JSONDecoder` for decoding types from Supabase.
package static let `default`: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom { decoder in
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)

for formatter in supportedDateFormatters {
if let date = formatter.value.date(from: string) {
return date
}
}

throw DecodingError.dataCorruptedError(
in: container, debugDescription: "Invalid date format: \(string)"
)
}
return decoder
}()
}
61 changes: 47 additions & 14 deletions Sources/Helpers/HTTP/HTTPFields.swift
Original file line number Diff line number Diff line change
@@ -1,37 +1,70 @@
import HTTPTypes

package extension HTTPFields {
init(_ dictionary: [String: String]) {
extension HTTPFields {
package init(_ dictionary: [String: String]) {
self.init(dictionary.map { .init(name: .init($0.key)!, value: $0.value) })
}
var dictionary: [String: String] {

package var dictionary: [String: String] {
let keyValues = self.map {
($0.name.rawName, $0.value)
}

return .init(keyValues, uniquingKeysWith: { $1 })
}
mutating func merge(with other: Self) {

package mutating func merge(with other: Self) {
for field in other {
self[field.name] = field.value
}
}
func merging(with other: Self) -> Self {

package func merging(with other: Self) -> Self {
var copy = self

for field in other {
copy[field.name] = field.value
}

return copy
}

/// Append or update a value in header.
///
/// Example:
/// ```swift
/// var headers: HTTPFields = [
/// "Prefer": "count=exact,return=representation"
/// ]
///
/// headers.appendOrUpdate(.prefer, value: "return=minimal")
/// #expect(headers == ["Prefer": "count=exact,return=minimal"]
/// ```
package mutating func appendOrUpdate(
_ name: HTTPField.Name,
value: String,
separator: String = ","
) {
if let currentValue = self[name] {
var components = currentValue.components(separatedBy: separator)

if let key = value.split(separator: "=").first,
let index = components.firstIndex(where: { $0.hasPrefix("\(key)=") })
{
components[index] = value
} else {
components.append(value)
}

self[name] = components.joined(separator: separator)
} else {
self[name] = value
}
}
}

package extension HTTPField.Name {
static let xClientInfo = HTTPField.Name("X-Client-Info")!
static let xRegion = HTTPField.Name("x-region")!
static let xRelayError = HTTPField.Name("x-relay-error")!
extension HTTPField.Name {
package static let xClientInfo = HTTPField.Name("X-Client-Info")!
package static let xRegion = HTTPField.Name("x-region")!
package static let xRelayError = HTTPField.Name("x-relay-error")!
}
29 changes: 7 additions & 22 deletions Sources/Storage/Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,19 @@ import ConcurrencyExtras
import Foundation

extension JSONEncoder {
@available(*, deprecated, message: "Access to storage encoder is going to be removed.")
public static let defaultStorageEncoder: JSONEncoder = {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
return encoder
}()

static let unconfiguredEncoder: JSONEncoder = .init()
}

extension JSONDecoder {
public static let defaultStorageDecoder: JSONDecoder = {
let decoder = JSONDecoder()
let formatter = LockIsolated(ISO8601DateFormatter())
formatter.withValue {
$0.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
}

decoder.dateDecodingStrategy = .custom { decoder in
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)

if let date = formatter.withValue({ $0.date(from: string) }) {
return date
}

throw DecodingError.dataCorruptedError(
in: container, debugDescription: "Invalid date format: \(string)"
)
}

return decoder
}()
@available(*, deprecated, message: "Access to storage decoder is going to be removed.")
public static var defaultStorageDecoder: JSONDecoder {
.default
}
}
9 changes: 6 additions & 3 deletions Sources/Storage/Deprecated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ extension StorageClientConfiguration {
@available(
*,
deprecated,
message: "Replace usages of this initializer with new init(url:headers:encoder:decoder:session:logger)"
message:
"Replace usages of this initializer with new init(url:headers:encoder:decoder:session:logger)"
)
public init(
url: URL,
Expand Down Expand Up @@ -101,7 +102,8 @@ extension StorageFileApi {
@available(
*,
deprecated,
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."
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."
)
public struct File: Hashable, Equatable {
public var name: String
Expand All @@ -121,7 +123,8 @@ public struct File: Hashable, Equatable {
*,
deprecated,
renamed: "MultipartFormData",
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."
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."
)
public class FormData {
var files: [File] = []
Expand Down
6 changes: 3 additions & 3 deletions Sources/Storage/Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import Helpers
kUTTagClassFilenameExtension, pathExtension as CFString, nil
)?.takeRetainedValue(),
let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?
.takeRetainedValue()
.takeRetainedValue()
{
return contentType as String
}
Expand All @@ -43,7 +43,7 @@ import Helpers
kUTTagClassFilenameExtension, pathExtension as CFString, nil
)?.takeRetainedValue(),
let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?
.takeRetainedValue()
.takeRetainedValue()
{
return contentType as String
}
Expand All @@ -62,7 +62,7 @@ import Helpers
kUTTagClassFilenameExtension, pathExtension as CFString, nil
)?.takeRetainedValue(),
let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?
.takeRetainedValue()
.takeRetainedValue()
{
return contentType as String
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/Storage/StorageApi.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation
import Helpers
import HTTPTypes
import Helpers

#if canImport(FoundationNetworking)
import FoundationNetworking
Expand Down Expand Up @@ -36,7 +36,7 @@ public class StorageApi: @unchecked Sendable {

let response = try await http.send(request)

guard (200 ..< 300).contains(response.statusCode) else {
guard (200..<300).contains(response.statusCode) else {
if let error = try? configuration.decoder.decode(
StorageError.self,
from: response.data
Expand Down
2 changes: 1 addition & 1 deletion Sources/Storage/StorageError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ public struct StorageError: Error, Decodable, Sendable {
public var message: String
public var error: String?

public init(statusCode: String?, message: String, error: String?) {
public init(statusCode: String? = nil, message: String, error: String? = nil) {
self.statusCode = statusCode
self.message = message
self.error = error
Expand Down
Loading
Loading