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

Add negative leak test #6448

Closed
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
38 changes: 34 additions & 4 deletions .github/actions/ios-end-to-end-tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,22 @@ inputs:
xcode_test_plan:
description: 'Xcode Test Plan to run'
required: true
partner_api_token:
description: 'Partner API Token'
required: true
test_name:
description: 'Test case/suite name. Will run all tests in the test plan if not provided.'
required: false

runs:
using: 'composite'
steps:
- name: Make sure app is not installed
run: ios-deploy --id $IOS_TEST_DEVICE_UDID --uninstall_only --bundle_id net.mullvad.MullvadVPN
shell: bash
env:
IOS_TEST_DEVICE_UDID: ${{ inputs.test_device_udid }}

- name: Configure Xcode project
run: |
for file in *.xcconfig.template ; do cp $file ${file//.template/} ; done
Expand All @@ -34,26 +46,44 @@ runs:
sed -i "" \
"/TEST_DEVICE_IDENTIFIER_UUID =/ s/= .*/= $TEST_DEVICE_IDENTIFIER_UUID/" \
UITests.xcconfig
echo -e "\nHAS_TIME_ACCOUNT_NUMBER = $HAS_TIME_ACCOUNT_NUMBER" >> UITests.xcconfig
echo "NO_TIME_ACCOUNT_NUMBER = $NO_TIME_ACCOUNT_NUMBER" >> UITests.xcconfig
sed -i "" \
"/PARTNER_API_TOKEN =/ s#= .*#= $PARTNER_API_TOKEN#" \
UITests.xcconfig
sed -i "" \
"/ATTACH_APP_LOGS_ON_FAILURE =/ s#= .*#= 1#" \
UITests.xcconfig
shell: bash
working-directory: ios/Configurations
env:
IOS_DEVICE_PIN_CODE: ${{ inputs.ios_device_pin_code }}
TEST_DEVICE_IDENTIFIER_UUID: ${{ inputs.test_device_identifier_uuid }}
HAS_TIME_ACCOUNT_NUMBER: ${{ inputs.has_time_account_number }}
NO_TIME_ACCOUNT_NUMBER: ${{ inputs.no_time_account_number }}
PARTNER_API_TOKEN: ${{ inputs.partner_api_token }}

- name: Run end-to-end-tests
run: |
if [ -n "$TEST_NAME" ]; then
TEST_NAME_ARGUMENT=" -only-testing $TEST_NAME"
else
TEST_NAME_ARGUMENT=""
fi
set -o pipefail && env NSUnbufferedIO=YES xcodebuild \
-project MullvadVPN.xcodeproj \
-scheme MullvadVPNUITests \
-testPlan $XCODE_TEST_PLAN \
-testPlan $XCODE_TEST_PLAN $TEST_NAME_ARGUMENT \
-resultBundlePath xcode-test-report \
-destination "platform=iOS,id=$TEST_DEVICE_UDID" \
clean test 2>&1 | xcbeautify --report junit --report-path test-report
clean test 2>&1 | xcbeautify --report junit --report-path junit-test-report
shell: bash
working-directory: ios/
env:
XCODE_TEST_PLAN: ${{ inputs.xcode_test_plan }}
TEST_DEVICE_UDID: ${{ inputs.test_device_udid }}
TEST_NAME: ${{ inputs.test_name }}

- name: Uninstall app if still installed
run: ios-deploy --id $IOS_TEST_DEVICE_UDID --uninstall_only --bundle_id net.mullvad.MullvadVPN
shell: bash
env:
IOS_TEST_DEVICE_UDID: ${{ inputs.test_device_udid }}
2 changes: 1 addition & 1 deletion .github/workflows/git-commit-message-style.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ jobs:
# This action defaults to 50 char subjects, but 72 is fine.
max-subject-line-length: '72'
# The action's wordlist is a bit short. Add more accepted verbs
additional-verbs: 'tidy, wrap'
additional-verbs: 'tidy, wrap, obfuscate'
4 changes: 4 additions & 0 deletions .github/workflows/ios-end-to-end-tests-settings-migration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ jobs:
no_time_account_number: ${{ secrets.IOS_NO_TIME_ACCOUNT_NUMBER_PRODUCTION }}
test_device_udid: ${{ secrets.IOS_TEST_DEVICE_UDID }}
xcode_test_plan: 'MullvadVPNUITestsChangeDNSSettings'
partner_api_token: ${{ secrets.STAGEMOLE_PARTNER_AUTH }}

- name: Store test report for changing DNS settings
uses: actions/upload-artifact@v4
Expand All @@ -62,6 +63,7 @@ jobs:
has_time_account_number: ${{ secrets.IOS_HAS_TIME_ACCOUNT_NUMBER_PRODUCTION }}
no_time_account_number: ${{ secrets.IOS_NO_TIME_ACCOUNT_NUMBER_PRODUCTION }}
test_device_udid: ${{ secrets.IOS_TEST_DEVICE_UDID }}
partner_api_token: ${{ secrets.STAGEMOLE_PARTNER_AUTH }}
xcode_test_plan: 'MullvadVPNUITestsVerifyDNSSettingsChanged'

- name: Store test report for verifying DNS settings
Expand All @@ -85,6 +87,7 @@ jobs:
has_time_account_number: ${{ secrets.IOS_HAS_TIME_ACCOUNT_NUMBER_PRODUCTION }}
no_time_account_number: ${{ secrets.IOS_NO_TIME_ACCOUNT_NUMBER_PRODUCTION }}
test_device_udid: ${{ secrets.IOS_TEST_DEVICE_UDID }}
partner_api_token: ${{ secrets.STAGEMOLE_PARTNER_AUTH }}
xcode_test_plan: 'MullvadVPNUITestsChangeSettings'

- name: Store test report for changing all settings
Expand All @@ -106,6 +109,7 @@ jobs:
has_time_account_number: ${{ secrets.IOS_HAS_TIME_ACCOUNT_NUMBER_PRODUCTION }}
no_time_account_number: ${{ secrets.IOS_NO_TIME_ACCOUNT_NUMBER_PRODUCTION }}
test_device_udid: ${{ secrets.IOS_TEST_DEVICE_UDID }}
partner_api_token: ${{ secrets.STAGEMOLE_PARTNER_AUTH }}
xcode_test_plan: 'MullvadVPNUITestsVerifySettingsChanged'

- name: Store test report for verifying all other settings
Expand Down
22 changes: 20 additions & 2 deletions .github/workflows/ios-end-to-end-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ on:
- .github/workflows/ios-end-to-end-tests.yml
- ios/**
workflow_dispatch:
inputs:
# Optionally specify a test case or suite to run.
# Must be in the format MullvadVPNUITest/<test-suite-name>/<test-case-name> where test case name is optional.
test_name:
description: 'Only run test case/suite'
required: false
schedule:
# At midnight every day.
# Notifications for scheduled workflows are sent to the user who last modified the cron
Expand All @@ -26,6 +32,7 @@ jobs:
if: github.event.pull_request.merged || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule'
name: End to end tests
runs-on: [self-hosted, macOS, ios-test]
timeout-minutes: 60
steps:
- name: Configure Rust
uses: actions-rs/toolchain@v1
Expand All @@ -50,11 +57,13 @@ jobs:
uses: ./.github/actions/ios-end-to-end-tests
with:
xcode_test_plan: ${{ env.XCODE_TEST_PLAN }}
test_name: ${{ github.event.inputs.test_name }}
ios_device_pin_code: ${{ secrets.IOS_DEVICE_PIN_CODE }}
test_device_identifier_uuid: ${{ secrets.IOS_TEST_DEVICE_IDENTIFIER_UUID }}
has_time_account_number: ${{ secrets.IOS_HAS_TIME_ACCOUNT_NUMBER_PRODUCTION }}
no_time_account_number: ${{ secrets.IOS_NO_TIME_ACCOUNT_NUMBER_PRODUCTION }}
test_device_udid: ${{ secrets.IOS_TEST_DEVICE_UDID }}
partner_api_token: ${{ secrets.STAGEMOLE_PARTNER_AUTH }}

- name: Comment PR on test failure
if: failure() && github.event_name == 'pull_request'
Expand All @@ -76,5 +85,14 @@ jobs:
if: always()
uses: actions/upload-artifact@v4
with:
name: test-report
path: ios/test-report/junit.xml
name: test-results
path: |
ios/junit-test-report/junit.xml
ios/xcode-test-report.xcresult

- name: Store app log artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: app-logs
path: ios/xcode-test-report/**/app-log-*.log
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.ui.test.onNodeWithText
import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
import net.mullvad.mullvadvpn.compose.setContentWithTheme
import net.mullvad.mullvadvpn.viewmodel.DnsDialogViewState
import net.mullvad.mullvadvpn.viewmodel.ValidationError
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension

Expand All @@ -19,11 +20,11 @@ class DnsDialogTest {

private val defaultState =
DnsDialogViewState(
ipAddress = "",
validationResult = DnsDialogViewState.ValidationResult.Success,
input = "",
validationError = null,
isLocal = false,
isAllowLanEnabled = false,
isNewEntry = true
index = null
)

@SuppressLint("ComposableNaming")
Expand All @@ -32,7 +33,7 @@ class DnsDialogTest {
state: DnsDialogViewState = defaultState,
onDnsInputChange: (String) -> Unit = { _ -> },
onSaveDnsClick: () -> Unit = {},
onRemoveDnsClick: () -> Unit = {},
onRemoveDnsClick: (Int) -> Unit = {},
onDismiss: () -> Unit = {}
) {
DnsDialog(state, onDnsInputChange, onSaveDnsClick, onRemoveDnsClick, onDismiss)
Expand Down Expand Up @@ -93,8 +94,8 @@ class DnsDialogTest {
setContentWithTheme {
testDnsDialog(
defaultState.copy(
ipAddress = invalidIpAddress,
validationResult = DnsDialogViewState.ValidationResult.InvalidAddress,
input = invalidIpAddress,
validationError = ValidationError.InvalidAddress,
)
)
}
Expand All @@ -110,8 +111,8 @@ class DnsDialogTest {
setContentWithTheme {
testDnsDialog(
defaultState.copy(
ipAddress = "192.168.0.1",
validationResult = DnsDialogViewState.ValidationResult.DuplicateAddress,
input = "192.168.0.1",
validationError = ValidationError.DuplicateAddress,
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollToNode
import io.mockk.MockKAnnotations
import io.mockk.coVerify
import io.mockk.mockk
import io.mockk.verify
import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
Expand All @@ -17,6 +18,8 @@ import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_LAST_ITEM_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_QUANTUM_ITEM_OFF_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_QUANTUM_ITEM_ON_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_UDP_OVER_TCP_PORT_ITEM_X_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_UDP_OVER_TCP_PORT_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_WIREGUARD_CUSTOM_PORT_NUMBER_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG
Expand All @@ -25,6 +28,7 @@ import net.mullvad.mullvadvpn.lib.model.Mtu
import net.mullvad.mullvadvpn.lib.model.Port
import net.mullvad.mullvadvpn.lib.model.PortRange
import net.mullvad.mullvadvpn.lib.model.QuantumResistantState
import net.mullvad.mullvadvpn.lib.model.SelectedObfuscation
import net.mullvad.mullvadvpn.onNodeWithTagAndText
import net.mullvad.mullvadvpn.viewmodel.CustomDnsItem
import org.junit.jupiter.api.BeforeEach
Expand Down Expand Up @@ -206,6 +210,77 @@ class VpnSettingsScreenTest {
onNodeWithContentDescription(LOCAL_DNS_SERVER_WARNING).assertExists()
}

@Test
fun testSelectTcpOverUdpPortOption() =
composeExtension.use {
// Arrange
val onObfuscationPortSelected: (Constraint<Port>) -> Unit = mockk(relaxed = true)
setContentWithTheme {
VpnSettingsScreen(
state =
VpnSettingsUiState.createDefault(
selectedObfuscation = SelectedObfuscation.Udp2Tcp,
selectedObfuscationPort = Constraint.Only(Port(5001))
),
onObfuscationPortSelected = onObfuscationPortSelected
)
}

// Act
onNodeWithTag(LAZY_LIST_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_UDP_OVER_TCP_PORT_TEST_TAG))
onNodeWithText("UDP-over-TCP port").performClick()
onNodeWithTag(LAZY_LIST_TEST_TAG)
.performScrollToNode(
hasTestTag(String.format(LAZY_LIST_UDP_OVER_TCP_PORT_ITEM_X_TEST_TAG, 5001))
)

// Assert
onNodeWithTagAndText(
testTag = String.format(LAZY_LIST_UDP_OVER_TCP_PORT_ITEM_X_TEST_TAG, 5001),
text = "5001"
)
.assertExists()
.performClick()

coVerify(exactly = 1) { onObfuscationPortSelected.invoke(Constraint.Only(Port(5001))) }
}

@Test
fun testAttemptSelectTcpOverUdpPortOption() =
composeExtension.use {
// Arrange
val onObfuscationPortSelected: (Constraint<Port>) -> Unit = mockk(relaxed = true)
setContentWithTheme {
VpnSettingsScreen(
state =
VpnSettingsUiState.createDefault(
selectedObfuscation = SelectedObfuscation.Off,
),
onObfuscationPortSelected = onObfuscationPortSelected
)
}

// Act
onNodeWithTag(LAZY_LIST_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_UDP_OVER_TCP_PORT_TEST_TAG))
onNodeWithText("UDP-over-TCP port").performClick()
onNodeWithTag(LAZY_LIST_TEST_TAG)
.performScrollToNode(
hasTestTag(String.format(LAZY_LIST_UDP_OVER_TCP_PORT_ITEM_X_TEST_TAG, 5001))
)

// Assert
onNodeWithTagAndText(
testTag = String.format(LAZY_LIST_UDP_OVER_TCP_PORT_ITEM_X_TEST_TAG, 5001),
text = "5001"
)
.assertExists()
.performClick()

verify(exactly = 0) { onObfuscationPortSelected.invoke(any()) }
}

@Test
fun testShowSelectedTunnelQuantumOption() =
composeExtension.use {
Expand Down Expand Up @@ -401,6 +476,29 @@ class VpnSettingsScreenTest {
verify { mockedClickHandler.invoke(null, null) }
}

@Test
fun testShowObfuscationInfo() =
composeExtension.use {
val mockedNavigateToObfuscationInfo: () -> Unit = mockk(relaxed = true)

// Arrange
setContentWithTheme {
VpnSettingsScreen(
state = VpnSettingsUiState.createDefault(),
navigateToObfuscationInfo = mockedNavigateToObfuscationInfo
)
}

// Act

onNodeWithTag(LAZY_LIST_TEST_TAG)
.performScrollToNode(hasTestTag(LAZY_LIST_UDP_OVER_TCP_PORT_TEST_TAG))
onNodeWithText("WireGuard obfuscation").performClick()

// Assert
verify(exactly = 1) { mockedNavigateToObfuscationInfo() }
}

@Test
fun testShowTunnelQuantumInfo() =
composeExtension.use {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
Expand Down Expand Up @@ -46,14 +47,15 @@ fun ExpandableComposeCell(
title: String,
isExpanded: Boolean,
isEnabled: Boolean = true,
testTag: String = "",
onCellClicked: (Boolean) -> Unit = {},
onInfoClicked: (() -> Unit)? = null
) {
val titleModifier = Modifier.alpha(if (isEnabled) AlphaVisible else AlphaInactive)
val bodyViewModifier = Modifier

BaseCell(
modifier = Modifier.focusProperties { canFocus = false },
modifier = Modifier.testTag(testTag).focusProperties { canFocus = false },
headlineContent = {
BaseCellTitle(
title = title,
Expand Down
Loading
Loading