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

BIT-91: Stub out API services #11

Merged
merged 5 commits into from
Aug 29, 2023
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A protocol for an API service used to make account requests.
///
protocol AccountAPIService {}

extension APIService: AccountAPIService {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A protocol for an API service used to make audit requests.
///
protocol AuditAPIService {}

extension APIService: AuditAPIService {}
5 changes: 5 additions & 0 deletions BitwardenShared/Core/Services/Auth/API/AuthAPIService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A protocol for an API service used to make auth requests.
///
protocol AuthAPIService {}

extension APIService: AuthAPIService {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A protocol for an API service used to make device requests.
///
protocol DeviceAPIService {}

extension APIService: DeviceAPIService {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A protocol for an API service used to make organization requests.
///
protocol OrganizationAPIService {}

extension APIService: OrganizationAPIService {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A protocol for an API service used to make organization user requests.
///
protocol OrganizationUserAPIService {}

extension APIService: OrganizationUserAPIService {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A protocol for an API service used to make passwordless login requests.
///
protocol PasswordlessLoginAPIService {}

extension APIService: PasswordlessLoginAPIService {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A protocol for an API service used to make two factor auth requests.
///
protocol TwoFactorAPIService {}

extension APIService: TwoFactorAPIService {}
30 changes: 30 additions & 0 deletions BitwardenShared/Core/Services/Platform/API/APIService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Foundation
import Networking

/// A service used by the application to make API requests.
///
class APIService {
// MARK: Properties

/// The API service that is used for general requests.
let apiService: HTTPService

/// The API service used for logging events.
let eventsService: HTTPService

/// The API service used for user identity requests.
let identityService: HTTPService

// MARK: Initialization

/// Initialize an `APIService` used to make API requests.
///
/// - Parameter client: The underlying `HTTPClient` that performs the network request. Defaults
/// to `URLSession.shared`.
///
init(client: HTTPClient = URLSession.shared) {
apiService = HTTPService(baseURL: URL(string: "https://vault.bitwarden.com/api")!, client: client)
eventsService = HTTPService(baseURL: URL(string: "https://vault.bitwarden.com/events")!, client: client)
identityService = HTTPService(baseURL: URL(string: "https://vault.bitwarden.com/identity")!, client: client)
}
fedemkr marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import XCTest

@testable import BitwardenShared
@testable import Networking

class APIServiceTests: BitwardenTestCase {
var subject: APIService!

override func setUp() {
super.setUp()

subject = APIService()
}

override func tearDown() {
super.tearDown()

subject = nil
}

/// `init(client:)` sets the default base URLs for the HTTP services.
func testInitDefaultURLs() {
let apiServiceBaseURL = subject.apiService.baseURL
XCTAssertEqual(apiServiceBaseURL, URL(string: "https://vault.bitwarden.com/api")!)

let eventsServiceBaseURL = subject.eventsService.baseURL
XCTAssertEqual(eventsServiceBaseURL, URL(string: "https://vault.bitwarden.com/events")!)

let identityServiceBaseURL = subject.identityService.baseURL
XCTAssertEqual(identityServiceBaseURL, URL(string: "https://vault.bitwarden.com/identity")!)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A protocol for an API service used to make config requests.
///
protocol ConfigAPIService {}

extension APIService: ConfigAPIService {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A protocol for an API service used to make event requests.
///
protocol EventAPIService {}

extension APIService: EventAPIService {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Foundation

/// A type that wraps fixture data for use in mocking API responses during tests.
///
struct APITestData {
let data: Data

static func loadFromBundle(resource: String, extension: String) -> APITestData {
let bundle = Bundle(for: BitwardenTestCase.self)
guard let url = bundle.url(forResource: resource, withExtension: `extension`) else {
fatalError("Unable to locate file \(resource).\(`extension`) in the bundle.")
}
do {
return try APITestData(data: Data(contentsOf: url))
} catch {
fatalError("Unable to load data from \(resource).\(`extension`) in the bundle. Error: \(error)")
}
}
}

extension APITestData {
// Create static instances to load API responses from the bundle or static data.
// Example:
// static let getSync = loadFromBundle(resource: "getSync", extension: "json")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Networking

/// An `HTTPClient` that can be used to return mocked responses.
///
class MockHTTPClient: HTTPClient {
fedemkr marked this conversation as resolved.
Show resolved Hide resolved
/// A list of requests that have been received by the HTTP client.
var requests: [HTTPRequest] = []

/// Gets the next result or sets a single result for the HTTP client to return for the next request.
var result: Result<HTTPResponse, Error>? {
get {
results.first
}
set {
guard let newValue else {
results.removeAll()
return
}
results = [newValue]
}
}

/// A list of results that will be returned in order for future requests.
var results: [Result<HTTPResponse, Error>] = []

/// Sends a request and returns a mock response, if one exists.
///
/// - Parameter request: The request to make on the client.
/// - Returns: A mock response for the request, if one exists.
///
func send(_ request: HTTPRequest) async throws -> HTTPResponse {
requests.append(request)

guard !results.isEmpty else { throw MockHTTPClientError.noResultForRequest }

let result = results.removeFirst()
return try result.get()
}
}

/// Errors thrown by `MockHTTPClient`.
enum MockHTTPClientError: Error {
/// There's no results set to
case noResultForRequest
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Foundation
import Networking

extension Result where Success == HTTPResponse, Error: Error {
static func httpSuccess(testData: APITestData) -> Result<HTTPResponse, Error> {
let response = HTTPResponse(
url: URL(string: "https://example.com")!,
fedemkr marked this conversation as resolved.
Show resolved Hide resolved
statusCode: 200,
headers: [:],
body: testData.data,
requestID: UUID()
)
return .success(response)
}

static func httpFailure(
statusCode: Int = 500,
headers: [String: String] = [:],
data: Data = Data()
) -> Result<HTTPResponse, Error> {
let response = HTTPResponse(
url: URL(string: "https://example.com")!,
statusCode: statusCode,
headers: headers,
body: data,
requestID: UUID()
)
return .success(response)
}

static func httpFailure(_ error: Error) -> Result<HTTPResponse, Error> {
.failure(error)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A protocol for an API service used to make send requests.
///
protocol SendAPIService {}

extension APIService: SendAPIService {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A protocol for an API service used to make attachment requests.
///
protocol AttachmentAPIService {}

extension APIService: AttachmentAPIService {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A protocol for an API service used to make cipher requests.
///
protocol CipherAPIService {}

extension APIService: CipherAPIService {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A protocol for an API service used to make folder requests.
///
protocol FolderAPIService {}

extension APIService: FolderAPIService {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A protocol for an API service used to make sync requests.
///
protocol SyncAPIService {}

extension APIService: SyncAPIService {}