Skip to content

Commit 5bb1012

Browse files
Add account creation and deletion tests
1 parent 2859c5b commit 5bb1012

30 files changed

+357
-415
lines changed

ios/MullvadVPN.xcodeproj/project.pbxproj

+72-10
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"pins" : [
3+
{
4+
"identity" : "swift-log",
5+
"kind" : "remoteSourceControl",
6+
"location" : "https://github.com/apple/swift-log.git",
7+
"state" : {
8+
"revision" : "173f567a2dfec11d74588eea82cecea555bdc0bc",
9+
"version" : "1.4.0"
10+
}
11+
},
12+
{
13+
"identity" : "wireguard-apple",
14+
"kind" : "remoteSourceControl",
15+
"location" : "https://github.com/mullvad/wireguard-apple.git",
16+
"state" : {
17+
"revision" : "11a00c20dc03f2751db47e94f585c0778c7bde82"
18+
}
19+
}
20+
],
21+
"version" : 2
22+
}

ios/MullvadVPN/Classes/AccessbilityIdentifier.swift

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public enum AccessibilityIdentifier: String {
1717
case cancelButton
1818
case connectionPanelButton
1919
case collapseButton
20+
case createAccountButton
2021
case deleteButton
2122
case disconnectButton
2223
case infoButton
@@ -47,19 +48,24 @@ public enum AccessibilityIdentifier: String {
4748
// Labels
4849
case headerDeviceNameLabel
4950
case connectionStatusLabel
51+
case welcomeAccountNumberLabel
5052

5153
// Views
5254
case accountView
5355
case alertContainerView
5456
case alertTitle
57+
case changeLogAlert
5558
case headerBarView
5659
case loginView
60+
case outOfTimeView
5761
case termsOfServiceView
5862
case selectLocationView
5963
case selectLocationTableView
6064
case settingsTableView
6165
case tunnelControlView
6266
case problemReportView
67+
case welcomeView
68+
case deleteAccountView
6369

6470
// Other UI elements
6571
case connectionPanelInAddressRow
@@ -70,6 +76,7 @@ public enum AccessibilityIdentifier: String {
7076
case selectLocationSearchTextField
7177
case problemReportEmailTextField
7278
case problemReportMessageTextView
79+
case deleteAccountTextField
7380

7481
// DNS settings
7582
case dnsSettings

ios/MullvadVPN/Coordinators/ChangeLogCoordinator.swift

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ final class ChangeLogCoordinator: Coordinator, Presentable {
2626
func start() {
2727
let presentation = AlertPresentation(
2828
id: "change-log-ok-alert",
29+
accessibilityIdentifier: .changeLogAlert,
2930
header: interactor.viewModel.header,
3031
title: interactor.viewModel.title,
3132
attributedMessage: interactor.viewModel.body,

ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionContentView.swift

+2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class AccountDeletionContentView: UIView {
8181
private lazy var accountTextField: AccountTextField = {
8282
let groupingStyle = AccountTextField.GroupingStyle.lastPart
8383
let textField = AccountTextField(groupingStyle: groupingStyle)
84+
textField.accessibilityIdentifier = .deleteAccountTextField
8485
textField.font = .preferredFont(forTextStyle: .body, weight: .bold)
8586
textField.placeholder = Array(repeating: "X", count: 4).joined()
8687
textField.placeholderTextColor = .lightGray
@@ -346,6 +347,7 @@ class AccountDeletionContentView: UIView {
346347
}
347348

348349
private func setupAppearance() {
350+
accessibilityIdentifier = .deleteAccountView
349351
translatesAutoresizingMaskIntoConstraints = false
350352
backgroundColor = .secondaryColor
351353
directionalLayoutMargins = UIMetrics.contentLayoutMargins

ios/MullvadVPN/View controllers/Alert/AlertPresentation.swift

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ struct AlertAction {
2424
struct AlertPresentation: Identifiable, CustomDebugStringConvertible {
2525
let id: String
2626

27+
var accessibilityIdentifier: AccessibilityIdentifier?
2728
var header: String?
2829
var icon: AlertIcon?
2930
var title: String?

ios/MullvadVPN/View controllers/Alert/AlertViewController.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ class AlertViewController: UIViewController {
5656
view.backgroundColor = .secondaryColor
5757
view.layer.cornerRadius = 11
5858

59-
view.accessibilityIdentifier = .alertContainerView
60-
6159
return view
6260
}()
6361

@@ -112,6 +110,9 @@ class AlertViewController: UIViewController {
112110

113111
view.backgroundColor = .black.withAlphaComponent(0.5)
114112

113+
let accessibilityIdentifier = presentation.accessibilityIdentifier ?? .alertContainerView
114+
view.accessibilityIdentifier = accessibilityIdentifier
115+
115116
setContent()
116117
setConstraints()
117118
}

ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeContentView.swift

+2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ final class WelcomeContentView: UIView {
5454

5555
private let accountNumberLabel: UILabel = {
5656
let label = UILabel()
57+
label.accessibilityIdentifier = .welcomeAccountNumberLabel
5758
label.adjustsFontForContentSizeCategory = true
5859
label.lineBreakMode = .byWordWrapping
5960
label.numberOfLines = .zero
@@ -192,6 +193,7 @@ final class WelcomeContentView: UIView {
192193
override init(frame: CGRect) {
193194
super.init(frame: frame)
194195

196+
accessibilityIdentifier = .welcomeView
195197
backgroundColor = .primaryColor
196198
directionalLayoutMargins = UIMetrics.contentLayoutMargins
197199
backgroundColor = .secondaryColor

ios/MullvadVPN/View controllers/Login/LoginContentView.swift

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class LoginContentView: UIView {
8484

8585
let createAccountButton: AppButton = {
8686
let button = AppButton(style: .default)
87+
button.accessibilityIdentifier = .createAccountButton
8788
button.translatesAutoresizingMaskIntoConstraints = false
8889
button.setTitle(NSLocalizedString(
8990
"CREATE_ACCOUNT_BUTTON_LABEL",

ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeContentView.swift

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ class OutOfTimeContentView: UIView {
9595

9696
override init(frame: CGRect) {
9797
super.init(frame: frame)
98+
accessibilityIdentifier = .outOfTimeView
9899
translatesAutoresizingMaskIntoConstraints = false
99100
backgroundColor = .secondaryColor
100101
directionalLayoutMargins = UIMetrics.contentLayoutMargins

ios/MullvadVPNUITests/AccountTests.swift

+40
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,46 @@ class AccountTests: LoggedOutUITestCase {
1515
try super.setUpWithError()
1616
}
1717

18+
override func tearDownWithError() throws {}
19+
20+
func testCreateAccount() throws {
21+
LoginPage(app)
22+
.tapCreateAccountButton()
23+
24+
// Verify welcome page is shown and get account number from it
25+
let accountNumber = WelcomePage(app).getAccountNumber()
26+
27+
try MullvadAPIWrapper().deleteAccount(accountNumber)
28+
}
29+
30+
func testDeleteAccount() throws {
31+
let accountNumber = try MullvadAPIWrapper().createAccount()
32+
33+
LoginPage(app)
34+
.tapAccountNumberTextField()
35+
.enterText(accountNumber)
36+
.tapAccountNumberSubmitButton()
37+
38+
OutOfTimePage(app)
39+
40+
HeaderBar(app)
41+
.tapAccountButton()
42+
43+
AccountPage(app)
44+
.tapDeleteAccountButton()
45+
46+
AccountDeletionPage(app)
47+
.enterText(String(accountNumber.suffix(4)))
48+
.tapDeleteAccountButton()
49+
50+
// Attempt to login with deleted account and verify that it fails
51+
LoginPage(app)
52+
.tapAccountNumberTextField()
53+
.enterText(accountNumber)
54+
.tapAccountNumberSubmitButton()
55+
.verifyFailIconShown()
56+
}
57+
1858
func testLogin() throws {
1959
LoginPage(app)
2060
.tapAccountNumberTextField()

ios/MullvadVPNUITests/MullvadApi.swift

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ struct InitMutableBufferError: Error {
3535
class MullvadApi {
3636
private var clientContext = MullvadApiClient()
3737

38+
/// Initialize the Mullvad API client
39+
/// - Parameters:
40+
/// - apiAddress: Address of the Mullvad API server in the format \<IP-address\>:\<port\>
41+
/// - hostname: Hostname of the Mullvad API server
3842
init(apiAddress: String, hostname: String) throws {
3943
let result = mullvad_api_client_initialize(
4044
&clientContext,

ios/MullvadVPNUITests/Networking/MullvadAPIWrapper.swift

+58-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// AppAPI.swift
2+
// MullvadAPIWrapper.swift
33
// MullvadVPNUITests
44
//
55
// Created by Niklas Berglund on 2024-01-18.
@@ -11,18 +11,27 @@ import XCTest
1111

1212
enum MullvadAPIError: Error {
1313
case incorrectConfigurationFormat
14+
case requestError
1415
}
1516

1617
class MullvadAPIWrapper {
1718
// swiftlint:disable force_cast
1819
static let hostName = Bundle(for: MullvadAPIWrapper.self)
1920
.infoDictionary?["ApiHostName"] as! String
2021

22+
private var mullvadAPI: MullvadApi
23+
2124
/// API endpoint configuration value in the format <IP-address>:<port>
2225
static let endpoint = Bundle(for: MullvadAPIWrapper.self)
2326
.infoDictionary?["ApiEndpoint"] as! String
2427
// swiftlint:enable force_cast
2528

29+
init() throws {
30+
let apiAddress = try Self.getAPIIPAddress() + ":" + Self.getAPIPort()
31+
let hostname = Self.getAPIHostname()
32+
mullvadAPI = try MullvadApi(apiAddress: apiAddress, hostname: hostname)
33+
}
34+
2635
public static func getAPIHostname() -> String {
2736
return hostName
2837
}
@@ -42,4 +51,52 @@ class MullvadAPIWrapper {
4251

4352
return port
4453
}
54+
55+
/// Generate a mock WireGuard key
56+
private func generateMockWireGuardKey() -> Data {
57+
var bytes = [UInt8]()
58+
59+
for _ in 0 ..< 44 {
60+
bytes.append(UInt8.random(in: 0 ..< 255))
61+
}
62+
63+
return Data(bytes)
64+
}
65+
66+
func createAccount() -> String {
67+
do {
68+
let accountNumber = try mullvadAPI.createAccount()
69+
return accountNumber
70+
} catch {
71+
XCTFail("Failed to create account using app API")
72+
return String()
73+
}
74+
}
75+
76+
func deleteAccount(_ accountNumber: String) {
77+
do {
78+
try mullvadAPI.delete(account: accountNumber)
79+
} catch {
80+
XCTFail("Failed to delete account using app API")
81+
}
82+
}
83+
84+
/// Add another device to specified account. A dummy WireGuard key will be generated.
85+
func addDevice(_ account: String) throws {
86+
let devicePublicKey = generateMockWireGuardKey()
87+
88+
do {
89+
try mullvadAPI.addDevice(forAccount: account, publicKey: devicePublicKey)
90+
} catch {
91+
throw MullvadAPIError.requestError
92+
}
93+
}
94+
95+
func getAccountExpiry(_ account: String) throws -> UInt64 {
96+
do {
97+
return try mullvadAPI.getExpiry(forAccount: account)
98+
} catch {
99+
throw MullvadAPIError.requestError
100+
}
101+
}
45102
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// AccountDeletionPage.swift
3+
// MullvadVPNUITests
4+
//
5+
// Created by Niklas Berglund on 2024-01-30.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import XCTest
11+
12+
class AccountDeletionPage: Page {
13+
@discardableResult override init(_ app: XCUIApplication) {
14+
super.init(app)
15+
16+
self.pageAccessibilityIdentifier = .deleteAccountView
17+
waitForPageToBeShown()
18+
}
19+
20+
@discardableResult func tapTextField() -> Self {
21+
app.textFields[AccessibilityIdentifier.deleteAccountTextField].tap()
22+
return self
23+
}
24+
25+
@discardableResult func tapDeleteAccountButton() -> Self {
26+
guard let pageAccessibilityIdentifier = self.pageAccessibilityIdentifier else {
27+
XCTFail("Page's accessibility identifier not set")
28+
return self
29+
}
30+
31+
app.otherElements[pageAccessibilityIdentifier].buttons[AccessibilityIdentifier.deleteButton].tap()
32+
return self
33+
}
34+
35+
@discardableResult func tapCancelButton() -> Self {
36+
app.buttons[AccessibilityIdentifier.cancelButton].tap()
37+
return self
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// ChangeLogAlert.swift
3+
// MullvadVPNUITests
4+
//
5+
// Created by Niklas Berglund on 2024-02-20.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import XCTest
11+
12+
class ChangeLogAlert: Page {
13+
@discardableResult override init(_ app: XCUIApplication) {
14+
super.init(app)
15+
16+
self.pageAccessibilityIdentifier = .changeLogAlert
17+
waitForPageToBeShown()
18+
}
19+
20+
@discardableResult func tapOkay() -> Self {
21+
app.buttons[AccessibilityIdentifier.alertOkButton].tap()
22+
return self
23+
}
24+
}

ios/MullvadVPNUITests/Pages/LoginPage.swift

+5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ class LoginPage: Page {
2727
return self
2828
}
2929

30+
@discardableResult public func tapCreateAccountButton() -> Self {
31+
app.buttons[AccessibilityIdentifier.createAccountButton].tap()
32+
return self
33+
}
34+
3035
@discardableResult public func verifyDeviceLabelShown() -> Self {
3136
XCTAssertTrue(
3237
app.staticTexts[AccessibilityIdentifier.headerDeviceNameLabel]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// OutOfTimePage.swift
3+
// MullvadVPNUITests
4+
//
5+
// Created by Niklas Berglund on 2024-02-20.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import XCTest
11+
12+
class OutOfTimePage: Page {
13+
@discardableResult override init(_ app: XCUIApplication) {
14+
super.init(app)
15+
16+
self.pageAccessibilityIdentifier = .outOfTimeView
17+
waitForPageToBeShown()
18+
}
19+
}

ios/MullvadVPNUITests/Pages/SelectLocationPage.swift

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class SelectLocationPage: Page {
1414
super.init(app)
1515

1616
self.pageAccessibilityIdentifier = .selectLocationView
17+
waitForPageToBeShown()
1718
}
1819

1920
@discardableResult func tapLocationCell(withName name: String) -> Self {

0 commit comments

Comments
 (0)