[BWA-155] refactor: Move ErrorResponseModel to BitwardenKit (#1480)

This commit is contained in:
Katherine Bertelsen 2025-04-04 15:47:57 -05:00 committed by GitHub
parent 91fbe6947e
commit 679ed571a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 204 additions and 134 deletions

View File

@ -1,24 +0,0 @@
import TestHelpers
import XCTest
@testable import AuthenticatorShared
// MARK: - ErrorResponseModelTests
class ErrorResponseModelTests: BitwardenTestCase {
/// Tests that `singleMessage()` returns the validation error's message.
func testSingleMessage() throws {
let json = APITestData.createAccountAccountAlreadyExists.data
let decoder = JSONDecoder()
let subject = try decoder.decode(ErrorResponseModel.self, from: json)
XCTAssertEqual(subject.singleMessage(), "Email 'j@a.com' is already taken.")
}
/// Tests that `singleMessage()` returns an error message when there are no validation errors.
func testSingleMessageNilValidationErrors() throws {
let json = APITestData.createAccountNilValidationErrors.data
let decoder = JSONDecoder()
let subject = try decoder.decode(ErrorResponseModel.self, from: json)
XCTAssertEqual(subject.singleMessage(), "The model state is invalid.")
}
}

View File

@ -1,3 +1,4 @@
import BitwardenKit
import Networking
import TestHelpers
import XCTest

View File

@ -1,3 +1,5 @@
import BitwardenKit
// MARK: - ServerError
/// An enumeration of server errors.

View File

@ -1,3 +1,4 @@
import BitwardenKit
import Networking
// MARK: - ResponseValidationError

View File

@ -1,3 +1,4 @@
import BitwardenKit
import Networking
import TestHelpers
import XCTest

View File

@ -1,5 +1,6 @@
// swiftlint:disable:this file_name
import BitwardenKit
import Foundation
import Networking
import TestHelpers

View File

@ -4,6 +4,6 @@ import XCTest
open class BitwardenTestCase: BaseBitwardenTestCase {
@MainActor
override open class func setUp() {
TestDataHelpers.bundleClass = Self.self
TestDataHelpers.defaultBundle = Bundle(for: Self.self)
}
}

View File

@ -5,14 +5,22 @@ import Networking
/// An error response returned from an API request.
///
struct ErrorResponseModel: Codable, Equatable {
public struct ErrorResponseModel: Codable, Equatable {
// MARK: Properties
/// Validation errors returned from the API request.
let validationErrors: [String: [String]]?
public let validationErrors: [String: [String]]?
/// The error message.
let message: String
public let message: String
// MARK: Initializers
/// Public version of synthesized initializer.
public init(validationErrors: [String : [String]]?, message: String) {
self.validationErrors = validationErrors
self.message = message
}
// MARK: Methods
@ -20,7 +28,7 @@ struct ErrorResponseModel: Codable, Equatable {
///
/// - Returns: The validation error or an error message.
///
func singleMessage() -> String {
public func singleMessage() -> String {
guard let validationErrors, !validationErrors.isEmpty else { return message }
return validationErrors.values.first { values in
@ -32,5 +40,5 @@ struct ErrorResponseModel: Codable, Equatable {
// MARK: JSONResponse
extension ErrorResponseModel: JSONResponse {
static var decoder = JSONDecoder.pascalOrSnakeCaseDecoder
public static let decoder = JSONDecoder.pascalOrSnakeCaseDecoder
}

View File

@ -1,7 +1,7 @@
import TestHelpers
import XCTest
@testable import BitwardenShared
@testable import BitwardenKit
// MARK: - ErrorResponseModelTests

View File

@ -0,0 +1,143 @@
import Foundation
import TestHelpers
public extension APITestData {
// MARK: Account Revision Date
/// Test data for an account revision date.
static func accountRevisionDate( // swiftlint:disable:this type_contents_order
_ date: Date = Date(timeIntervalSince1970: 1_704_067_200)
) -> APITestData {
APITestData(data: Data(String(date.timeIntervalSince1970 * 1000).utf8))
}
// MARK: Create Account
/// Test data with a validation error of "Email 'j@a.com' is already taken."
static let createAccountAccountAlreadyExists = loadFromJsonBundle(
resource: "CreateAccountAccountAlreadyExists",
bundle: BitwardenKitMocksBundleFinder.bundle
)
/// Test data with a CAPTCHA validation error.
static let createAccountCaptchaFailure = loadFromJsonBundle(
resource: "CreateAccountCaptchaFailure",
bundle: BitwardenKitMocksBundleFinder.bundle
)
/// Test data with a validation error of "The field Email must be a string with a maximum length of 256."
static let createAccountEmailExceedsMaxLength = loadFromJsonBundle(
resource: "CreateAccountEmailExceedsMaxLength",
bundle: BitwardenKitMocksBundleFinder.bundle
)
/// Test data with a validation error of "The field MasterPasswordHint must be a string with a maximum length of 50."
static let createAccountHintTooLong = loadFromJsonBundle(
resource: "CreateAccountHintTooLong",
bundle: BitwardenKitMocksBundleFinder.bundle
)
/// Test data with a validation error of "The Email field is not a supported e-mail address format."
static let createAccountInvalidEmailFormat = loadFromJsonBundle(
resource: "CreateAccountInvalidEmailFormat",
bundle: BitwardenKitMocksBundleFinder.bundle
)
/// Test data of an invalid model state with no validation errors.
static let createAccountNilValidationErrors = loadFromJsonBundle(
resource: "CreateAccountNilValidationErrors",
bundle: BitwardenKitMocksBundleFinder.bundle
)
/// Test data of a request to create an account for `example@email.com`
static let createAccountRequest = loadFromJsonBundle(
resource: "CreateAccountRequest",
bundle: BitwardenKitMocksBundleFinder.bundle
)
/// Test data of a successful account creation.
static let createAccountSuccess = loadFromJsonBundle(
resource: "CreateAccountSuccess",
bundle: BitwardenKitMocksBundleFinder.bundle
)
/// Test data with a validation error of "User verification failed."
static let deleteAccountRequestFailure = loadFromJsonBundle(
resource: "DeleteAccountRequestFailure",
bundle: BitwardenKitMocksBundleFinder.bundle
)
/// Test data with several leaked passwords.
static let hibpLeakedPasswords = loadFromBundle(
resource: "hibpLeakedPasswords",
extension: "txt",
bundle: BitwardenKitMocksBundleFinder.bundle
)
/// Test data with an invalid username/password error.
static let responseValidationError = loadFromJsonBundle(
resource: "ResponseValidationError",
bundle: BitwardenKitMocksBundleFinder.bundle
)
// MARK: Pre-Login
/// Test data for prelogin success.
static let preLoginSuccess = loadFromJsonBundle(
resource: "PreLoginSuccess",
bundle: BitwardenKitMocksBundleFinder.bundle
)
// MARK: Request Password Hint
/// Test data for a failure for password hint.
static let passwordHintFailure = loadFromJsonBundle(
resource: "PasswordHintFailure",
bundle: BitwardenKitMocksBundleFinder.bundle
)
// MARK: Start Registration
/// Test data with a validation error of "Email 'j@a.com' is already taken."
static let startRegistrationEmailAlreadyExists = loadFromJsonBundle(
resource: "StartRegistrationEmailAlreadyExists",
bundle: BitwardenKitMocksBundleFinder.bundle
)
/// Test data with a validation error of "The field Email must be a string with a maximum length of 256."
static let startRegistrationEmailExceedsMaxLength = loadFromJsonBundle(
resource: "StartRegistrationEmailExceedsMaxLength",
bundle: BitwardenKitMocksBundleFinder.bundle
)
/// Test data with a validation error of
static let startRegistrationInvalidEmailFormat = loadFromJsonBundle(
resource: "StartRegistrationInvalidEmailFormat",
bundle: BitwardenKitMocksBundleFinder.bundle
)
/// Test data with a validation error of "The Email field is not a supported e-mail address format."
static let startRegistrationCaptchaFailure = loadFromJsonBundle(
resource: "StartRegistrationCaptchaFailure",
bundle: BitwardenKitMocksBundleFinder.bundle
)
/// Test data for success with a registration start.
static let startRegistrationSuccess = loadFromBundle(
resource: "StartRegistrationSuccess",
extension: "txt",
bundle: BitwardenKitMocksBundleFinder.bundle
)
// MARK: Verify Email Token
/// Test data indicating that the verify email token link has expired.
static let verifyEmailTokenExpiredLink = loadFromJsonBundle(
resource: "VerifyEmailTokenExpiredLink",
bundle: BitwardenKitMocksBundleFinder.bundle
)
}
class BitwardenKitMocksBundleFinder {
static let bundle = Bundle(for: BitwardenKitMocksBundleFinder.self)
}

View File

@ -1,36 +0,0 @@
import Foundation
import Networking
// MARK: - ErrorResponseModel
/// An error response returned from an API request.
///
struct ErrorResponseModel: Codable, Equatable {
// MARK: Properties
/// Validation errors returned from the API request.
let validationErrors: [String: [String]]?
/// The error message.
let message: String
// MARK: Methods
/// A method that returns the specific validation error or an error message if the validation error is nil.
///
/// - Returns: The validation error or an error message.
///
func singleMessage() -> String {
guard let validationErrors, !validationErrors.isEmpty else { return message }
return validationErrors.values.first { values in
!values.isEmpty
}?.first ?? message
}
}
// MARK: JSONResponse
extension ErrorResponseModel: JSONResponse {
static let decoder = JSONDecoder.pascalOrSnakeCaseDecoder
}

View File

@ -1,48 +0,0 @@
import Foundation
import TestHelpers
extension APITestData {
// MARK: Account Revision Date
static func accountRevisionDate( // swiftlint:disable:this type_contents_order
_ date: Date = Date(timeIntervalSince1970: 1_704_067_200)
) -> APITestData {
APITestData(data: Data(String(date.timeIntervalSince1970 * 1000).utf8))
}
// MARK: Create Account
static let createAccountAccountAlreadyExists = loadFromJsonBundle(resource: "CreateAccountAccountAlreadyExists")
static let createAccountCaptchaFailure = loadFromJsonBundle(resource: "CreateAccountCaptchaFailure")
static let createAccountEmailExceedsMaxLength = loadFromJsonBundle(resource: "CreateAccountEmailExceedsMaxLength")
static let createAccountHintTooLong = loadFromJsonBundle(resource: "CreateAccountHintTooLong")
static let createAccountInvalidEmailFormat = loadFromJsonBundle(resource: "CreateAccountInvalidEmailFormat")
static let createAccountNilValidationErrors = loadFromJsonBundle(resource: "CreateAccountNilValidationErrors")
static let createAccountRequest = loadFromJsonBundle(resource: "CreateAccountRequest")
static let createAccountSuccess = loadFromJsonBundle(resource: "CreateAccountSuccess")
static let deleteAccountRequestFailure = loadFromJsonBundle(resource: "DeleteAccountRequestFailure")
static let hibpLeakedPasswords = loadFromBundle(resource: "hibpLeakedPasswords", extension: "txt")
static let responseValidationError = loadFromJsonBundle(resource: "ResponseValidationError")
// MARK: Pre-Login
static let preLoginSuccess = loadFromJsonBundle(resource: "PreLoginSuccess")
// MARK: Request Password Hint
static let passwordHintFailure = loadFromJsonBundle(resource: "PasswordHintFailure")
// MARK: Start Registration
static let startRegistrationEmailAlreadyExists = loadFromJsonBundle(resource: "StartRegistrationEmailAlreadyExists")
static let startRegistrationEmailExceedsMaxLength = loadFromJsonBundle(
resource: "StartRegistrationEmailExceedsMaxLength"
)
static let startRegistrationInvalidEmailFormat = loadFromJsonBundle(resource: "StartRegistrationInvalidEmailFormat")
static let startRegistrationCaptchaFailure = loadFromJsonBundle(resource: "StartRegistrationCaptchaFailure")
static let startRegistrationSuccess = loadFromBundle(resource: "StartRegistrationSuccess", extension: "txt")
// MARK: Verify Email Token
static let verifyEmailTokenExpiredLink = loadFromJsonBundle(resource: "VerifyEmailTokenExpiredLink")
}

View File

@ -1,3 +1,4 @@
import BitwardenKit
import Foundation
import Networking

View File

@ -1,3 +1,4 @@
import BitwardenKit
import Networking
import TestHelpers
import XCTest

View File

@ -1,3 +1,4 @@
import BitwardenKit
import Foundation
import Networking

View File

@ -1,3 +1,4 @@
import BitwardenKit
import Networking
import TestHelpers
import XCTest

View File

@ -1,3 +1,4 @@
import BitwardenKit
import Foundation
import Networking

View File

@ -1,3 +1,4 @@
import BitwardenKit
import Networking
import TestHelpers
import XCTest

View File

@ -1,3 +1,4 @@
import BitwardenKit
import Foundation
// MARK: - ServerError

View File

@ -1,3 +1,4 @@
import BitwardenKit
import Networking
// MARK: - ResponseValidationError

View File

@ -1,3 +1,4 @@
import BitwardenKit
import Networking
import TestHelpers
import XCTest

View File

@ -1,4 +1,5 @@
import AuthenticationServices
import BitwardenKit
import Networking
import TestHelpers
import XCTest

View File

@ -1,4 +1,5 @@
import AuthenticationServices
import BitwardenKit
import Networking
import TestHelpers
import XCTest

View File

@ -1,4 +1,5 @@
import AuthenticationServices
import BitwardenKit
import Networking
import TestHelpers
import XCTest

View File

@ -1,4 +1,5 @@
// swiftlint:disable:this file_name
import BitwardenKit
import Foundation
import Networking

View File

@ -1,3 +1,4 @@
import BitwardenKit
import TestHelpers
import XCTest

View File

@ -1,3 +1,4 @@
import BitwardenKit
import TestHelpers
import XCTest

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenSdk
import Networking
import TestHelpers

View File

@ -10,7 +10,7 @@ open class BitwardenTestCase: BaseBitwardenTestCase {
// Apply default appearances for snapshot tests.
UI.applyDefaultAppearances()
TestDataHelpers.bundleClass = Self.self
TestDataHelpers.defaultBundle = Bundle(for: Self.self)
}
/// Executes any logic that should be applied before each test runs.

View File

@ -8,7 +8,7 @@ open class BitwardenTestCase: BaseBitwardenTestCase {
// Apply default appearances for snapshot tests.
UI.applyDefaultAppearances()
TestDataHelpers.bundleClass = Self.self
TestDataHelpers.defaultBundle = Bundle(for: Self.self)
}
/// Executes any logic that should be applied before each test runs.

View File

@ -12,14 +12,14 @@ public struct APITestData {
}
/// Loads test data from a provided file in the test class's bundle.
public static func loadFromBundle(resource: String, extension: String) -> APITestData {
let data = TestDataHelpers.loadFromBundle(resource: resource, extension: `extension`)
public static func loadFromBundle(resource: String, extension: String, bundle: Bundle? = nil) -> APITestData {
let data = TestDataHelpers.loadFromBundle(resource: resource, extension: `extension`, bundle: bundle)
return APITestData(data: data)
}
/// Loads test data from a provided JSON file in the test class's bundle.
public static func loadFromJsonBundle(resource: String) -> APITestData {
loadFromBundle(resource: resource, extension: "json")
public static func loadFromJsonBundle(resource: String, bundle: Bundle? = nil) -> APITestData {
loadFromBundle(resource: resource, extension: "json", bundle: bundle)
}
}

View File

@ -3,33 +3,35 @@ import Foundation
/// A type that wraps fixture data for use in mocking responses during tests.
///
public enum TestDataHelpers {
/// The class used to determine the bundle to load files from.
public static var bundleClass: AnyClass?
/// The default bundle to try loading files from.
public static var defaultBundle: Bundle?
/// Loads the data from the provided file.
public static func loadFromBundle(resource: String, extension: String) -> Data {
guard let bundleClass else {
fatalError("Class to determine test bundle from not set properly in the test case.")
public static func loadFromBundle(resource: String, extension: String, bundle: Bundle? = nil) -> Data {
let resolvedBundle = bundle ?? defaultBundle
guard let resolvedBundle else {
fatalError("Default test data bundle from not set properly in the test case.")
}
let bundle = Bundle(for: bundleClass.self)
guard let url = bundle.url(forResource: resource, withExtension: `extension`) else {
fatalError("Unable to locate file \(resource).\(`extension`) in the bundle.")
guard let url = resolvedBundle.url(forResource: resource, withExtension: `extension`) else {
// swiftlint:disable:next line_length
fatalError("Unable to locate file \(resource).\(`extension`) in the bundle \(resolvedBundle.bundleURL.lastPathComponent).")
}
do {
return try Data(contentsOf: url)
} catch {
fatalError("Unable to load data from \(resource).\(`extension`) in the bundle. Error: \(error)")
// swiftlint:disable:next line_length
fatalError("Unable to load data from \(resource).\(`extension`) in the bundle \(resolvedBundle.bundleURL.lastPathComponent). Error: \(error)")
}
}
/// Convenience function for loading data from a JSON file.
public static func loadFromJsonBundle(resource: String) -> Data {
loadFromBundle(resource: resource, extension: "json")
public static func loadFromJsonBundle(resource: String, bundle: Bundle? = nil) -> Data {
loadFromBundle(resource: resource, extension: "json", bundle: bundle)
}
/// Convenience function for loading a JSON file into a UTF-8 string.
public static func loadUTFStringFromJsonBundle(resource: String) -> String? {
let data = loadFromJsonBundle(resource: resource)
public static func loadUTFStringFromJsonBundle(resource: String, bundle: Bundle? = nil) -> String? {
let data = loadFromJsonBundle(resource: resource, bundle: bundle)
return String(data: data, encoding: .utf8)
}
}

View File

@ -106,6 +106,7 @@ targets:
sources:
- path: BitwardenKit
excludes:
- "**/Fixtures/*"
- "**/Mocks/*"
- "**/*Tests.*"
- "**/TestHelpers/*"
@ -127,6 +128,7 @@ targets:
sources:
- path: BitwardenKit
includes:
- "**/Fixtures/*"
- "**/Mocks/*"
dependencies:
- target: BitwardenKit

View File

@ -411,6 +411,7 @@ targets:
dependencies:
- target: Bitwarden
- target: BitwardenShared
- target: BitwardenKit/BitwardenKitMocks
- target: BitwardenKit/TestHelpers
- package: SnapshotTesting
- package: SnapshotTesting