BIT-104: Accounts Register Request (#23)

This commit is contained in:
Jubie Alade 2023-09-13 14:54:41 -05:00 committed by GitHub
parent 888dfd6941
commit a1c85ffeed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 323 additions and 2 deletions

View File

@ -0,0 +1,57 @@
import Foundation
import Networking
// MARK: - CreateAccountRequestModel
/// The data to include in the body of a `CreateAccountRequest`.
///
struct CreateAccountRequestModel: Equatable {
// MARK: Properties
/// The captcha response used in validating a user for this request.
let captchaResponse: String? = nil
/// The user's email address.
let email: String
/// The type of kdf for this request.
let kdf: KdfType? = nil
/// The number of kdf iterations performed in this request.
let kdfIterations: Int? = nil
/// The kdf memory allocated for the computed password hash.
let kdfMemory: Int? = nil
/// The number of threads upon which the kdf iterations are performed.
let kdfParallelism: Int? = nil
/// The key used for this request.
let key: String? = nil
/// The keys used for this request.
let keys: KeysRequestModel? = nil
/// The master password hash used to authenticate a user.
let masterPasswordHash: String // swiftlint:disable:this inclusive_language
/// The master password hint.
let masterPasswordHint: String? = nil // swiftlint:disable:this inclusive_language
/// The user's name.
let name: String? = nil
/// The organization's user ID.
let organizationUserId: String? = nil
/// The token used when making this request.
let token: String? = nil
}
// MARK: JSONRequestBody
extension CreateAccountRequestModel: JSONRequestBody {
static var encoder: JSONEncoder {
JSONEncoder()
}
}

View File

@ -0,0 +1,15 @@
import Foundation
import Networking
// MARK: - CreateAccountResponseModel
/// The response returned from the API upon creating an account.
///
struct CreateAccountResponseModel: JSONResponse {
static var decoder = JSONDecoder()
// MARK: Properties
/// The captcha bypass token returned in this response.
var captchaBypassToken: String?
}

View File

@ -0,0 +1,21 @@
import XCTest
@testable import BitwardenShared
// MARK: - CreateAccountResponseModelTests
class CreateAccountResponseModelTests: BitwardenTestCase {
/// Tests that a response is initialized correctly.
func test_init() {
let subject = CreateAccountResponseModel(captchaBypassToken: "captchaBypassToken")
XCTAssertEqual(subject.captchaBypassToken, "captchaBypassToken")
}
/// Tests the successful decoding of a JSON response.
func test_decode_success() throws {
let json = APITestData.createAccountResponse.data
let decoder = JSONDecoder()
let subject = try decoder.decode(CreateAccountResponseModel.self, from: json)
XCTAssertEqual(subject.captchaBypassToken, "captchaBypassToken")
}
}

View File

@ -0,0 +1,4 @@
extension APITestData {
static let createAccountRequest = loadFromBundle(resource: "Request", extension: "json")
static let createAccountResponse = loadFromBundle(resource: "Success", extension: "json")
}

View File

@ -0,0 +1,23 @@
{
"name": "name",
"email": "email",
"masterPasswordHash": "masterPasswordHash",
"masterPasswordHint": "masterPasswordHint",
"captchaResponse": "captchaResponse",
"key": "key",
"keys": {
"publicKey": "publicKey",
"encryptedPrivateKey": "encryptedPrivateKey"
},
"token": "token",
"organizationUserId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kdf": 0,
"kdfIterations": 0,
"kdfMemory": 0,
"kdfParallelism": 0,
"referenceData": {
"additionalProp1": "string",
"additionalProp2": "string",
"additionalProp3": "string"
}
}

View File

@ -0,0 +1,4 @@
{
"object": "object",
"captchaBypassToken": "captchaBypassToken"
}

View File

@ -0,0 +1,11 @@
// MARK: - KdfType
/// The type of key derivation function.
///
enum KdfType: Int, Codable, Equatable {
/// The PBKDF2 SHA256 type.
case pbkdf2sha256 = 0
/// The Argon2id type.
case argon2id = 1
}

View File

@ -0,0 +1,13 @@
// MARK: - KeysRequestModel
/// A model for keys used in the `CreateAccountRequest`.
///
struct KeysRequestModel: Codable, Equatable {
// MARK: Properties
/// The public key used in a `CreateAccountRequest`.
var publicKey: String?
/// The encrypted private key used in a `CreateAccountRequest`.
let encryptedPrivateKey: String
}

View File

@ -1,5 +1,22 @@
// MARK: - AccountAPIService
/// A protocol for an API service used to make account requests.
///
protocol AccountAPIService {}
protocol AccountAPIService {
/// Creates an API call for when the user submits an account creation form.
///
/// - Parameter body: The body to be included in the request.
///
/// - Returns data returned from the `CreateAccountRequest`.
///
func createNewAccount(body: CreateAccountRequestModel) async throws -> CreateAccountResponseModel
}
extension APIService: AccountAPIService {}
// MARK: - APIService
extension APIService: AccountAPIService {
func createNewAccount(body: CreateAccountRequestModel) async throws -> CreateAccountResponseModel {
let request = CreateAccountRequest(body: body)
return try await apiService.send(request)
}
}

View File

@ -0,0 +1,74 @@
import XCTest
@testable import BitwardenShared
// MARK: - AccountAPIServiceTests
class AccountAPIServiceTests: BitwardenTestCase {
// MARK: Properties
var client: MockHTTPClient!
var subject: APIService!
override func setUp() {
super.setUp()
client = MockHTTPClient()
subject = APIService(client: client)
}
override func tearDown() {
super.tearDown()
client = nil
subject = nil
}
// MARK: Account creation
/// `createNewAccount(email:masterPasswordHash)` throws an error if the request fails.
func test_create_account_httpFailure() async {
client.result = .httpFailure()
await assertAsyncThrows {
_ = try await subject.createNewAccount(
body: CreateAccountRequestModel(
email: "example@email.com",
masterPasswordHash: "1234"
)
)
}
}
/// `createNewAccount(email:masterPasswordHash)` throws a decoding error if the response is not the expected type.
func test_create_account_failure() async throws {
let resultData = APITestData(data: Data("this should fail".utf8))
client.result = .httpSuccess(testData: resultData)
await assertAsyncThrows {
_ = try await subject.createNewAccount(
body: CreateAccountRequestModel(
email: "example@email.com",
masterPasswordHash: "1234"
)
)
}
}
/// `createNewAccount(email:masterPasswordHash)` returns the correct value from the API with a successful request.
func test_create_account_success() async throws {
let resultData = APITestData.createAccountResponse
client.result = .httpSuccess(testData: resultData)
let successfulResponse = try await subject.createNewAccount(
body: CreateAccountRequestModel(
email: "example@email.com",
masterPasswordHash: "1234"
)
)
let request = try XCTUnwrap(client.requests.first)
XCTAssertEqual(request.method, .post)
XCTAssertEqual(request.url.relativePath, "/api/accounts/register")
XCTAssertEqual(successfulResponse.captchaBypassToken, "captchaBypassToken")
XCTAssertNotNil(request.body)
}
}

View File

@ -0,0 +1,28 @@
import Foundation
import Networking
// MARK: - CreateAccountRequest
/// The API request sent when submitting an account creation form.
///
struct CreateAccountRequest: Request {
typealias Response = CreateAccountResponseModel
typealias Body = CreateAccountRequestModel
/// The body of this request.
var body: CreateAccountRequestModel?
/// The HTTP method for this request.
let method: HTTPMethod = .post
/// The URL path for this request.
var path: String = "/accounts/register"
/// Creates a new `CreateAccountRequest` instance.
///
/// - Parameter body: The body of the request.
///
init(body: CreateAccountRequestModel) {
self.body = body
}
}

View File

@ -0,0 +1,54 @@
import XCTest
@testable import BitwardenShared
// MARK: - CreateAccountRequestTests
class CreateAccountRequestTests: BitwardenTestCase {
/// Validate that the method is correct.
func test_method() {
let subject = CreateAccountRequest(
body: CreateAccountRequestModel(
email: "email@example.com",
masterPasswordHash: "1234"
)
)
XCTAssertEqual(subject.method, .post)
}
/// Validate that the path is correct.
func test_path() {
let subject = CreateAccountRequest(
body: CreateAccountRequestModel(
email: "email@example.com",
masterPasswordHash: "1234"
)
)
XCTAssertEqual(subject.path, "/accounts/register")
}
/// Validate that the body is not nil.
func test_body() {
let subject = CreateAccountRequest(
body: CreateAccountRequestModel(
email: "email@example.com",
masterPasswordHash: "1234"
)
)
XCTAssertNotNil(subject.body)
}
// MARK: Init
/// Validate that the value provided to the init method is correct.
func test_init_body() {
let subject = CreateAccountRequest(
body: CreateAccountRequestModel(
email: "email@example.com",
masterPasswordHash: "1234"
)
)
XCTAssertEqual(subject.body?.email, "email@example.com")
XCTAssertEqual(subject.body?.masterPasswordHash, "1234")
}
}