Skip to content
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
46 changes: 20 additions & 26 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ name: Build APK

on:
push:
branches: [ main, dev]
branches: [ main, dev ]
pull_request:
branches: [ main, dev, feature/* ]
types: [opened, synchronize, reopened]

jobs:
build:
Expand All @@ -17,23 +20,24 @@ jobs:
with:
java-version: '17'
distribution: 'temurin'

- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-


- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Run unit tests
run: ./gradlew test --continue

- name: Run tests
run: ./gradlew test

- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ github.run_number }}
path: |
app/build/reports/tests/
app/build/test-results/
app/build/reports/androidTests/
retention-days: 7

- name: Build debug APK
run: ./gradlew assembleDebug

Expand All @@ -42,14 +46,4 @@ jobs:
with:
name: debug-apk
path: app/build/outputs/apk/debug/*.apk
retention-days: 30

- name: Build release APK
run: ./gradlew assembleRelease

- name: Upload release APK
uses: actions/upload-artifact@v4
with:
name: release-apk
path: app/build/outputs/apk/release/*.apk
retention-days: 30
retention-days: 7
29 changes: 0 additions & 29 deletions .github/workflows/build_pull_request.yml

This file was deleted.

10 changes: 0 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,6 @@ jobs:
java-version: '17'
distribution: 'temurin'

- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Grant execute permission for gradlew
run: chmod +x gradlew

Expand Down
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ settings.gradle.kts # Project settings

```bash
# Clone the repository
git clone https://github.com/aunchagaonkar/Network-Switch.git
cd Network-Switch
git clone https://github.com/aunchagaonkar/NetworkSwitch.git
cd NetworkSwitch

# Build debug APK
./gradlew assembleDebug
Expand All @@ -117,7 +117,7 @@ cd Network-Switch
```

## TODO
- [ ] Add unit tests for all core components
- [x] Add unit tests for all core components
- [ ] Add network speed monitoring
- [ ] Implement network statistics tracking
- [ ] Add support for 3G fallback modes
Expand Down Expand Up @@ -147,6 +147,14 @@ Contributions are welcome! Please follow these guidelines:
- Follow Material Design guidelines for UI changes

### Testing
This project includes a suite of unit tests to ensure code quality and stability.

To run the unit tests, execute the following command from the root of the project:

```bash
./gradlew test
```

- Add unit tests for new functionality
- Test on both rooted and non-rooted devices
- Verify compatibility across different Android versions
Expand Down
18 changes: 17 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ android {
// Resource optimization
resourceConfigurations += listOf("en", "xxhdpi")

// Disable unnecessary features for smaller APK
// Disable unnecessary features
vectorDrawables {
useSupportLibrary = true
}
Expand Down Expand Up @@ -82,6 +82,14 @@ android {
kotlinCompilerExtensionVersion = libs.versions.compose.get()
}

testOptions {
unitTests {
isReturnDefaultValues = true
isIncludeAndroidResources = true
}
animationsDisabled = true
}

packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
Expand All @@ -108,6 +116,9 @@ android {
// JNI libraries optimization
jniLibs {
useLegacyPackaging = false
// Exclude native libraries from packaging
excludes += "**/libandroidx.graphics.path.so"
excludes += "**/libdatastore_shared_counter.so"
}
}

Expand Down Expand Up @@ -161,11 +172,16 @@ dependencies {

// Testing
testImplementation(libs.junit)
testImplementation(libs.mockk)
testImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)

// Hilt Testing
testImplementation(libs.hilt.android.testing)
kaptTest(libs.hilt.compiler)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.supernova.networkswitch.data.repository

import com.supernova.networkswitch.data.source.RootNetworkControlDataSource
import com.supernova.networkswitch.data.source.ShizukuNetworkControlDataSource
import com.supernova.networkswitch.domain.model.ControlMethod
import com.supernova.networkswitch.domain.repository.PreferencesRepository
import com.supernova.networkswitch.util.CoroutineTestRule
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import android.telephony.SubscriptionManager
import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.unmockkAll
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test

