Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
59 changes: 33 additions & 26 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,33 @@ on:
- main
- release/*
paths:
- 'Sources/**'
- 'Tests/**'
- 'Examples/**'
- '*.swift'
- 'Package.swift'
- 'Package.resolved'
- '.github/workflows/ci.yml'
- 'Makefile'
- '*.xcodeproj/**'
- '*.xcworkspace/**'
- '.swiftpm/**'
- "Sources/**"
- "Tests/**"
- "Examples/**"
- "*.swift"
- "Package.swift"
- "Package.resolved"
- ".github/workflows/ci.yml"
- "Makefile"
- "*.xcodeproj/**"
- "*.xcworkspace/**"
- ".swiftpm/**"
pull_request:
branches:
- "*"
- release/*
paths:
- 'Sources/**'
- 'Tests/**'
- 'Examples/**'
- '*.swift'
- 'Package.swift'
- 'Package.resolved'
- '.github/workflows/ci.yml'
- 'Makefile'
- '*.xcodeproj/**'
- '*.xcworkspace/**'
- '.swiftpm/**'
- "Sources/**"
- "Tests/**"
- "Examples/**"
- "*.swift"
- "Package.swift"
- "Package.resolved"
- ".github/workflows/ci.yml"
- "Makefile"
- "*.xcodeproj/**"
- "*.xcworkspace/**"
- ".swiftpm/**"
workflow_dispatch:

concurrency:
Expand Down Expand Up @@ -119,7 +119,7 @@ jobs:
spm:
runs-on: macos-15
strategy:
matrix:
matrix:
config: [debug, release]
steps:
- uses: actions/checkout@v5
Expand All @@ -136,11 +136,18 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: "Remove IntegrationTests"
run: rm -r Tests/IntegrationTests/*
- name: "Cache Swift Package"
uses: actions/cache@v4
with:
path: .build
key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
restore-keys: |
${{ runner.os }}-spm-
- name: "Build Swift Package"
run: swift build

- name: "Test Swift Package"
run: swift test --skip IntegrationTests

# android:
# name: Android
# runs-on: ubuntu-latest
Expand Down
70 changes: 48 additions & 22 deletions Sources/Storage/MultipartFormData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -419,8 +419,17 @@ class MultipartFormData {
var buffer = [UInt8](repeating: 0, count: streamBufferSize)
let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize)

if let error = inputStream.streamError {
throw MultipartFormDataError.inputStreamReadFailed(error: error)
if bytesRead < 0 {
if let error = inputStream.streamError {
throw MultipartFormDataError.inputStreamReadFailed(error: error)
} else {
throw MultipartFormDataError.inputStreamReadFailed(
error: MultipartFormDataError.UnexpectedInputStreamLength(
bytesExpected: bodyPart.bodyContentLength,
bytesRead: UInt64(encoded.count)
)
)
}
}

if bytesRead > 0 {
Expand Down Expand Up @@ -474,9 +483,17 @@ class MultipartFormData {
let bufferSize = min(streamBufferSize, Int(bytesLeftToRead))
var buffer = [UInt8](repeating: 0, count: bufferSize)
let bytesRead = inputStream.read(&buffer, maxLength: bufferSize)

if let streamError = inputStream.streamError {
throw MultipartFormDataError.inputStreamReadFailed(error: streamError)
if bytesRead < 0 {
if let streamError = inputStream.streamError {
throw MultipartFormDataError.inputStreamReadFailed(error: streamError)
} else {
throw MultipartFormDataError.inputStreamReadFailed(
error: MultipartFormDataError.UnexpectedInputStreamLength(
bytesExpected: bodyPart.bodyContentLength,
bytesRead: bodyPart.bodyContentLength - bytesLeftToRead
)
)
}
}

if bytesRead > 0 {
Expand Down Expand Up @@ -514,8 +531,17 @@ class MultipartFormData {
while bytesToWrite > 0, outputStream.hasSpaceAvailable {
let bytesWritten = outputStream.write(buffer, maxLength: bytesToWrite)

if let error = outputStream.streamError {
throw MultipartFormDataError.outputStreamWriteFailed(error: error)
if bytesWritten < 0 {
if let error = outputStream.streamError {
throw MultipartFormDataError.outputStreamWriteFailed(error: error)
} else {
throw MultipartFormDataError.outputStreamWriteFailed(
error: MultipartFormDataError.UnexpectedInputStreamLength(
bytesExpected: UInt64(buffer.count),
bytesRead: UInt64(buffer.count - bytesToWrite)
)
)
}
}

bytesToWrite -= bytesWritten
Expand Down Expand Up @@ -650,10 +676,10 @@ enum MultipartFormDataError: Error {

var underlyingError: (any Error)? {
switch self {
case let .bodyPartFileNotReachableWithError(_, error),
let .bodyPartFileSizeQueryFailedWithError(_, error),
let .inputStreamReadFailed(error),
let .outputStreamWriteFailed(error):
case .bodyPartFileNotReachableWithError(_, let error),
.bodyPartFileSizeQueryFailedWithError(_, let error),
.inputStreamReadFailed(let error),
.outputStreamWriteFailed(let error):
error

case .bodyPartURLInvalid,
Expand All @@ -671,17 +697,17 @@ enum MultipartFormDataError: Error {

var url: URL? {
switch self {
case let .bodyPartURLInvalid(url),
let .bodyPartFilenameInvalid(url),
let .bodyPartFileNotReachable(url),
let .bodyPartFileNotReachableWithError(url, _),
let .bodyPartFileIsDirectory(url),
let .bodyPartFileSizeNotAvailable(url),
let .bodyPartFileSizeQueryFailedWithError(url, _),
let .bodyPartInputStreamCreationFailed(url),
let .outputStreamFileAlreadyExists(url),
let .outputStreamURLInvalid(url),
let .outputStreamCreationFailed(url):
case .bodyPartURLInvalid(let url),
.bodyPartFilenameInvalid(let url),
.bodyPartFileNotReachable(let url),
.bodyPartFileNotReachableWithError(let url, _),
.bodyPartFileIsDirectory(let url),
.bodyPartFileSizeNotAvailable(let url),
.bodyPartFileSizeQueryFailedWithError(let url, _),
.bodyPartInputStreamCreationFailed(let url),
.outputStreamFileAlreadyExists(let url),
.outputStreamURLInvalid(let url),
.outputStreamCreationFailed(let url):
url

case .inputStreamReadFailed, .outputStreamWriteFailed:
Expand Down
49 changes: 26 additions & 23 deletions Tests/HelpersTests/HTTPErrorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import Foundation
import Helpers
import XCTest

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

final class HTTPErrorTests: XCTestCase {

func testInitialization() {
Expand All @@ -19,9 +23,9 @@ final class HTTPErrorTests: XCTestCase {
httpVersion: "1.1",
headerFields: ["Content-Type": "application/json"]
)!

let error = HTTPError(data: data, response: response)

XCTAssertEqual(error.data, data)
XCTAssertEqual(error.response, response)
}
Expand All @@ -34,9 +38,9 @@ final class HTTPErrorTests: XCTestCase {
httpVersion: "1.1",
headerFields: nil
)!

let error = HTTPError(data: data, response: response)

XCTAssertEqual(
error.errorDescription,
"Status Code: 400 Body: Bad Request: Invalid parameters"
Expand All @@ -51,9 +55,9 @@ final class HTTPErrorTests: XCTestCase {
httpVersion: "1.1",
headerFields: nil
)!

let error = HTTPError(data: data, response: response)

XCTAssertEqual(error.errorDescription, "Status Code: 404 Body: ")
}

Expand All @@ -67,29 +71,29 @@ final class HTTPErrorTests: XCTestCase {
httpVersion: "1.1",
headerFields: nil
)!

let error = HTTPError(data: data, response: response)

XCTAssertEqual(error.errorDescription, "Status Code: 500")
}

func testLocalizedErrorDescription_WithJSONData() {
let jsonString = """
{
"error": "Validation failed",
"details": "Email format is invalid"
}
"""
{
"error": "Validation failed",
"details": "Email format is invalid"
}
"""
let data = Data(jsonString.utf8)
let response = HTTPURLResponse(
url: URL(string: "https://example.com")!,
statusCode: 422,
httpVersion: "1.1",
headerFields: ["Content-Type": "application/json"]
)!

let error = HTTPError(data: data, response: response)

XCTAssertEqual(
error.errorDescription,
"Status Code: 422 Body: \(jsonString)"
Expand All @@ -105,9 +109,9 @@ final class HTTPErrorTests: XCTestCase {
httpVersion: "1.1",
headerFields: nil
)!

let error = HTTPError(data: data, response: response)

XCTAssertEqual(
error.errorDescription,
"Status Code: 400 Body: \(message)"
Expand All @@ -123,16 +127,15 @@ final class HTTPErrorTests: XCTestCase {
httpVersion: "1.1",
headerFields: nil
)!

let error = HTTPError(data: data, response: response)

XCTAssertEqual(
error.errorDescription,
"Status Code: 413 Body: \(largeMessage)"
)
}


func testProperties() {
let data = Data("test error".utf8)
let response = HTTPURLResponse(
Expand All @@ -141,13 +144,13 @@ final class HTTPErrorTests: XCTestCase {
httpVersion: "1.1",
headerFields: ["Content-Type": "application/json"]
)!

let error = HTTPError(data: data, response: response)

// Test that properties are correctly set
XCTAssertEqual(error.data, data)
XCTAssertEqual(error.response, response)
XCTAssertEqual(error.response.statusCode, 400)
XCTAssertEqual(error.response.url, URL(string: "https://example.com")!)
}
}
}
10 changes: 8 additions & 2 deletions Tests/HelpersTests/LoggerInterceptorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@
// Created by Coverage Tests
//

import Foundation
import HTTPTypes
import XCTest

@testable import Helpers
import HTTPTypes

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

final class LoggerInterceptorTests: XCTestCase {

Expand Down Expand Up @@ -69,7 +75,7 @@ final class LoggerInterceptorTests: XCTestCase {
}

// Verify request was logged
XCTAssertEqual(logger.verboseLogs.count, 2) // Request and response
XCTAssertEqual(logger.verboseLogs.count, 2) // Request and response
XCTAssertTrue(logger.verboseLogs[0].contains("Request:"))
XCTAssertTrue(logger.verboseLogs[0].contains("/users"))
}
Expand Down
13 changes: 12 additions & 1 deletion Tests/RealtimeTests/PushV2Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
//

import ConcurrencyExtras
import Foundation
import XCTest

@testable import Realtime

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

final class PushV2Tests: XCTestCase {

func testPushStatusValues() {
Expand Down Expand Up @@ -334,6 +339,12 @@ private final class MockRealtimeClient: RealtimeClientProtocol, @unchecked Senda

private struct MockHTTPClient: HTTPClientType {
func send(_ request: HTTPRequest) async throws -> HTTPResponse {
return HTTPResponse(data: Data(), response: HTTPURLResponse())
let urlResponse = HTTPURLResponse(
url: URL(string: "https://example.com")!,
statusCode: 200,
httpVersion: nil,
headerFields: nil
)!
return HTTPResponse(data: Data(), response: urlResponse)
}
}
Loading
Loading