@ExperimentalCoroutinesApi
class NetworkControlRepositoryImplTest {

@get:Rule
val coroutineTestRule = CoroutineTestRule()

private lateinit var rootDataSource: RootNetworkControlDataSource
private lateinit var shizukuDataSource: ShizukuNetworkControlDataSource
private lateinit var preferencesRepository: PreferencesRepository
private lateinit var repository: NetworkControlRepositoryImpl

@Before
fun setUp() {
rootDataSource = mockk(relaxed = true)
shizukuDataSource = mockk(relaxed = true)
preferencesRepository = mockk(relaxed = true)

mockkStatic(SubscriptionManager::class)
every { SubscriptionManager.getDefaultDataSubscriptionId() } returns 1

repository = NetworkControlRepositoryImpl(
rootDataSource,
shizukuDataSource,
preferencesRepository
)
}

@After
fun tearDown() {
unmockkAll()
}

@Test
fun `checkCompatibility uses RootDataSource when method is ROOT`() = runTest {
coEvery { preferencesRepository.getControlMethod() } returns ControlMethod.ROOT

repository.checkCompatibility(ControlMethod.ROOT)

coVerify { rootDataSource.checkCompatibility(1) }
coVerify(exactly = 0) { shizukuDataSource.checkCompatibility(any()) }
}

@Test
fun `checkCompatibility uses ShizukuDataSource when method is SHIZUKU`() = runTest {
coEvery { preferencesRepository.getControlMethod() } returns ControlMethod.SHIZUKU

repository.checkCompatibility(ControlMethod.SHIZUKU)

coVerify { shizukuDataSource.checkCompatibility(1) }
coVerify(exactly = 0) { rootDataSource.checkCompatibility(any()) }
}

@Test
fun `getFivegEnabled uses RootDataSource when method is ROOT`() = runTest {
coEvery { preferencesRepository.getControlMethod() } returns ControlMethod.ROOT
val subId = 1

repository.getFivegEnabled(subId)

coVerify { rootDataSource.getFivegEnabled(subId) }
coVerify(exactly = 0) { shizukuDataSource.getFivegEnabled(any()) }
}

@Test
fun `getFivegEnabled uses ShizukuDataSource when method is SHIZUKU`() = runTest {
coEvery { preferencesRepository.getControlMethod() } returns ControlMethod.SHIZUKU
val subId = 1

repository.getFivegEnabled(subId)

coVerify { shizukuDataSource.getFivegEnabled(subId) }
coVerify(exactly = 0) { rootDataSource.getFivegEnabled(any()) }
}

@Test
fun `setFivegEnabled uses RootDataSource when method is ROOT`() = runTest {
coEvery { preferencesRepository.getControlMethod() } returns ControlMethod.ROOT
val subId = 1
val enabled = true

repository.setFivegEnabled(subId, enabled)

coVerify { rootDataSource.setFivegEnabled(subId, enabled) }
coVerify(exactly = 0) { shizukuDataSource.setFivegEnabled(any(), any()) }
}

@Test
fun `setFivegEnabled uses ShizukuDataSource when method is SHIZUKU`() = runTest {
coEvery { preferencesRepository.getControlMethod() } returns ControlMethod.SHIZUKU
val subId = 1
val enabled = true

repository.setFivegEnabled(subId, enabled)

coVerify { shizukuDataSource.setFivegEnabled(subId, enabled) }
coVerify(exactly = 0) { rootDataSource.setFivegEnabled(any(), any()) }
}

@Test
fun `resetConnections calls reset on both data sources`() = runTest {
repository.resetConnections()

coVerify { rootDataSource.resetConnection() }
coVerify { shizukuDataSource.resetConnection() }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.supernova.networkswitch.data.repository

import com.supernova.networkswitch.data.source.PreferencesDataSource
import com.supernova.networkswitch.domain.model.ControlMethod
import com.supernova.networkswitch.util.CoroutineTestRule
import io.mockk.coVerify
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test

@ExperimentalCoroutinesApi
class PreferencesRepositoryImplTest {

@get:Rule
val coroutineTestRule = CoroutineTestRule()

private lateinit var mockDataSource: PreferencesDataSource
private lateinit var repository: PreferencesRepositoryImpl

@Before
fun setUp() {
mockDataSource = mockk(relaxed = true)
repository = PreferencesRepositoryImpl(mockDataSource)
}

@Test
fun `getControlMethod calls data source`() = runTest {
repository.getControlMethod()
coVerify { mockDataSource.getControlMethod() }
}

@Test
fun `setControlMethod calls data source`() = runTest {
val method = ControlMethod.ROOT
repository.setControlMethod(method)
coVerify { mockDataSource.setControlMethod(method) }
}

@Test
fun `observeControlMethod calls data source`() {
repository.observeControlMethod()
verify { mockDataSource.observeControlMethod() }
}
}
Loading