repo created

This commit is contained in:
Fr4nz D13trich 2025-09-18 17:54:51 +02:00
commit 1ef725ef20
2483 changed files with 278273 additions and 0 deletions

View file

@ -0,0 +1,222 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client
import android.view.View
import androidx.annotation.UiThread
import androidx.test.core.app.launchActivity
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.DrawerActions
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withId
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import com.owncloud.android.lib.resources.activities.model.Activity
import com.owncloud.android.lib.resources.activities.model.RichElement
import com.owncloud.android.lib.resources.activities.model.RichObject
import com.owncloud.android.lib.resources.activities.models.PreviewObject
import com.owncloud.android.lib.resources.status.OCCapability
import com.owncloud.android.ui.activities.ActivitiesActivity
import com.owncloud.android.utils.EspressoIdlingResource
import com.owncloud.android.utils.ScreenshotTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.util.GregorianCalendar
class ActivitiesActivityIT : AbstractIT() {
private val testClassName = "com.nextcloud.client.ActivitiesActivityIT"
@Before
fun registerIdlingResource() {
IdlingRegistry.getInstance().register(EspressoIdlingResource.countingIdlingResource)
}
@After
fun unregisterIdlingResource() {
IdlingRegistry.getInstance().unregister(EspressoIdlingResource.countingIdlingResource)
}
@Test
@UiThread
@ScreenshotTest
fun openDrawer() {
launchActivity<ActivitiesActivity>().use { scenario ->
scenario.onActivity { sut ->
onIdleSync {
EspressoIdlingResource.increment()
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open())
sut.dismissSnackbar()
EspressoIdlingResource.decrement()
val screenShotName = createName(testClassName + "_" + "openDrawer", "")
onView(isRoot()).check(matches(isDisplayed()))
screenshotViaName(sut, screenShotName)
}
}
}
}
@Test
@UiThread
@ScreenshotTest
fun loading() {
launchActivity<ActivitiesActivity>().use { scenario ->
scenario.onActivity { sut ->
onIdleSync {
EspressoIdlingResource.increment()
sut.dismissSnackbar()
sut.binding.emptyList.root.visibility = View.GONE
sut.binding.swipeContainingList.visibility = View.GONE
sut.binding.loadingContent.visibility = View.VISIBLE
EspressoIdlingResource.decrement()
val screenShotName = createName(testClassName + "_" + "loading", "")
onView(isRoot()).check(matches(isDisplayed()))
screenshotViaName(sut, screenShotName)
}
}
}
}
@Test
@UiThread
@ScreenshotTest
fun empty() {
launchActivity<ActivitiesActivity>().use { scenario ->
scenario.onActivity { sut ->
onIdleSync {
EspressoIdlingResource.increment()
sut.showActivities(mutableListOf(), nextcloudClient, -1)
sut.setProgressIndicatorState(false)
sut.dismissSnackbar()
EspressoIdlingResource.decrement()
val screenShotName = createName(testClassName + "_" + "empty", "")
onView(isRoot()).check(matches(isDisplayed()))
screenshotViaName(sut, screenShotName)
}
}
}
}
@Test
@UiThread
@ScreenshotTest
@SuppressWarnings("MagicNumber")
fun showActivities() {
val capability = OCCapability()
capability.versionMayor = 20
fileDataStorageManager.saveCapabilities(capability)
val date = GregorianCalendar()
date.set(2005, 4, 17, 10, 35, 30) // random date
val richObjectList: ArrayList<RichObject> = ArrayList()
richObjectList.add(RichObject("file", "abc", "text.txt", "/text.txt", "link", "tag"))
richObjectList.add(RichObject("file", "1", "text.txt", "/text.txt", "link", "tag"))
val previewObjectList1: ArrayList<PreviewObject> = ArrayList()
previewObjectList1.add(PreviewObject(1, "source", "link", true, "text/plain", "view", "test1.txt"))
val previewObjectList3: ArrayList<PreviewObject> = ArrayList()
previewObjectList3.add(PreviewObject(1, "source", "link", true, "image/jpg", "view", "test1.jpg"))
val activities = mutableListOf(
Activity(
1,
date.time,
date.time,
"files",
"file_changed",
"user1",
"user1",
"You changed text.txt",
"",
"icon",
"link",
"files",
"1",
"/text.txt",
previewObjectList1,
RichElement("", richObjectList)
),
Activity(
1,
date.time,
date.time,
"dav",
"calendar_event",
"user1",
"user1",
"You have deleted calendar entry Appointment",
"",
"icon",
"link",
"calendar",
"35",
"",
ArrayList(),
RichElement()
),
Activity(
1,
date.time,
date.time,
"files",
"file_changed",
"user1",
"user1",
"You changed image.jpg",
"",
"icon",
"link",
"files",
"1",
"/image.jpg",
previewObjectList3,
RichElement("", richObjectList)
)
)
launchActivity<ActivitiesActivity>().use { scenario ->
scenario.onActivity { sut ->
onIdleSync {
EspressoIdlingResource.increment()
sut.showActivities(activities as List<Any>?, nextcloudClient, -1)
sut.setProgressIndicatorState(false)
sut.dismissSnackbar()
EspressoIdlingResource.decrement()
val screenShotName = createName(testClassName + "_" + "showActivities", "")
onView(isRoot()).check(matches(isDisplayed()))
screenshotViaName(sut, screenShotName)
}
}
}
}
@Test
@UiThread
@ScreenshotTest
fun error() {
launchActivity<ActivitiesActivity>().use { scenario ->
scenario.onActivity { sut ->
onIdleSync {
EspressoIdlingResource.increment()
sut.showEmptyContent("Error", "Error! Please try again later!")
sut.setProgressIndicatorState(false)
sut.dismissSnackbar()
EspressoIdlingResource.decrement()
val screenShotName = createName(testClassName + "_" + "error", "")
onView(isRoot()).check(matches(isDisplayed()))
screenshotViaName(sut, screenShotName)
}
}
}
}
}

View file

@ -0,0 +1,44 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2021 Andy Scherzinger <info@andy-scherzinger.de>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client;
import android.widget.TextView;
import com.nextcloud.test.GrantStoragePermissionRule;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.R;
import com.owncloud.android.authentication.AuthenticatorActivity;
import com.owncloud.android.utils.ScreenshotTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import androidx.test.core.app.ActivityScenario;
public class AuthenticatorActivityIT extends AbstractIT {
private final String testClassName = "com.nextcloud.client.AuthenticatorActivityIT";
private static final String URL = "cloud.nextcloud.com";
@Rule
public final TestRule permissionRule = GrantStoragePermissionRule.grant();
@Test
@ScreenshotTest
public void login() {
try (ActivityScenario<AuthenticatorActivity> scenario = ActivityScenario.launch(AuthenticatorActivity.class)) {
scenario.onActivity(sut -> onIdleSync(() -> {
((TextView) sut.findViewById(R.id.host_url_input)).setText(URL);
sut.runOnUiThread(() -> sut.getAccountSetupBinding().hostUrlInput.clearFocus());
String screenShotName = createName(testClassName + "_" + "login", "");
screenshotViaName(sut, screenShotName);
}));
}
}
}

View file

@ -0,0 +1,39 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2019 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client;
import android.app.Activity;
import com.nextcloud.test.GrantStoragePermissionRule;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.ui.activity.CommunityActivity;
import com.owncloud.android.utils.ScreenshotTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import androidx.test.espresso.intent.rule.IntentsTestRule;
public class CommunityActivityIT extends AbstractIT {
@Rule public IntentsTestRule<CommunityActivity> activityRule = new IntentsTestRule<>(CommunityActivity.class,
true,
false);
@Rule
public final TestRule permissionRule = GrantStoragePermissionRule.grant();
@Test
@ScreenshotTest
public void open() {
Activity sut = activityRule.launchActivity(null);
screenshot(sut);
}
}

View file

@ -0,0 +1,17 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client;
public enum EndToEndAction {
CREATE_FOLDER,
GO_INTO_FOLDER,
GO_UP,
UPLOAD_FILE,
DOWNLOAD_FILE,
DELETE_FILE,
}

View file

@ -0,0 +1,263 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2019 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client
import android.app.Activity
import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.DrawerActions
import androidx.test.espresso.contrib.NavigationViewActions
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
import com.nextcloud.test.RetryTestRule
import com.owncloud.android.AbstractOnServerIT
import com.owncloud.android.R
import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation
import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation
import com.owncloud.android.lib.resources.files.ToggleFavoriteRemoteOperation
import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation
import com.owncloud.android.lib.resources.shares.OCShare
import com.owncloud.android.lib.resources.shares.ShareType
import com.owncloud.android.operations.CreateFolderOperation
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.adapter.OCFileListItemViewHolder
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
class FileDisplayActivityIT : AbstractOnServerIT() {
@get:Rule
val activityRule = IntentsTestRule(
FileDisplayActivity::class.java,
true,
false
)
@get:Rule
val retryRule = RetryTestRule() // showShares is flaky
// @ScreenshotTest // todo run without real server
@Test
fun showShares() {
Assert.assertTrue(ExistenceCheckRemoteOperation("/shareToAdmin/", true).execute(client).isSuccess)
Assert.assertTrue(CreateFolderRemoteOperation("/shareToAdmin/", true).execute(client).isSuccess)
Assert.assertTrue(CreateFolderRemoteOperation("/shareToGroup/", true).execute(client).isSuccess)
Assert.assertTrue(CreateFolderRemoteOperation("/shareViaLink/", true).execute(client).isSuccess)
Assert.assertTrue(CreateFolderRemoteOperation("/noShare/", true).execute(client).isSuccess)
// assertTrue(new CreateFolderRemoteOperation("/shareToCircle/", true).execute(client).isSuccess());
// share folder to user "admin"
Assert.assertTrue(
CreateShareRemoteOperation(
"/shareToAdmin/",
ShareType.USER,
"admin",
false,
"",
OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER
).execute(client).isSuccess
)
// share folder via public link
Assert.assertTrue(
CreateShareRemoteOperation(
"/shareViaLink/",
ShareType.PUBLIC_LINK,
"",
true,
"",
OCShare.READ_PERMISSION_FLAG
).execute(client).isSuccess
)
// share folder to group
Assert.assertTrue(
CreateShareRemoteOperation(
"/shareToGroup/",
ShareType.GROUP,
"users",
false,
"",
OCShare.NO_PERMISSION
).execute(client).isSuccess
)
// share folder to circle
// get share
// RemoteOperationResult searchResult = new GetShareesRemoteOperation("publicCircle", 1, 50).execute(client);
// assertTrue(searchResult.getLogMessage(), searchResult.isSuccess());
//
// JSONObject resultJson = (JSONObject) searchResult.getData().get(0);
// String circleId = resultJson.getJSONObject("value").getString("shareWith");
//
// assertTrue(new CreateShareRemoteOperation("/shareToCircle/",
// ShareType.CIRCLE,
// circleId,
// false,
// "",
// OCShare.DEFAULT_PERMISSION)
// .execute(client).isSuccess());
val sut: Activity = activityRule.launchActivity(null)
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
// open drawer
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open())
// click "shared"
onView(withId(R.id.nav_view))
.perform(NavigationViewActions.navigateTo(R.id.nav_shared))
shortSleep()
shortSleep()
// screenshot(sut) // todo run without real server
}
@Test
fun allFiles() {
val sut = activityRule.launchActivity(null)
// given test folder
Assert.assertTrue(
CreateFolderOperation("/test/", user, targetContext, storageManager)
.execute(client)
.isSuccess
)
// navigate into it
val test = storageManager.getFileByPath("/test/")
sut.file = test
sut.startSyncFolderOperation(test, false)
Assert.assertEquals(storageManager.getFileByPath("/test/"), sut.currentDir)
// open drawer
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open())
// click "all files"
onView(withId(R.id.nav_view))
.perform(NavigationViewActions.navigateTo(R.id.nav_all_files))
// then should be in root again
shortSleep()
Assert.assertEquals(storageManager.getFileByPath("/"), sut.currentDir)
}
@Test
fun checkToolbarTitleOnNavigation() {
// Create folder structure
val topFolder = "folder1"
val childFolder = "folder2"
CreateFolderOperation("/$topFolder/", user, targetContext, storageManager)
.execute(client)
CreateFolderOperation("/$topFolder/$childFolder/", user, targetContext, storageManager)
.execute(client)
activityRule.launchActivity(null)
shortSleep()
// go into "foo"
onView(withText(topFolder)).perform(click())
shortSleep()
// check title is right
checkToolbarTitle(topFolder)
// go into "bar"
onView(withText(childFolder)).perform(click())
shortSleep()
// check title is right
checkToolbarTitle(childFolder)
// browse back up, we should be back in "foo"
Espresso.pressBack()
shortSleep()
// check title is right
checkToolbarTitle(topFolder)
}
private fun checkToolbarTitle(childFolder: String) {
onView(withId(R.id.appbar)).check(
matches(
hasDescendant(
withText(childFolder)
)
)
)
}
@Test
fun browseFavoriteAndBack() {
// Create folder structure
val topFolder = "folder1"
CreateFolderOperation("/$topFolder/", user, targetContext, storageManager)
.execute(client)
ToggleFavoriteRemoteOperation(true, "/$topFolder/")
.execute(client)
val sut = activityRule.launchActivity(null)
// navigate to favorites
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open())
onView(withId(R.id.nav_view))
.perform(NavigationViewActions.navigateTo(R.id.nav_favorites))
shortSleep()
// check sort button is not shown, favorites are not sortable
onView(withId(R.id.sort_button)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
// browse into folder
onView(withId(R.id.list_root))
.perform(closeSoftKeyboard())
.perform(
RecyclerViewActions.actionOnItemAtPosition<OCFileListItemViewHolder>(
0,
click()
)
)
shortSleep()
checkToolbarTitle(topFolder)
// sort button should now be visible
onView(withId(R.id.sort_button)).check(matches(ViewMatchers.isDisplayed()))
// browse back, should be back to All Files
Espresso.pressBack()
checkToolbarTitle(sut.getString(R.string.app_name))
onView(withId(R.id.sort_button)).check(matches(ViewMatchers.isDisplayed()))
}
@Test
fun switchToGridView() {
activityRule.launchActivity(null)
Assert.assertTrue(
CreateFolderOperation("/test/", user, targetContext, storageManager)
.execute(client)
.isSuccess
)
onView(withId(R.id.switch_grid_view_button)).perform(click())
}
@Test
fun openAccountSwitcher() {
activityRule.launchActivity(null)
onView(withId(R.id.switch_account_button)).perform(click())
}
}

View file

@ -0,0 +1,124 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2019 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client
import android.Manifest
import androidx.test.espresso.Espresso
import androidx.test.espresso.contrib.DrawerActions
import androidx.test.espresso.contrib.NavigationViewActions
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.rule.GrantPermissionRule
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.utils.ScreenshotTest
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
class FileDisplayActivityScreenshotIT : AbstractIT() {
@get:Rule
val activityRule = IntentsTestRule(
FileDisplayActivity::class.java,
true,
false
)
@get:Rule
val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
companion object {
private const val TAG = "FileDisplayActivityScreenshotIT"
}
@Test
@ScreenshotTest
fun open() {
try {
val sut = activityRule.launchActivity(null)
shortSleep()
sut.runOnUiThread {
sut.listOfFilesFragment!!.setFabEnabled(false)
sut.resetScrolling(true)
sut.listOfFilesFragment!!.setEmptyListLoadingMessage()
sut.listOfFilesFragment!!.isLoading = false
}
shortSleep()
waitForIdleSync()
screenshot(sut)
} catch (e: SecurityException) {
Log_OC.e(TAG, "Error caught at open $e")
}
}
@Test
@ScreenshotTest
fun showMediaThenAllFiles() {
try {
val fileDisplayActivity = activityRule.launchActivity(null)
val sut = fileDisplayActivity.listOfFilesFragment
Assert.assertNotNull(sut)
sut!!.setFabEnabled(false)
sut.setEmptyListLoadingMessage()
sut.isLoading = false
// open drawer
Espresso.onView(ViewMatchers.withId(R.id.drawer_layout)).perform(DrawerActions.open())
// click "all files"
Espresso.onView(ViewMatchers.withId(R.id.nav_view))
.perform(NavigationViewActions.navigateTo(R.id.nav_gallery))
// wait
shortSleep()
// click "all files"
Espresso.onView(ViewMatchers.withId(R.id.drawer_layout)).perform(DrawerActions.open())
Espresso.onView(ViewMatchers.withId(R.id.nav_view))
.perform(NavigationViewActions.navigateTo(R.id.nav_all_files))
// then compare screenshot
shortSleep()
sut.setFabEnabled(false)
sut.setEmptyListLoadingMessage()
sut.isLoading = false
shortSleep()
screenshot(fileDisplayActivity)
} catch (e: SecurityException) {
Log_OC.e(TAG, "Error caught at open $e")
}
}
@Test
@ScreenshotTest
fun drawer() {
try {
val sut = activityRule.launchActivity(null)
Espresso.onView(ViewMatchers.withId(R.id.drawer_layout)).perform(DrawerActions.open())
shortSleep()
sut.runOnUiThread {
sut.hideInfoBox()
sut.resetScrolling(true)
sut.listOfFilesFragment!!.setFabEnabled(false)
sut.listOfFilesFragment!!.setEmptyListLoadingMessage()
sut.listOfFilesFragment!!.isLoading = false
}
shortSleep()
waitForIdleSync()
screenshot(sut)
} catch (e: SecurityException) {
Log_OC.e(TAG, "Error caught at open $e")
}
}
}

View file

@ -0,0 +1,34 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2019 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client;
import android.app.Activity;
import com.nextcloud.client.onboarding.FirstRunActivity;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.utils.ScreenshotTest;
import org.junit.Rule;
import org.junit.Test;
import androidx.test.espresso.intent.rule.IntentsTestRule;
public class FirstRunActivityIT extends AbstractIT {
@Rule public IntentsTestRule<FirstRunActivity> activityRule = new IntentsTestRule<>(FirstRunActivity.class,
true,
false);
@Test
@ScreenshotTest
public void open() {
Activity sut = activityRule.launchActivity(null);
screenshot(sut);
}
}

View file

@ -0,0 +1,71 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2019 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client
import android.app.Activity
import android.content.Intent
import android.os.Looper
import androidx.test.espresso.intent.rule.IntentsTestRule
import com.nextcloud.test.GrantStoragePermissionRule
import com.owncloud.android.AbstractIT
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
import com.owncloud.android.ui.activity.RequestCredentialsActivity
import com.owncloud.android.ui.activity.SettingsActivity
import com.owncloud.android.utils.EncryptionUtils
import com.owncloud.android.utils.ScreenshotTest
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
@Suppress("FunctionNaming")
class SettingsActivityIT : AbstractIT() {
@get:Rule
val activityRule = IntentsTestRule(
SettingsActivity::class.java,
true,
false
)
@get:Rule
val permissionRule = GrantStoragePermissionRule.grant()
@Test
@ScreenshotTest
fun open() {
val sut: Activity = activityRule.launchActivity(null)
screenshot(sut)
}
@Test
@ScreenshotTest
fun showMnemonic_Error() {
val sut = activityRule.launchActivity(null)
sut.handleMnemonicRequest(null)
shortSleep()
waitForIdleSync()
screenshot(sut)
}
@Test
fun showMnemonic() {
if (Looper.myLooper() == null) {
Looper.prepare()
}
val intent = Intent()
intent.putExtra(RequestCredentialsActivity.KEY_CHECK_RESULT, RequestCredentialsActivity.KEY_CHECK_RESULT_TRUE)
val arbitraryDataProvider = ArbitraryDataProviderImpl(targetContext)
arbitraryDataProvider.storeOrUpdateKeyValue(user.accountName, EncryptionUtils.MNEMONIC, "Secret mnemonic")
val sut = activityRule.launchActivity(null)
sut.runOnUiThread {
sut.handleMnemonicRequest(intent)
}
Looper.myLooper()?.quitSafely()
Assert.assertTrue(true) // if we reach this, everything is ok
}
}

View file

@ -0,0 +1,81 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client;
import android.app.Activity;
import android.content.Intent;
import com.nextcloud.client.preferences.SubFolderRule;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.databinding.SyncedFoldersLayoutBinding;
import com.owncloud.android.datamodel.MediaFolderType;
import com.owncloud.android.datamodel.SyncedFolder;
import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
import com.owncloud.android.ui.activity.SyncedFoldersActivity;
import com.owncloud.android.ui.dialog.SyncedFolderPreferencesDialogFragment;
import com.owncloud.android.utils.ScreenshotTest;
import org.junit.Rule;
import org.junit.Test;
import java.util.Objects;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
public class SyncedFoldersActivityIT extends AbstractIT {
@Rule public IntentsTestRule<SyncedFoldersActivity> activityRule = new IntentsTestRule<>(SyncedFoldersActivity.class,
true,
false);
@Test
@ScreenshotTest
public void open() {
SyncedFoldersActivity activity = activityRule.launchActivity(null);
activity.adapter.clear();
SyncedFoldersLayoutBinding sut = activity.binding;
shortSleep();
screenshot(sut.emptyList.emptyListView);
}
@Test
@ScreenshotTest
public void testSyncedFolderDialog() {
SyncedFolderDisplayItem item = new SyncedFolderDisplayItem(1,
"/sdcard/DCIM/",
"/InstantUpload/",
true,
false,
false,
true,
"test@https://nextcloud.localhost",
0,
0,
true,
1000,
"Name",
MediaFolderType.IMAGE,
false,
SubFolderRule.YEAR_MONTH,
false,
SyncedFolder.NOT_SCANNED_YET);
SyncedFolderPreferencesDialogFragment sut = SyncedFolderPreferencesDialogFragment.newInstance(item, 0);
Intent intent = new Intent(targetContext, SyncedFoldersActivity.class);
SyncedFoldersActivity activity = activityRule.launchActivity(intent);
sut.show(activity.getSupportFragmentManager(), "");
getInstrumentation().waitForIdleSync();
shortSleep();
screenshot(Objects.requireNonNull(sut.requireDialog().getWindow()).getDecorView());
}
}

View file

@ -0,0 +1,31 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH
* SPDX-FileCopyrightText: 2019 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client
import android.app.Application
import android.app.Instrumentation
import android.content.Context
import android.os.Build
import androidx.test.runner.AndroidJUnitRunner
import com.github.tmurakami.dexopener.DexOpener
import com.nextcloud.test.TestMainApp
class TestRunner : AndroidJUnitRunner() {
@Throws(ClassNotFoundException::class, IllegalAccessException::class, InstantiationException::class)
override fun newApplication(cl: ClassLoader, className: String, context: Context): Application {
/*
* Initialize DexOpener only on API below 28 to enable mocking of Kotlin classes.
* On API 28+ the platform supports mocking natively.
*/
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
DexOpener.install(this)
}
return Instrumentation.newApplication(TestMainApp::class.java, context)
}
}

View file

@ -0,0 +1,30 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.ui.activity.UploadListActivity;
import com.owncloud.android.utils.ScreenshotTest;
import org.junit.Rule;
import org.junit.Test;
import androidx.test.espresso.intent.rule.IntentsTestRule;
public class UploadListActivityActivityIT extends AbstractIT {
@Rule public IntentsTestRule<UploadListActivity> activityRule = new IntentsTestRule<>(UploadListActivity.class,
true,
false);
@Test
@ScreenshotTest
public void openDrawer() {
super.openDrawer(activityRule);
}
}

View file

@ -0,0 +1,40 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.account
import android.os.Parcel
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class AnonymousUserTest {
@Test
fun anonymousUserImplementsParcelable() {
// GIVEN
// anonymous user instance
val original = AnonymousUser("test_account")
// WHEN
// instance is serialized into Parcel
// instance is retrieved from Parcel
val parcel = Parcel.obtain()
parcel.setDataPosition(0)
parcel.writeParcelable(original, 0)
parcel.setDataPosition(0)
val retrieved = parcel.readParcelable<User>(User::class.java.classLoader)
// THEN
// retrieved instance in distinct
// instances are equal
Assert.assertNotSame(original, retrieved)
Assert.assertTrue(retrieved is AnonymousUser)
Assert.assertEquals(original, retrieved)
}
}

View file

@ -0,0 +1,58 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.account
import android.os.Parcel
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotSame
import org.junit.Assert.assertTrue
import org.junit.Test
class MockUserTest {
private companion object {
const val ACCOUNT_NAME = "test_account_name"
const val ACCOUNT_TYPE = "test_account_type"
}
@Test
fun mock_user_is_parcelable() {
// GIVEN
// mock user instance
val original = MockUser(ACCOUNT_NAME, ACCOUNT_TYPE)
// WHEN
// instance is serialized into Parcel
// instance is retrieved from Parcel
val parcel = Parcel.obtain()
parcel.setDataPosition(0)
parcel.writeParcelable(original, 0)
parcel.setDataPosition(0)
val retrieved = parcel.readParcelable<User>(User::class.java.classLoader)
// THEN
// retrieved instance in distinct
// instances are equal
assertNotSame(original, retrieved)
assertTrue(retrieved is MockUser)
assertEquals(original, retrieved)
}
@Test
fun mock_user_has_platform_account() {
// GIVEN
// mock user instance
val mock = MockUser(ACCOUNT_NAME, ACCOUNT_TYPE)
// THEN
// can convert to platform account
val account = mock.toPlatformAccount()
assertEquals(ACCOUNT_NAME, account.name)
assertEquals(ACCOUNT_TYPE, account.type)
}
}

View file

@ -0,0 +1,64 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2019 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.account;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.net.Uri;
import android.os.Bundle;
import com.owncloud.android.AbstractOnServerIT;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientManager;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import org.junit.Test;
import java.io.IOException;
import androidx.test.platform.app.InstrumentationRegistry;
import static org.junit.Assert.assertEquals;
public class OwnCloudClientManagerTest extends AbstractOnServerIT {
/**
* Like on files app we create & store an account in Android's account manager.
*/
@Test
public void testUserId() throws OperationCanceledException, AuthenticatorException, IOException,
AccountUtils.AccountNotFoundException {
Bundle arguments = InstrumentationRegistry.getArguments();
Uri url = Uri.parse(arguments.getString("TEST_SERVER_URL"));
String loginName = arguments.getString("TEST_SERVER_USERNAME");
String password = arguments.getString("TEST_SERVER_PASSWORD");
AccountManager accountManager = AccountManager.get(targetContext);
String accountName = AccountUtils.buildAccountName(url, loginName);
Account newAccount = new Account(accountName, "nextcloud");
accountManager.addAccountExplicitly(newAccount, password, null);
accountManager.setUserData(newAccount, AccountUtils.Constants.KEY_OC_BASE_URL, url.toString());
accountManager.setUserData(newAccount, AccountUtils.Constants.KEY_USER_ID, loginName);
OwnCloudClientManager manager = new OwnCloudClientManager();
OwnCloudAccount account = new OwnCloudAccount(newAccount, targetContext);
OwnCloudClient client = manager.getClientFor(account, targetContext);
assertEquals(loginName, client.getUserId());
accountManager.removeAccountExplicitly(newAccount);
assertEquals(1, accountManager.getAccounts().length);
}
}

View file

@ -0,0 +1,106 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.account
import android.accounts.Account
import android.net.Uri
import android.os.Parcel
import com.owncloud.android.lib.common.OwnCloudAccount
import com.owncloud.android.lib.common.OwnCloudBasicCredentials
import com.owncloud.android.lib.resources.status.OwnCloudVersion
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotSame
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import java.net.URI
class RegisteredUserTest {
private companion object {
fun buildTestUser(accountName: String): RegisteredUser {
val uri = Uri.parse("https://nextcloud.localhost")
val credentials = OwnCloudBasicCredentials("user", "pass")
val account = Account(accountName, "test-type")
val ownCloudAccount = OwnCloudAccount(uri, credentials)
val server = Server(
uri = URI(uri.toString()),
version = OwnCloudVersion.nextcloud_17
)
return RegisteredUser(
account = account,
ownCloudAccount = ownCloudAccount,
server = server
)
}
}
private lateinit var user: RegisteredUser
@Before
fun setUp() {
user = buildTestUser("test@nextcloud.localhost")
}
@Test
fun registeredUserImplementsParcelable() {
// GIVEN
// registered user instance
// WHEN
// instance is serialized into Parcel
// instance is retrieved from Parcel
val parcel = Parcel.obtain()
parcel.setDataPosition(0)
parcel.writeParcelable(user, 0)
parcel.setDataPosition(0)
val deserialized = parcel.readParcelable<User>(User::class.java.classLoader)
// THEN
// retrieved instance in distinct
// instances are equal
assertNotSame(user, deserialized)
assertTrue(deserialized is RegisteredUser)
assertEquals(user, deserialized)
}
@Test
fun accountNamesEquality() {
// GIVEN
// registered user instance with lower-case account name
// registered user instance with mixed-case account name
val user1 = buildTestUser("account_name")
val user2 = buildTestUser("Account_Name")
// WHEN
// account names are checked for equality
val equal = user1.nameEquals(user2)
// THEN
// account names are equal
assertTrue(equal)
}
@Test
fun accountNamesEqualityCheckIsNullSafe() {
// GIVEN
// registered user instance with lower-case account name
// null account
val user1 = buildTestUser("account_name")
val user2: User? = null
// WHEN
// account names are checked for equality against null
val equal = user1.nameEquals(user2)
// THEN
// account names are not equal
assertFalse(equal)
}
}

View file

@ -0,0 +1,79 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2019-2023 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.account;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.os.Bundle;
import com.nextcloud.client.preferences.AppPreferences;
import com.nextcloud.client.preferences.AppPreferencesImpl;
import com.owncloud.android.AbstractOnServerIT;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import org.junit.Before;
import org.junit.Test;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertFalse;
public class UserAccountManagerImplTest extends AbstractOnServerIT {
private AccountManager accountManager;
@Before
public void setUp() {
accountManager = AccountManager.get(targetContext);
}
@Test
public void updateOneAccount() {
AppPreferences appPreferences = AppPreferencesImpl.fromContext(targetContext);
UserAccountManagerImpl sut = new UserAccountManagerImpl(targetContext, accountManager);
assertEquals(1, sut.getAccounts().length);
assertFalse(appPreferences.isUserIdMigrated());
Account account = sut.getAccounts()[0];
// for testing remove userId
accountManager.setUserData(account, AccountUtils.Constants.KEY_USER_ID, null);
assertNull(accountManager.getUserData(account, AccountUtils.Constants.KEY_USER_ID));
boolean success = sut.migrateUserId();
assertTrue(success);
Bundle arguments = androidx.test.platform.app.InstrumentationRegistry.getArguments();
String userId = arguments.getString("TEST_SERVER_USERNAME");
// assume that userId == loginname (as we manually set it)
assertEquals(userId, accountManager.getUserData(account, AccountUtils.Constants.KEY_USER_ID));
}
@Test
public void checkName() {
UserAccountManagerImpl sut = new UserAccountManagerImpl(targetContext, accountManager);
Account owner = new Account("John@nextcloud.local", "nextcloud");
Account account1 = new Account("John@nextcloud.local", "nextcloud");
Account account2 = new Account("john@nextcloud.local", "nextcloud");
OCFile file1 = new OCFile("/test1.pdf");
file1.setOwnerId("John");
assertTrue(sut.accountOwnsFile(file1, owner));
assertTrue(sut.accountOwnsFile(file1, account1));
assertTrue(sut.accountOwnsFile(file1, account2));
file1.setOwnerId("john");
assertTrue(sut.accountOwnsFile(file1, owner));
assertTrue(sut.accountOwnsFile(file1, account1));
assertTrue(sut.accountOwnsFile(file1, account2));
}
}

View file

@ -0,0 +1,95 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper_ozturk@proton.me>
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.assistant
import com.nextcloud.client.assistant.repository.AssistantRepository
import com.owncloud.android.AbstractOnServerIT
import com.owncloud.android.lib.resources.status.NextcloudVersion
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
@Suppress("MagicNumber")
class AssistantRepositoryTests : AbstractOnServerIT() {
private var sut: AssistantRepository? = null
@Before
fun setup() {
sut = AssistantRepository(nextcloudClient)
}
@Test
fun testGetTaskTypes() {
testOnlyOnServer(NextcloudVersion.nextcloud_28)
if (capability.assistant.isFalse) {
return
}
val result = sut?.getTaskTypes()
assertTrue(result?.isSuccess == true)
val taskTypes = result?.resultData?.types
assertTrue(taskTypes?.isNotEmpty() == true)
}
@Test
fun testGetTaskList() {
testOnlyOnServer(NextcloudVersion.nextcloud_28)
if (capability.assistant.isFalse) {
return
}
val result = sut?.getTaskList("assistant")
assertTrue(result?.isSuccess == true)
val taskList = result?.resultData?.tasks
assertTrue(taskList?.isEmpty() == true || (taskList?.size ?: 0) > 0)
}
@Test
fun testCreateTask() {
testOnlyOnServer(NextcloudVersion.nextcloud_28)
if (capability.assistant.isFalse) {
return
}
val input = "Give me some random output for test purpose"
val type = "OCP\\TextProcessing\\FreePromptTaskType"
val result = sut?.createTask(input, type)
assertTrue(result?.isSuccess == true)
}
@Test
fun testDeleteTask() {
testOnlyOnServer(NextcloudVersion.nextcloud_28)
if (capability.assistant.isFalse) {
return
}
testCreateTask()
sleep(120)
val resultOfTaskList = sut?.getTaskList("assistant")
assertTrue(resultOfTaskList?.isSuccess == true)
sleep(120)
val taskList = resultOfTaskList?.resultData?.tasks
assert((taskList?.size ?: 0) > 0)
val result = sut?.deleteTask(taskList!!.first().id)
assertTrue(result?.isSuccess == true)
}
}

View file

@ -0,0 +1,82 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.database.migrations
import androidx.room.testing.MigrationTestHelper
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.nextcloud.client.database.NextcloudDatabase
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.io.IOException
@RunWith(AndroidJUnit4::class)
class MigrationTest {
@get:Rule
val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
NextcloudDatabase::class.java.canonicalName,
FrameworkSQLiteOpenHelperFactory()
)
@Test
@Throws(IOException::class)
fun migrate67to68() {
val nullId = 6
val notNullId = 7
val notNullLocalIdValue = 1234
var db = helper.createDatabase(TEST_DB, 67)
// create some data
db.apply {
execSQL(
"INSERT INTO filelist VALUES($nullId,'foo.zip','foo.zip','/foo.zip','/foo.zip',1,1643648081," +
"1643648081000,'application/zip',178382355,NULL,'test@nextcloud',1674554955638,0,0,''," +
"'f45028679b68652c6b345b5d8c9a5d63',0,'RGDNVW','00014889ocb5tqw7y2f3',NULL,0,0,0,0,NULL,0,NULL," +
"0,0,'test','test','','[]',NULL,'null',0,-1,NULL,NULL,NULL,0,0,NULL);"
)
execSQL(
"INSERT INTO filelist VALUES($notNullId,'foo.zip','foo.zip','/foo.zip','/foo.zip',1,1643648081," +
"1643648081000,'application/zip',178382355,NULL,'test@nextcloud',1674554955638,0,0,''," +
"'f45028679b68652c6b345b5d8c9a5d63',0,'RGDNVW','00014889ocb5tqw7y2f3',NULL,0,0,0,0,NULL,0,NULL," +
"0,0,'test','test','','[]',NULL,'null',0,-1,NULL,NULL,NULL,0,0,NULL);"
)
execSQL("UPDATE filelist SET local_id = NULL WHERE _id = $nullId")
execSQL("UPDATE filelist SET local_id = $notNullLocalIdValue WHERE _id = $notNullId")
close()
}
// run migration and validate schema matches
db = helper.runMigrationsAndValidate(TEST_DB, 68, true, Migration67to68())
// check values are correct
db.query("SELECT local_id FROM filelist WHERE _id=$nullId").use { cursor ->
cursor.moveToFirst()
val localId = cursor.getInt(cursor.getColumnIndex("local_id"))
assertEquals("NULL localId is not -1 after migration", -1, localId)
}
db.query("SELECT local_id FROM filelist WHERE _id=$notNullId").use { cursor ->
cursor.moveToFirst()
val localId = cursor.getInt(cursor.getColumnIndex("local_id"))
assertEquals("Not null localId is not the same after migration", notNullLocalIdValue, localId)
}
db.close()
}
companion object {
private const val TEST_DB = "migration-test"
}
}

View file

@ -0,0 +1,71 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.documentscan
import android.graphics.pdf.PdfRenderer
import android.os.ParcelFileDescriptor
import com.nextcloud.client.logger.Logger
import com.owncloud.android.AbstractIT
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import java.io.File
internal class GeneratePDFUseCaseTest : AbstractIT() {
@MockK
private lateinit var logger: Logger
private lateinit var sut: GeneratePDFUseCase
@Before
fun setUp() {
MockKAnnotations.init(this, relaxed = true)
sut = GeneratePDFUseCase(logger)
}
@Test
fun invalidArguments_shouldReturnFalse() {
var result = sut.execute(emptyList(), "/test/foo.pdf")
assertFalse("Usecase does not indicate failure with invalid arguments", result)
result = sut.execute(listOf("/test.jpg"), "")
assertFalse("Usecase does not indicate failure with invalid arguments", result)
}
@Test
fun generatePdf_checkPages() {
// can't think of how to test the _content_ of the pages
val images = listOf(
getFile("image.jpg"),
getFile("christine.jpg")
).map { it.path }
val output = "/sdcard/test.pdf"
val result = sut.execute(images, output)
assertTrue("Usecase does not indicate success", result)
val outputFile = File(output)
assertTrue("Output file does not exist", outputFile.exists())
ParcelFileDescriptor.open(outputFile, ParcelFileDescriptor.MODE_READ_ONLY).use {
PdfRenderer(it).use { renderer ->
val pageCount = renderer.pageCount
assertTrue("Page count is not correct", pageCount == 2)
}
}
// clean up
outputFile.delete()
}
}

View file

@ -0,0 +1,41 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.etm
import android.app.Activity
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
import com.owncloud.android.AbstractIT
import com.owncloud.android.utils.ScreenshotTest
import org.junit.Rule
import org.junit.Test
class EtmActivityTest : AbstractIT() {
@get:Rule
var activityRule = IntentsTestRule(EtmActivity::class.java, true, false)
@Test
@ScreenshotTest
fun overview() {
val sut: Activity = activityRule.launchActivity(null)
waitForIdleSync()
screenshot(sut)
}
@Test
@ScreenshotTest
fun accounts() {
val sut: EtmActivity = activityRule.launchActivity(null)
UiThreadStatement.runOnUiThread { sut.vm.onPageSelected(1) }
screenshot(sut)
}
}

View file

@ -0,0 +1,222 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.files
import android.net.Uri
import com.nextcloud.client.account.Server
import com.nextcloud.client.account.User
import com.nextcloud.client.account.UserAccountManager
import com.owncloud.android.lib.resources.status.OwnCloudVersion
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertSame
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Suite
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import java.net.URI
@RunWith(Suite::class)
@Suite.SuiteClasses(
DeepLinkHandlerTest.DeepLinkPattern::class,
DeepLinkHandlerTest.FileDeepLink::class
)
class DeepLinkHandlerTest {
@RunWith(Parameterized::class)
class DeepLinkPattern {
companion object {
val FILE_ID = 1234
val SERVER_BASE_URLS = listOf(
"http://hostname.net",
"https://hostname.net",
"http://hostname.net/subdir1",
"https://hostname.net/subdir1",
"http://hostname.net/subdir1/subdir2",
"https://hostname.net/subdir1/subdir2",
"http://hostname.net/subdir1/subdir2/subdir3",
"https://hostname.net/subdir1/subdir2/subdir3"
)
val INDEX_PHP_PATH = listOf(
"",
"/index.php"
)
@Parameterized.Parameters
@JvmStatic
fun urls(): Array<Array<Any>> {
val testInput = mutableListOf<Array<Any>>()
SERVER_BASE_URLS.forEach { baseUrl ->
INDEX_PHP_PATH.forEach { indexPath ->
val url = "$baseUrl$indexPath/f/$FILE_ID"
testInput.add(arrayOf(baseUrl, indexPath, "$FILE_ID", url))
}
}
return testInput.toTypedArray()
}
}
@Parameterized.Parameter(0)
lateinit var baseUrl: String
@Parameterized.Parameter(1)
lateinit var indexPath: String
@Parameterized.Parameter(2)
lateinit var fileId: String
@Parameterized.Parameter(3)
lateinit var url: String
@Test
fun matches_deep_link_patterns() {
val match = DeepLinkHandler.DEEP_LINK_PATTERN.matchEntire(url)
assertNotNull("Url [$url] does not match pattern", match)
assertEquals(baseUrl, match?.groupValues?.get(DeepLinkHandler.BASE_URL_GROUP_INDEX))
assertEquals(indexPath, match?.groupValues?.get(DeepLinkHandler.INDEX_PATH_GROUP_INDEX))
assertEquals(fileId, match?.groupValues?.get(DeepLinkHandler.FILE_ID_GROUP_INDEX))
}
@Test
fun no_trailing_path_allowed_after_file_id() {
val invalidUrl = "$url/"
val match = DeepLinkHandler.DEEP_LINK_PATTERN.matchEntire(invalidUrl)
assertNull(match)
}
}
class FileDeepLink {
companion object {
const val OTHER_SERVER_BASE_URL = "https://someotherserver.net"
const val SERVER_BASE_URL = "https://server.net"
const val FILE_ID = "1234567890"
val DEEP_LINK = Uri.parse("$SERVER_BASE_URL/index.php/f/$FILE_ID")
fun createMockUser(serverBaseUrl: String): User {
val user = mock<User>()
val uri = URI.create(serverBaseUrl)
val server = Server(uri = uri, version = OwnCloudVersion.nextcloud_19)
whenever(user.server).thenReturn(server)
return user
}
}
@Mock
lateinit var userAccountManager: UserAccountManager
lateinit var allUsers: List<User>
lateinit var handler: DeepLinkHandler
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(userAccountManager.allUsers).thenAnswer { allUsers }
allUsers = emptyList()
handler = DeepLinkHandler(userAccountManager)
}
@Test
fun no_user_can_open_file() {
// GIVEN
// no user capable of opening the file
allUsers = listOf(
createMockUser(OTHER_SERVER_BASE_URL),
createMockUser(OTHER_SERVER_BASE_URL)
)
// WHEN
// deep link is parsed
val match = handler.parseDeepLink(DEEP_LINK)
// THEN
// link is valid
// no user can open the file
assertNotNull(match)
assertEquals(0, match?.users?.size)
}
@Test
fun single_user_can_open_file() {
// GIVEN
// multiple users registered
// one user capable of opening the link
val matchingUser = createMockUser(SERVER_BASE_URL)
allUsers = listOf(
createMockUser(OTHER_SERVER_BASE_URL),
matchingUser,
createMockUser(OTHER_SERVER_BASE_URL)
)
// WHEN
// deep link is parsed
val match = handler.parseDeepLink(DEEP_LINK)
// THEN
// link can be opened by single user
assertNotNull(match)
assertSame(matchingUser, match?.users?.get(0))
}
@Test
fun multiple_users_can_open_file() {
// GIVEN
// mutltiple users registered
// multiple users capable of opening the link
val matchingUsers = setOf(
createMockUser(SERVER_BASE_URL),
createMockUser(SERVER_BASE_URL)
)
val otherUsers = setOf(
createMockUser(OTHER_SERVER_BASE_URL),
createMockUser(OTHER_SERVER_BASE_URL)
)
allUsers = listOf(matchingUsers, otherUsers).flatten()
// WHEN
// deep link is parsed
val match = handler.parseDeepLink(DEEP_LINK)
// THEN
// link can be opened by multiple matching users
assertNotNull(match)
assertEquals(matchingUsers, match?.users?.toSet())
}
@Test
fun match_contains_extracted_file_id() {
// WHEN
// valid deep file link is parsed
val match = handler.parseDeepLink(DEEP_LINK)
// THEN
// file id is returned
assertEquals(FILE_ID, match?.fileId)
}
@Test
fun no_match_for_invalid_link() {
// GIVEN
// invalid deep link
val invalidLink = Uri.parse("http://www.dodgylink.com/index.php")
// WHEN
// deep link is parsed
val match = handler.parseDeepLink(invalidLink)
// THEN
// no match
assertNull(match)
}
}
}

View file

@ -0,0 +1,46 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.files.download
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.rule.ServiceTestRule
import com.nextcloud.client.account.MockUser
import com.nextcloud.client.jobs.transfer.FileTransferService
import io.mockk.MockKAnnotations
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
class DownloaderServiceTest {
@get:Rule
val service = ServiceTestRule.withTimeout(3, TimeUnit.SECONDS)
val user = MockUser()
@Before
fun setUp() {
MockKAnnotations.init(this, relaxed = true)
}
@Test(expected = TimeoutException::class)
fun cannot_bind_to_service_without_user() {
val intent = FileTransferService.createBindIntent(getApplicationContext(), user)
intent.removeExtra(FileTransferService.EXTRA_USER)
service.bindService(intent)
}
@Test
fun bind_with_user() {
val intent = FileTransferService.createBindIntent(getApplicationContext(), user)
val binder = service.bindService(intent)
assertTrue(binder is FileTransferService.Binder)
}
}

View file

@ -0,0 +1,514 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.files.download
import com.nextcloud.client.account.User
import com.nextcloud.client.files.DownloadRequest
import com.nextcloud.client.files.Registry
import com.nextcloud.client.files.Request
import com.nextcloud.client.jobs.transfer.Transfer
import com.nextcloud.client.jobs.transfer.TransferState
import com.owncloud.android.datamodel.OCFile
import io.mockk.CapturingSlot
import io.mockk.MockKAnnotations
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.verify
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Suite
import java.util.UUID
@RunWith(Suite::class)
@Suite.SuiteClasses(
RegistryTest.Pending::class,
RegistryTest.Start::class,
RegistryTest.Progress::class,
RegistryTest.Complete::class,
RegistryTest.GetTransfers::class,
RegistryTest.IsRunning::class
)
class RegistryTest {
abstract class Base {
companion object {
const val MAX_TRANSFER_THREADS = 4
const val PROGRESS_FULL = 100
const val PROGRESS_HALF = 50
}
@MockK
lateinit var user: User
lateinit var file: OCFile
@MockK
lateinit var onTransferStart: (UUID, Request) -> Unit
@MockK
lateinit var onTransferChanged: (Transfer) -> Unit
internal lateinit var registry: Registry
@Before
fun setUpBase() {
MockKAnnotations.init(this, relaxed = true)
file = OCFile("/test/path")
registry = Registry(onTransferStart, onTransferChanged, MAX_TRANSFER_THREADS)
resetMocks()
}
fun resetMocks() {
clearAllMocks()
every { onTransferStart(any(), any()) } answers {}
every { onTransferChanged(any()) } answers {}
}
}
class Pending : Base() {
@Test
fun inserting_pending_transfer() {
// GIVEN
// registry has no pending transfers
assertEquals(0, registry.pending.size)
// WHEN
// new transfer requests added
val addedTransfersCount = 10
for (i in 0 until addedTransfersCount) {
val request = DownloadRequest(user, file)
registry.add(request)
}
// THEN
// transfer is added to the pending queue
assertEquals(addedTransfersCount, registry.pending.size)
}
}
class Start : Base() {
companion object {
const val ENQUEUED_REQUESTS_COUNT = 10
}
@Before
fun setUp() {
for (i in 0 until ENQUEUED_REQUESTS_COUNT) {
registry.add(DownloadRequest(user, file))
}
assertEquals(ENQUEUED_REQUESTS_COUNT, registry.pending.size)
}
@Test
fun starting_transfer() {
// WHEN
// started
registry.startNext()
// THEN
// up to max threads requests are started
// start callback is triggered
// update callback is triggered on transfer transition
// started transfers are in running state
assertEquals(
"Transfers not moved to running queue",
MAX_TRANSFER_THREADS,
registry.running.size
)
assertEquals(
"Transfers not moved from pending queue",
ENQUEUED_REQUESTS_COUNT - MAX_TRANSFER_THREADS,
registry.pending.size
)
verify(exactly = MAX_TRANSFER_THREADS) { onTransferStart(any(), any()) }
val startedTransfers = mutableListOf<Transfer>()
verify(exactly = MAX_TRANSFER_THREADS) { onTransferChanged(capture(startedTransfers)) }
assertEquals(
"Callbacks not invoked for running transfers",
MAX_TRANSFER_THREADS,
startedTransfers.size
)
startedTransfers.forEach {
assertEquals("Transfer not placed into running state", TransferState.RUNNING, it.state)
}
}
@Test
fun start_is_ignored_if_no_more_free_threads() {
// WHEN
// max number of running transfers
registry.startNext()
assertEquals(MAX_TRANSFER_THREADS, registry.running.size)
clearAllMocks()
// WHEN
// starting more transfers
registry.startNext()
// THEN
// no more transfers can be started
assertEquals(MAX_TRANSFER_THREADS, registry.running.size)
verify(exactly = 0) { onTransferStart(any(), any()) }
}
}
class Progress : Base() {
var uuid: UUID = UUID.randomUUID()
@Before
fun setUp() {
val request = DownloadRequest(user, file)
uuid = registry.add(request)
registry.startNext()
assertEquals(uuid, request.uuid)
assertEquals(1, registry.running.size)
resetMocks()
}
@Test
fun transfer_progress_is_updated() {
// GIVEN
// a transfer is running
// WHEN
// transfer progress is updated
val progressHalf = 50
registry.progress(uuid, progressHalf)
// THEN
// progress is updated
// update callback is invoked
val transfer = mutableListOf<Transfer>()
verify { onTransferChanged(capture(transfer)) }
assertEquals(1, transfer.size)
assertEquals(progressHalf, transfer.first().progress)
}
@Test
fun updates_for_non_running_transfers_are_ignored() {
// GIVEN
// transfer is not running
registry.complete(uuid, true)
assertEquals(0, registry.running.size)
resetMocks()
// WHEN
// progress for a non-running transfer is updated
registry.progress(uuid, PROGRESS_HALF)
// THEN
// progress update is ignored
verify(exactly = 0) { onTransferChanged(any()) }
}
@Test
fun updates_for_non_existing_transfers_are_ignored() {
// GIVEN
// some transfer is running
// WHEN
// progress is updated for non-existing transfer
val nonExistingTransferId = UUID.randomUUID()
registry.progress(nonExistingTransferId, PROGRESS_HALF)
// THEN
// progress uppdate is ignored
verify(exactly = 0) { onTransferChanged(any()) }
}
}
class Complete : Base() {
lateinit var uuid: UUID
@Before
fun setUp() {
uuid = registry.add(DownloadRequest(user, file))
registry.startNext()
registry.progress(uuid, PROGRESS_FULL)
resetMocks()
}
@Test
fun complete_successful_transfer_with_updated_file() {
// GIVEN
// a transfer is running
// WHEN
// transfer is completed
// file has been updated
val updatedFile = OCFile("/updated/file")
registry.complete(uuid, true, updatedFile)
// THEN
// transfer is completed successfully
// status carries updated file
val slot = CapturingSlot<Transfer>()
verify { onTransferChanged(capture(slot)) }
assertEquals(TransferState.COMPLETED, slot.captured.state)
assertSame(slot.captured.file, updatedFile)
}
@Test
fun complete_successful_transfer() {
// GIVEN
// a transfer is running
// WHEN
// transfer is completed
// file is not updated
registry.complete(uuid = uuid, success = true, file = null)
// THEN
// transfer is completed successfully
// status carries previous file
val slot = CapturingSlot<Transfer>()
verify { onTransferChanged(capture(slot)) }
assertEquals(TransferState.COMPLETED, slot.captured.state)
assertSame(slot.captured.file, file)
}
@Test
fun complete_failed_transfer() {
// GIVEN
// a transfer is running
// WHEN
// transfer is failed
registry.complete(uuid, false)
// THEN
// transfer is completed successfully
val slot = CapturingSlot<Transfer>()
verify { onTransferChanged(capture(slot)) }
assertEquals(TransferState.FAILED, slot.captured.state)
}
}
class GetTransfers : Base() {
val pendingTransferFile = OCFile("/pending")
val runningTransferFile = OCFile("/running")
val completedTransferFile = OCFile("/completed")
lateinit var pendingTransferId: UUID
lateinit var runningTransferId: UUID
lateinit var completedTransferId: UUID
@Before
fun setUp() {
completedTransferId = registry.add(DownloadRequest(user, completedTransferFile))
registry.startNext()
registry.complete(completedTransferId, true)
runningTransferId = registry.add(DownloadRequest(user, runningTransferFile))
registry.startNext()
pendingTransferId = registry.add(DownloadRequest(user, pendingTransferFile))
resetMocks()
assertEquals(1, registry.pending.size)
assertEquals(1, registry.running.size)
assertEquals(1, registry.completed.size)
}
@Test
fun get_by_path_searches_pending_queue() {
// GIVEN
// file transfer is pending
// WHEN
// transfer status is retrieved
val transfer = registry.getTransfer(pendingTransferFile)
// THEN
// transfer from pending queue is returned
assertNotNull(transfer)
assertEquals(pendingTransferId, transfer?.uuid)
}
@Test
fun get_by_id_searches_pending_queue() {
// GIVEN
// file transfer is pending
// WHEN
// transfer status is retrieved
val transfer = registry.getTransfer(pendingTransferId)
// THEN
// transfer from pending queue is returned
assertNotNull(transfer)
assertEquals(pendingTransferId, transfer?.uuid)
}
@Test
fun get_by_path_searches_running_queue() {
// GIVEN
// file transfer is running
// WHEN
// transfer status is retrieved
val transfer = registry.getTransfer(runningTransferFile)
// THEN
// transfer from pending queue is returned
assertNotNull(transfer)
assertEquals(runningTransferId, transfer?.uuid)
}
@Test
fun get_by_id_searches_running_queue() {
// GIVEN
// file transfer is running
// WHEN
// transfer status is retrieved
val transfer = registry.getTransfer(runningTransferId)
// THEN
// transfer from pending queue is returned
assertNotNull(transfer)
assertEquals(runningTransferId, transfer?.uuid)
}
@Test
fun get_by_path_searches_completed_queue() {
// GIVEN
// file transfer is pending
// WHEN
// transfer status is retrieved
val transfer = registry.getTransfer(completedTransferFile)
// THEN
// transfer from pending queue is returned
assertNotNull(transfer)
assertEquals(completedTransferId, transfer?.uuid)
}
@Test
fun get_by_id_searches_completed_queue() {
// GIVEN
// file transfer is pending
// WHEN
// transfer status is retrieved
val transfer = registry.getTransfer(completedTransferId)
// THEN
// transfer from pending queue is returned
assertNotNull(transfer)
assertEquals(completedTransferId, transfer?.uuid)
}
@Test
fun not_found_by_path() {
// GIVEN
// no transfer for a file
val nonExistingTransferFile = OCFile("/non-nexisting/transfer")
// WHEN
// transfer status is retrieved for a file
val transfer = registry.getTransfer(nonExistingTransferFile)
// THEN
// no transfer is found
assertNull(transfer)
}
@Test
fun not_found_by_id() {
// GIVEN
// no transfer for an id
val nonExistingId = UUID.randomUUID()
// WHEN
// transfer status is retrieved for a file
val transfer = registry.getTransfer(nonExistingId)
// THEN
// no transfer is found
assertNull(transfer)
}
}
class IsRunning : Base() {
@Test
fun no_requests() {
// WHEN
// all queues empty
assertEquals(0, registry.pending.size)
assertEquals(0, registry.running.size)
assertEquals(0, registry.completed.size)
// THEN
// not running
assertFalse(registry.isRunning)
}
@Test
fun request_pending() {
// WHEN
// request is enqueued
val request = DownloadRequest(user, OCFile("/path/alpha/1"))
registry.add(request)
assertEquals(1, registry.pending.size)
assertEquals(0, registry.running.size)
assertEquals(0, registry.completed.size)
// THEN
// is running
assertTrue(registry.isRunning)
}
@Test
fun request_running() {
// WHEN
// request is running
val request = DownloadRequest(user, OCFile("/path/alpha/1"))
registry.add(request)
registry.startNext()
assertEquals(0, registry.pending.size)
assertEquals(1, registry.running.size)
assertEquals(0, registry.completed.size)
// THEN
// is running
assertTrue(registry.isRunning)
}
@Test
fun request_completed() {
// WHEN
// request is running
val request = DownloadRequest(user, OCFile("/path/alpha/1"))
val id = registry.add(request)
registry.startNext()
registry.complete(id, true)
assertEquals(0, registry.pending.size)
assertEquals(0, registry.running.size)
assertEquals(1, registry.completed.size)
// THEN
// is not running
assertFalse(registry.isRunning)
}
}
}

View file

@ -0,0 +1,233 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.files.download
import android.content.ComponentName
import android.content.Context
import com.nextcloud.client.account.MockUser
import com.nextcloud.client.files.DownloadRequest
import com.nextcloud.client.jobs.transfer.FileTransferService
import com.nextcloud.client.jobs.transfer.Transfer
import com.nextcloud.client.jobs.transfer.TransferManager
import com.nextcloud.client.jobs.transfer.TransferManagerConnection
import com.nextcloud.client.jobs.transfer.TransferState
import com.owncloud.android.datamodel.OCFile
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.mockk
import io.mockk.verify
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
class TransferManagerConnectionTest {
lateinit var connection: TransferManagerConnection
@MockK
lateinit var context: Context
@MockK
lateinit var firstDownloadListener: (Transfer) -> Unit
@MockK
lateinit var secondDownloadListener: (Transfer) -> Unit
@MockK
lateinit var firstStatusListener: (TransferManager.Status) -> Unit
@MockK
lateinit var secondStatusListener: (TransferManager.Status) -> Unit
@MockK
lateinit var binder: FileTransferService.Binder
val file get() = OCFile("/path")
val componentName = ComponentName("", FileTransferService::class.java.simpleName)
val user = MockUser()
@Before
fun setUp() {
MockKAnnotations.init(this, relaxed = true)
connection = TransferManagerConnection(context, user)
}
@Test
fun listeners_are_set_after_connection() {
// GIVEN
// not connected
// listener is added
connection.registerTransferListener(firstDownloadListener)
connection.registerTransferListener(secondDownloadListener)
// WHEN
// service is bound
connection.onServiceConnected(componentName, binder)
// THEN
// all listeners are passed to the service
val listeners = mutableListOf<(Transfer) -> Unit>()
verify { binder.registerTransferListener(capture(listeners)) }
assertEquals(listOf(firstDownloadListener, secondDownloadListener), listeners)
}
@Test
fun listeners_are_set_immediately_when_connected() {
// GIVEN
// service is bound
connection.onServiceConnected(componentName, binder)
// WHEN
// listeners are added
connection.registerTransferListener(firstDownloadListener)
// THEN
// listener is forwarded to service
verify { binder.registerTransferListener(firstDownloadListener) }
}
@Test
fun listeners_are_removed_when_unbinding() {
// GIVEN
// service is bound
// service has some listeners
connection.onServiceConnected(componentName, binder)
connection.registerTransferListener(firstDownloadListener)
connection.registerTransferListener(secondDownloadListener)
// WHEN
// service unbound
connection.unbind()
// THEN
// listeners removed from service
verify { binder.removeTransferListener(firstDownloadListener) }
verify { binder.removeTransferListener(secondDownloadListener) }
}
@Test
fun missed_updates_are_delivered_on_connection() {
// GIVEN
// not bound
// has listeners
// download is scheduled and is progressing
connection.registerTransferListener(firstDownloadListener)
connection.registerTransferListener(secondDownloadListener)
val request1 = DownloadRequest(user, file)
connection.enqueue(request1)
val download1 = Transfer(request1.uuid, TransferState.RUNNING, 50, request1.file, request1)
val request2 = DownloadRequest(user, file)
connection.enqueue(request2)
val download2 = Transfer(request2.uuid, TransferState.RUNNING, 50, request2.file, request1)
every { binder.getTransfer(request1.uuid) } returns download1
every { binder.getTransfer(request2.uuid) } returns download2
// WHEN
// service is bound
connection.onServiceConnected(componentName, binder)
// THEN
// listeners receive current download state for pending downloads
val firstListenerNotifications = mutableListOf<Transfer>()
verify { firstDownloadListener(capture(firstListenerNotifications)) }
assertEquals(listOf(download1, download2), firstListenerNotifications)
val secondListenerNotifications = mutableListOf<Transfer>()
verify { secondDownloadListener(capture(secondListenerNotifications)) }
assertEquals(listOf(download1, download2), secondListenerNotifications)
}
@Test
fun downloader_status_updates_are_delivered_on_connection() {
// GIVEN
// not bound
// has status listeners
val mockStatus: TransferManager.Status = mockk()
every { binder.status } returns mockStatus
connection.registerStatusListener(firstStatusListener)
connection.registerStatusListener(secondStatusListener)
// WHEN
// service is bound
connection.onServiceConnected(componentName, binder)
// THEN
// downloader status is delivered
verify { firstStatusListener(mockStatus) }
verify { secondStatusListener(mockStatus) }
}
@Test
fun downloader_status_not_requested_if_no_listeners() {
// GIVEN
// not bound
// no status listeners
// WHEN
// service is bound
connection.onServiceConnected(componentName, binder)
// THEN
// downloader status is not requested
verify(exactly = 0) { binder.status }
}
@Test
fun not_running_if_not_connected() {
// GIVEN
// downloader is running
// connection not bound
every { binder.isRunning } returns true
// THEN
// not running
assertFalse(connection.isRunning)
}
@Test
fun is_running_from_binder_if_connected() {
// GIVEN
// service bound
every { binder.isRunning } returns true
connection.onServiceConnected(componentName, binder)
// WHEN
// is runnign flag accessed
val isRunning = connection.isRunning
// THEN
// call delegated to binder
assertTrue(isRunning)
verify(exactly = 1) { binder.isRunning }
}
@Test
fun missed_updates_not_tracked_before_listeners_registered() {
// GIVEN
// not bound
// some downloads requested without listener
val request = DownloadRequest(user, file)
connection.enqueue(request)
val download = Transfer(request.uuid, TransferState.RUNNING, 50, request.file, request)
connection.registerTransferListener(firstDownloadListener)
every { binder.getTransfer(request.uuid) } returns download
// WHEN
// service is bound
connection.onServiceConnected(componentName, binder)
// THEN
// missed updates not redelivered
verify(exactly = 0) { firstDownloadListener(any()) }
}
}

View file

@ -0,0 +1,279 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.files.download
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.nextcloud.client.account.User
import com.nextcloud.client.core.ManualAsyncRunner
import com.nextcloud.client.core.OnProgressCallback
import com.nextcloud.client.files.DownloadRequest
import com.nextcloud.client.files.Request
import com.nextcloud.client.jobs.download.DownloadTask
import com.nextcloud.client.jobs.transfer.Transfer
import com.nextcloud.client.jobs.transfer.TransferManagerImpl
import com.nextcloud.client.jobs.transfer.TransferState
import com.nextcloud.client.jobs.upload.UploadTask
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.OwnCloudClient
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Suite
import org.mockito.MockitoAnnotations
@RunWith(Suite::class)
@Suite.SuiteClasses(
TransferManagerTest.Enqueue::class,
TransferManagerTest.TransferStatusUpdates::class
)
class TransferManagerTest {
abstract class Base {
companion object {
const val MAX_TRANSFER_THREADS = 4
}
@MockK
lateinit var user: User
@MockK
lateinit var client: OwnCloudClient
@MockK
lateinit var mockDownloadTaskFactory: DownloadTask.Factory
@MockK
lateinit var mockUploadTaskFactory: UploadTask.Factory
/**
* All task mock functions created during test run are
* stored here.
*/
lateinit var downloadTaskMocks: MutableList<DownloadTask>
lateinit var runner: ManualAsyncRunner
lateinit var transferManager: TransferManagerImpl
/**
* Response value for all download tasks
*/
var downloadTaskResult: Boolean = true
/**
* Progress values posted by all download task mocks before
* returning result value
*/
var taskProgress = listOf<Int>()
@Before
fun setUpBase() {
MockKAnnotations.init(this, relaxed = true)
MockitoAnnotations.initMocks(this)
downloadTaskMocks = mutableListOf()
runner = ManualAsyncRunner()
transferManager = TransferManagerImpl(
runner = runner,
downloadTaskFactory = mockDownloadTaskFactory,
uploadTaskFactory = mockUploadTaskFactory,
threads = MAX_TRANSFER_THREADS
)
downloadTaskResult = true
every { mockDownloadTaskFactory.create() } answers { createMockTask() }
}
private fun createMockTask(): DownloadTask {
val task = mockk<DownloadTask>()
every { task.download(any(), any(), any()) } answers {
taskProgress.forEach {
arg<OnProgressCallback<Int>>(1).invoke(it)
}
val request = arg<Request>(0)
DownloadTask.Result(request.file, downloadTaskResult)
}
downloadTaskMocks.add(task)
return task
}
}
class Enqueue : Base() {
@Test
fun enqueued_download_is_started_immediately() {
// GIVEN
// downloader has no running downloads
// WHEN
// download is enqueued
val file = OCFile("/path")
val request = DownloadRequest(user, file)
transferManager.enqueue(request)
// THEN
// download is started immediately
val download = transferManager.getTransfer(request.uuid)
assertEquals(TransferState.RUNNING, download?.state)
}
@Test
fun enqueued_downloads_are_pending_if_running_queue_is_full() {
// GIVEN
// downloader is downloading max simultaneous files
for (i in 0 until MAX_TRANSFER_THREADS) {
val file = OCFile("/running/download/path/$i")
val request = DownloadRequest(user, file)
transferManager.enqueue(request)
val runningDownload = transferManager.getTransfer(request.uuid)
assertEquals(runningDownload?.state, TransferState.RUNNING)
}
// WHEN
// another download is enqueued
val file = OCFile("/path")
val request = DownloadRequest(user, file)
transferManager.enqueue(request)
// THEN
// download is pending
val download = transferManager.getTransfer(request.uuid)
assertEquals(TransferState.PENDING, download?.state)
}
}
class TransferStatusUpdates : Base() {
@get:Rule
val rule = InstantTaskExecutorRule()
val file = OCFile("/path")
@Test
fun download_task_completes() {
// GIVEN
// download is running
// download is being observed
val downloadUpdates = mutableListOf<Transfer>()
transferManager.registerTransferListener { downloadUpdates.add(it) }
transferManager.enqueue(DownloadRequest(user, file))
// WHEN
// download task finishes successfully
runner.runOne()
// THEN
// listener is notified about status change
assertEquals(TransferState.RUNNING, downloadUpdates[0].state)
assertEquals(TransferState.COMPLETED, downloadUpdates[1].state)
}
@Test
fun download_task_fails() {
// GIVEN
// download is running
// download is being observed
val downloadUpdates = mutableListOf<Transfer>()
transferManager.registerTransferListener { downloadUpdates.add(it) }
transferManager.enqueue(DownloadRequest(user, file))
// WHEN
// download task fails
downloadTaskResult = false
runner.runOne()
// THEN
// listener is notified about status change
assertEquals(TransferState.RUNNING, downloadUpdates[0].state)
assertEquals(TransferState.FAILED, downloadUpdates[1].state)
}
@Test
fun download_progress_is_updated() {
// GIVEN
// download is running
val downloadUpdates = mutableListOf<Transfer>()
transferManager.registerTransferListener { downloadUpdates.add(it) }
transferManager.enqueue(DownloadRequest(user, file))
// WHEN
// download progress updated 4 times before completion
taskProgress = listOf(25, 50, 75, 100)
runner.runOne()
// THEN
// listener receives 6 status updates
// transition to running
// 4 progress updates
// completion
assertEquals(6, downloadUpdates.size)
if (downloadUpdates.size >= 6) {
assertEquals(TransferState.RUNNING, downloadUpdates[0].state)
assertEquals(25, downloadUpdates[1].progress)
assertEquals(50, downloadUpdates[2].progress)
assertEquals(75, downloadUpdates[3].progress)
assertEquals(100, downloadUpdates[4].progress)
assertEquals(TransferState.COMPLETED, downloadUpdates[5].state)
}
}
@Test
fun download_task_is_created_only_for_running_downloads() {
// WHEN
// multiple downloads are enqueued
for (i in 0 until MAX_TRANSFER_THREADS * 2) {
transferManager.enqueue(DownloadRequest(user, file))
}
// THEN
// download task is created only for running downloads
assertEquals(MAX_TRANSFER_THREADS, downloadTaskMocks.size)
}
}
class RunningStatusUpdates : Base() {
@get:Rule
val rule = InstantTaskExecutorRule()
@Test
fun is_running_flag_on_enqueue() {
// WHEN
// download is enqueued
val file = OCFile("/path/to/file")
val request = DownloadRequest(user, file)
transferManager.enqueue(request)
// THEN
// is running changes
assertTrue(transferManager.isRunning)
}
@Test
fun is_running_flag_on_completion() {
// GIVEN
// a download is in progress
val file = OCFile("/path/to/file")
val request = DownloadRequest(user, file)
transferManager.enqueue(request)
assertTrue(transferManager.isRunning)
// WHEN
// download is processed
runner.runOne()
// THEN
// downloader is not running
assertFalse(transferManager.isRunning)
}
}
}

View file

@ -0,0 +1,166 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.integrations.deck
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import androidx.test.platform.app.InstrumentationRegistry
import com.nextcloud.client.account.User
import com.owncloud.android.lib.resources.notifications.models.Notification
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Suite
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@RunWith(Suite::class)
@Suite.SuiteClasses(
DeckApiTest.DeckIsInstalled::class,
DeckApiTest.DeckIsNotInstalled::class
)
class DeckApiTest {
abstract class Fixture {
@Mock
lateinit var packageManager: PackageManager
lateinit var context: Context
@Mock
lateinit var user: User
lateinit var deck: DeckApiImpl
@Before
fun setUpFixture() {
MockitoAnnotations.initMocks(this)
context = InstrumentationRegistry.getInstrumentation().targetContext
deck = DeckApiImpl(context, packageManager)
}
}
@RunWith(Parameterized::class)
class DeckIsInstalled : Fixture() {
@Parameterized.Parameter(0)
lateinit var installedDeckPackage: String
companion object {
@Parameterized.Parameters
@JvmStatic
fun initParametrs(): Array<String> {
return DeckApiImpl.DECK_APP_PACKAGES
}
}
@Before
fun setUp() {
whenever(packageManager.resolveActivity(any(), anyInt())).thenAnswer {
val intent = it.getArgument<Intent>(0)
return@thenAnswer if (intent.component?.packageName == installedDeckPackage) {
ResolveInfo()
} else {
null
}
}
}
@Test
fun can_forward_deck_notification() {
// GIVEN
// notification to deck arrives
val notification = Notification().apply { app = "deck" }
// WHEN
// deck action is created
val forwardActionIntent = deck.createForwardToDeckActionIntent(notification, user)
// THEN
// open action is created
assertTrue("Failed for $installedDeckPackage", forwardActionIntent.isPresent)
}
@Test
fun notifications_from_other_apps_are_ignored() {
// GIVEN
// notification from other app arrives
val deckNotification = Notification().apply {
app = "some_other_app"
}
// WHEN
// deck action is created
val openDeckActionIntent = deck.createForwardToDeckActionIntent(deckNotification, user)
// THEN
// deck application is not being resolved
// open action is not created
verify(packageManager, never()).resolveActivity(anyOrNull(), anyOrNull<Int>())
assertFalse(openDeckActionIntent.isPresent)
}
}
class DeckIsNotInstalled : Fixture() {
@Before
fun setUp() {
whenever(packageManager.resolveActivity(any(), anyInt())).thenReturn(null)
}
@Test
fun cannot_forward_deck_notification() {
// GIVEN
// notification is coming from deck app
val notification = Notification().apply {
app = DeckApiImpl.APP_NAME
}
// WHEN
// creating open in deck action
val openDeckActionIntent = deck.createForwardToDeckActionIntent(notification, user)
// THEN
// deck application is being resolved using all known packages
// open action is not created
verify(packageManager, times(DeckApiImpl.DECK_APP_PACKAGES.size))
.resolveActivity(anyOrNull(), anyOrNull<Int>())
assertFalse(openDeckActionIntent.isPresent)
}
@Test
fun notifications_from_other_apps_are_ignored() {
// GIVEN
// notification is coming from other app
val notification = Notification().apply {
app = "some_other_app"
}
// WHEN
// creating open in deck action
val openDeckActionIntent = deck.createForwardToDeckActionIntent(notification, user)
// THEN
// deck application is not being resolved
// open action is not created
verify(packageManager, never()).resolveActivity(anyOrNull(), anyOrNull<Int>())
assertFalse(openDeckActionIntent.isPresent)
}
}
}

View file

@ -0,0 +1,418 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.jobs
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.test.annotation.UiThreadTest
import androidx.work.Data
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.nextcloud.client.account.User
import com.nextcloud.client.core.Clock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Suite
import org.mockito.ArgumentMatcher
import org.mockito.kotlin.KArgumentCaptor
import org.mockito.kotlin.any
import org.mockito.kotlin.argThat
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import java.util.Date
import java.util.UUID
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
/**
* When using IDE to run enire Suite, make sure tests are run using Android Instrumentation Test
* runner. By default IDE runs normal JUnit - this is AS problem. One must configure the
* test run manually.
*/
@RunWith(Suite::class)
@Suite.SuiteClasses(
BackgroundJobManagerTest.Manager::class,
BackgroundJobManagerTest.ContentObserver::class,
BackgroundJobManagerTest.PeriodicContactsBackup::class,
BackgroundJobManagerTest.ImmediateContactsBackup::class,
BackgroundJobManagerTest.ImmediateContactsImport::class,
BackgroundJobManagerTest.Tags::class
)
class BackgroundJobManagerTest {
/**
* Used to help with ambiguous type inference
*/
class IsOneTimeWorkRequest : ArgumentMatcher<OneTimeWorkRequest> {
override fun matches(argument: OneTimeWorkRequest?): Boolean = true
}
/**
* Used to help with ambiguous type inference
*/
class IsPeriodicWorkRequest : ArgumentMatcher<PeriodicWorkRequest> {
override fun matches(argument: PeriodicWorkRequest?): Boolean = true
}
abstract class Fixture {
companion object {
internal const val USER_ACCOUNT_NAME = "user@nextcloud"
internal val TIMESTAMP = System.currentTimeMillis()
}
internal lateinit var user: User
internal lateinit var workManager: WorkManager
internal lateinit var clock: Clock
internal lateinit var backgroundJobManager: BackgroundJobManagerImpl
@Before
fun setUpFixture() {
user = mock()
whenever(user.accountName).thenReturn(USER_ACCOUNT_NAME)
workManager = mock()
clock = mock()
whenever(clock.currentTime).thenReturn(TIMESTAMP)
whenever(clock.currentDate).thenReturn(Date(TIMESTAMP))
backgroundJobManager = BackgroundJobManagerImpl(workManager, clock, mock())
}
fun assertHasRequiredTags(tags: Set<String>, jobName: String, user: User? = null) {
assertTrue("""'all' tag is mandatory""", tags.contains("*"))
assertTrue("name tag is mandatory", tags.contains(BackgroundJobManagerImpl.formatNameTag(jobName, user)))
assertTrue("timestamp tag is mandatory", tags.contains(BackgroundJobManagerImpl.formatTimeTag(TIMESTAMP)))
if (user != null) {
assertTrue("user tag is mandatory", tags.contains(BackgroundJobManagerImpl.formatUserTag(user)))
}
}
fun buildWorkInfo(index: Long): WorkInfo = WorkInfo(
id = UUID.randomUUID(),
state = WorkInfo.State.RUNNING,
outputData = Data.Builder().build(),
tags = setOf(BackgroundJobManagerImpl.formatTimeTag(1581820284000)),
progress = Data.Builder().build(),
runAttemptCount = 1,
generation = 0
)
}
class Manager : Fixture() {
class SyncObserver<T> : Observer<T> {
val latch = CountDownLatch(1)
var value: T? = null
override fun onChanged(t: T) {
value = t
latch.countDown()
}
fun getValue(timeout: Long = 3, timeUnit: TimeUnit = TimeUnit.SECONDS): T? {
val result = latch.await(timeout, timeUnit)
if (!result) {
throw TimeoutException()
}
return value
}
}
@Test
@UiThreadTest
fun get_all_job_info() {
// GIVEN
// work manager has 2 registered workers
val platformWorkInfo = listOf(
buildWorkInfo(0),
buildWorkInfo(1),
buildWorkInfo(2)
)
val lv = MutableLiveData<List<WorkInfo>>()
lv.value = platformWorkInfo
whenever(workManager.getWorkInfosByTagLiveData(eq("*"))).thenReturn(lv)
// WHEN
// job info for all jobs is requested
val jobs = backgroundJobManager.jobs
// THEN
// live data with job info is returned
// live data contains 2 job info instances
// job info is sorted by timestamp from newest to oldest
assertNotNull(jobs)
val observer = SyncObserver<List<JobInfo>>()
jobs.observeForever(observer)
val jobInfo = observer.getValue()
assertNotNull(jobInfo)
assertEquals(platformWorkInfo.size, jobInfo?.size)
jobInfo?.let {
assertEquals(platformWorkInfo[2].id, it[0].id)
assertEquals(platformWorkInfo[1].id, it[1].id)
assertEquals(platformWorkInfo[0].id, it[2].id)
}
}
@Test
fun cancel_all_jobs() {
// WHEN
// all jobs are cancelled
backgroundJobManager.cancelAllJobs()
// THEN
// all jobs with * tag are cancelled
verify(workManager).cancelAllWorkByTag(BackgroundJobManagerImpl.TAG_ALL)
}
}
class ContentObserver : Fixture() {
private lateinit var request: OneTimeWorkRequest
@Before
fun setUp() {
val requestCaptor: KArgumentCaptor<OneTimeWorkRequest> = argumentCaptor()
backgroundJobManager.scheduleContentObserverJob()
verify(workManager).enqueueUniqueWork(
any(),
any(),
requestCaptor.capture()
)
assertEquals(1, requestCaptor.allValues.size)
request = requestCaptor.firstValue
}
@Test
fun job_is_unique_and_replaces_previous_job() {
verify(workManager).enqueueUniqueWork(
eq(BackgroundJobManagerImpl.JOB_CONTENT_OBSERVER),
eq(ExistingWorkPolicy.REPLACE),
argThat(IsOneTimeWorkRequest())
)
}
@Test
fun job_request_has_mandatory_tags() {
assertHasRequiredTags(request.tags, BackgroundJobManagerImpl.JOB_CONTENT_OBSERVER)
}
}
class PeriodicContactsBackup : Fixture() {
private lateinit var request: PeriodicWorkRequest
@Before
fun setUp() {
val requestCaptor: KArgumentCaptor<PeriodicWorkRequest> = argumentCaptor()
backgroundJobManager.schedulePeriodicContactsBackup(user)
verify(workManager).enqueueUniquePeriodicWork(
any(),
any(),
requestCaptor.capture()
)
assertEquals(1, requestCaptor.allValues.size)
request = requestCaptor.firstValue
}
@Test
fun job_is_unique_for_user() {
verify(workManager).enqueueUniquePeriodicWork(
eq(BackgroundJobManagerImpl.JOB_PERIODIC_CONTACTS_BACKUP),
eq(ExistingPeriodicWorkPolicy.KEEP),
argThat(IsPeriodicWorkRequest())
)
}
@Test
fun job_request_has_mandatory_tags() {
assertHasRequiredTags(request.tags, BackgroundJobManagerImpl.JOB_PERIODIC_CONTACTS_BACKUP, user)
}
}
class ImmediateContactsBackup : Fixture() {
private lateinit var workInfo: MutableLiveData<WorkInfo>
private lateinit var jobInfo: LiveData<JobInfo?>
private lateinit var request: OneTimeWorkRequest
@Before
fun setUp() {
val requestCaptor: KArgumentCaptor<OneTimeWorkRequest> = argumentCaptor()
workInfo = MutableLiveData()
whenever(workManager.getWorkInfoByIdLiveData(any())).thenReturn(workInfo)
jobInfo = backgroundJobManager.startImmediateContactsBackup(user)
verify(workManager).enqueueUniqueWork(
any(),
any(),
requestCaptor.capture()
)
assertEquals(1, requestCaptor.allValues.size)
request = requestCaptor.firstValue
}
@Test
fun job_is_unique_for_user() {
verify(workManager).enqueueUniqueWork(
eq(BackgroundJobManagerImpl.JOB_IMMEDIATE_CONTACTS_BACKUP),
eq(ExistingWorkPolicy.KEEP),
argThat(IsOneTimeWorkRequest())
)
}
@Test
fun job_request_has_mandatory_tags() {
assertHasRequiredTags(request.tags, BackgroundJobManagerImpl.JOB_IMMEDIATE_CONTACTS_BACKUP, user)
}
@Test
@UiThreadTest
fun job_info_is_obtained_from_work_info() {
// GIVEN
// work info is available
workInfo.value = buildWorkInfo(0)
// WHEN
// job info has listener
jobInfo.observeForever {}
// THEN
// converted value is available
assertNotNull(jobInfo.value)
assertEquals(workInfo.value?.id, jobInfo.value?.id)
}
}
class ImmediateContactsImport : Fixture() {
private lateinit var workInfo: MutableLiveData<WorkInfo>
private lateinit var jobInfo: LiveData<JobInfo?>
private lateinit var request: OneTimeWorkRequest
@Before
fun setUp() {
val requestCaptor: KArgumentCaptor<OneTimeWorkRequest> = argumentCaptor()
workInfo = MutableLiveData()
whenever(workManager.getWorkInfoByIdLiveData(any())).thenReturn(workInfo)
jobInfo = backgroundJobManager.startImmediateContactsImport(
contactsAccountName = "name",
contactsAccountType = "type",
vCardFilePath = "/path/to/vcard/file",
selectedContacts = intArrayOf(1, 2, 3)
)
verify(workManager).enqueueUniqueWork(
any(),
any(),
requestCaptor.capture()
)
assertEquals(1, requestCaptor.allValues.size)
request = requestCaptor.firstValue
}
@Test
fun job_is_unique() {
verify(workManager).enqueueUniqueWork(
eq(BackgroundJobManagerImpl.JOB_IMMEDIATE_CONTACTS_IMPORT),
eq(ExistingWorkPolicy.KEEP),
argThat(IsOneTimeWorkRequest())
)
}
@Test
fun job_request_has_mandatory_tags() {
assertHasRequiredTags(request.tags, BackgroundJobManagerImpl.JOB_IMMEDIATE_CONTACTS_IMPORT)
}
@Test
@UiThreadTest
fun job_info_is_obtained_from_work_info() {
// GIVEN
// work info is available
workInfo.value = buildWorkInfo(0)
// WHEN
// job info has listener
jobInfo.observeForever {}
// THEN
// converted value is available
assertNotNull(jobInfo.value)
assertEquals(workInfo.value?.id, jobInfo.value?.id)
}
}
class Tags {
@Test
fun split_tag_key_and_value() {
// GIVEN
// valid tag
// tag has colons in value part
val tag = "${BackgroundJobManagerImpl.TAG_PREFIX_NAME}:value:with:colons and spaces"
// WHEN
// tag is parsed
val parsedTag = BackgroundJobManagerImpl.parseTag(tag)
// THEN
// key-value pair is returned
// key is first
// value with colons is second
assertNotNull(parsedTag)
assertEquals(BackgroundJobManagerImpl.TAG_PREFIX_NAME, parsedTag?.first)
assertEquals("value:with:colons and spaces", parsedTag?.second)
}
@Test
fun tags_with_invalid_prefixes_are_rejected() {
// GIVEN
// tag prefix is not on allowed prefixes list
val tag = "invalidprefix:value"
BackgroundJobManagerImpl.PREFIXES.forEach {
assertFalse(tag.startsWith(it))
}
// WHEN
// tag is parsed
val parsedTag = BackgroundJobManagerImpl.parseTag(tag)
// THEN
// tag is rejected
assertNull(parsedTag)
}
@Test
fun strings_without_colon_are_rejected() {
// GIVEN
// strings that are not tags
val tags = listOf(
BackgroundJobManagerImpl.TAG_ALL,
BackgroundJobManagerImpl.TAG_PREFIX_NAME,
"simplestring",
""
)
tags.forEach {
// WHEN
// string is parsed
val parsedTag = BackgroundJobManagerImpl.parseTag(it)
// THEN
// tag is rejected
assertNull(parsedTag)
}
}
}
}

View file

@ -0,0 +1,92 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.jobs
import android.Manifest
import androidx.test.rule.GrantPermissionRule
import androidx.work.WorkManager
import com.nextcloud.client.core.ClockImpl
import com.nextcloud.client.preferences.AppPreferencesImpl
import com.nextcloud.test.RetryTestRule
import com.owncloud.android.AbstractIT
import com.owncloud.android.AbstractOnServerIT
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.operations.DownloadFileOperation
import ezvcard.Ezvcard
import ezvcard.VCard
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import java.io.BufferedInputStream
import java.io.File
import java.io.FileInputStream
class ContactsBackupIT : AbstractOnServerIT() {
val workmanager = WorkManager.getInstance(targetContext)
val preferences = AppPreferencesImpl.fromContext(targetContext)
private val backgroundJobManager = BackgroundJobManagerImpl(workmanager, ClockImpl(), preferences)
@get:Rule
val writeContactsRule = GrantPermissionRule.grant(Manifest.permission.WRITE_CONTACTS)
@get:Rule
val readContactsRule = GrantPermissionRule.grant(Manifest.permission.READ_CONTACTS)
@get:Rule
val retryTestRule = RetryTestRule() // flaky test
private val vcard: String = "vcard.vcf"
@Test
fun importExport() {
val intArray = IntArray(1)
intArray[0] = 0
// import file to local contacts
backgroundJobManager.startImmediateContactsImport(null, null, getFile(vcard).absolutePath, intArray)
shortSleep()
// export contact
backgroundJobManager.startImmediateContactsBackup(user)
longSleep()
val backupFolder: String = targetContext.resources.getString(R.string.contacts_backup_folder) +
OCFile.PATH_SEPARATOR
refreshFolder("/")
longSleep()
refreshFolder(backupFolder)
longSleep()
val backupOCFile = storageManager.getFolderContent(
storageManager.getFileByDecryptedRemotePath(backupFolder),
false
)[0]
assertTrue(DownloadFileOperation(user, backupOCFile, AbstractIT.targetContext).execute(client).isSuccess)
val backupFile = File(backupOCFile.storagePath)
val vcardInputStream = BufferedInputStream(FileInputStream(getFile(vcard)))
val backupFileInputStream = BufferedInputStream(FileInputStream(backupFile))
// verify same
val originalCards: ArrayList<VCard> = ArrayList()
originalCards.addAll(Ezvcard.parse(vcardInputStream).all())
val backupCards: ArrayList<VCard> = ArrayList()
backupCards.addAll(Ezvcard.parse(backupFileInputStream).all())
assertEquals(originalCards.size, backupCards.size)
assertEquals(originalCards[0].formattedName.toString(), backupCards[0].formattedName.toString())
}
}

View file

@ -0,0 +1,116 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.migrations
import android.content.Context
import android.content.SharedPreferences
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
class MigrationsDbTest {
private lateinit var context: Context
private lateinit var store: MockSharedPreferences
private lateinit var db: MigrationsDb
@Before
fun setUp() {
context = InstrumentationRegistry.getInstrumentation().context
store = MockSharedPreferences()
assertTrue("State from previous test run found?", store.all.isEmpty())
db = MigrationsDb(store)
}
@Test
fun applied_migrations_are_returned_in_order() {
// GIVEN
// some migrations are marked as applied
// migration ids are stored in random order
val mockStore: SharedPreferences = mock()
val storedMigrationIds = LinkedHashSet<String>()
storedMigrationIds.apply {
add("3")
add("0")
add("2")
add("1")
}
whenever(mockStore.getStringSet(eq(MigrationsDb.DB_KEY_APPLIED_MIGRATIONS), any()))
.thenReturn(storedMigrationIds)
// WHEN
// applied migration ids are retrieved
val db = MigrationsDb(mockStore)
val ids = db.getAppliedMigrations()
// THEN
// returned list is sorted
assertEquals(ids, ids.sorted())
}
@Test
@Suppress("MagicNumber")
fun registering_new_applied_migration_preserves_old_ids() {
// WHEN
// some applied migrations are registered
val appliedMigrationIds = setOf("0", "1", "2")
store.edit().putStringSet(MigrationsDb.DB_KEY_APPLIED_MIGRATIONS, appliedMigrationIds).apply()
// WHEN
// new set of migration ids are registered
// some ids are added again
db.addAppliedMigration(2, 3, 4)
// THEN
// new ids are appended to set of existing ids
val expectedIds = setOf("0", "1", "2", "3", "4")
val storedIds = store.getStringSet(MigrationsDb.DB_KEY_APPLIED_MIGRATIONS, mutableSetOf())
assertEquals(expectedIds, storedIds)
}
@Test
fun failed_status_sets_status_flag_and_error_message() {
// GIVEN
// failure flag is not set
assertFalse(db.isFailed)
// WHEN
// failure status is set
val failureReason = "error message"
db.setFailed(0, failureReason)
// THEN
// failed flag is set
// error message is set
assertTrue(db.isFailed)
assertEquals(failureReason, db.failureReason)
}
@Test
fun last_migrated_version_is_set() {
// GIVEN
// last migrated version is not set
val oldVersion = db.lastMigratedVersion
assertEquals(MigrationsDb.NO_LAST_MIGRATED_VERSION, oldVersion)
// WHEN
// migrated version is set to a new value
val newVersion = 200
db.lastMigratedVersion = newVersion
// THEN
// new value is stored
assertEquals(newVersion, db.lastMigratedVersion)
}
}

View file

@ -0,0 +1,276 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.migrations
import androidx.test.annotation.UiThreadTest
import com.nextcloud.client.appinfo.AppInfo
import com.nextcloud.client.core.ManualAsyncRunner
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.inOrder
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
class MigrationsManagerTest {
companion object {
const val OLD_APP_VERSION = 41
const val NEW_APP_VERSION = 42
}
lateinit var migrationStep1Body: (Migrations.Step) -> Unit
lateinit var migrationStep1: Migrations.Step
lateinit var migrationStep2Body: (Migrations.Step) -> Unit
lateinit var migrationStep2: Migrations.Step
lateinit var migrationStep3Body: (Migrations.Step) -> Unit
lateinit var migrationStep3: Migrations.Step
lateinit var migrations: List<Migrations.Step>
@Mock
lateinit var appInfo: AppInfo
lateinit var migrationsDbStore: MockSharedPreferences
lateinit var migrationsDb: MigrationsDb
lateinit var asyncRunner: ManualAsyncRunner
internal lateinit var migrationsManager: MigrationsManagerImpl
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
migrationStep1Body = mock()
migrationStep1 = Migrations.Step(0, "first migration", true, migrationStep1Body)
migrationStep2Body = mock()
migrationStep2 = Migrations.Step(1, "second optional migration", false, migrationStep2Body)
migrationStep3Body = mock()
migrationStep3 = Migrations.Step(2, "third migration", true, migrationStep3Body)
migrations = listOf(migrationStep1, migrationStep2, migrationStep3)
asyncRunner = ManualAsyncRunner()
migrationsDbStore = MockSharedPreferences()
migrationsDb = MigrationsDb(migrationsDbStore)
whenever(appInfo.versionCode).thenReturn(NEW_APP_VERSION)
migrationsManager = MigrationsManagerImpl(
appInfo = appInfo,
migrationsDb = migrationsDb,
asyncRunner = asyncRunner,
migrations = migrations
)
}
@Test
fun inital_status_is_unknown() {
// GIVEN
// migration manager has not been used yets
// THEN
// status is not set
assertEquals(MigrationsManager.Status.UNKNOWN, migrationsManager.status.value)
}
@Test
@UiThreadTest
fun migrations_are_scheduled_on_background_thread() {
// GIVEN
// migrations can be applied
assertEquals(0, migrationsDb.getAppliedMigrations().size)
// WHEN
// migration is started
val count = migrationsManager.startMigration()
// THEN
// all migrations are scheduled on background thread
// single task is scheduled
assertEquals(migrations.size, count)
assertEquals(1, asyncRunner.size)
assertEquals(MigrationsManager.Status.RUNNING, migrationsManager.status.value)
}
@Test
@UiThreadTest
fun applied_migrations_are_recorded() {
// GIVEN
// no migrations are applied yet
// current app version is newer then last recorded migrated version
whenever(appInfo.versionCode).thenReturn(NEW_APP_VERSION)
migrationsDb.lastMigratedVersion = OLD_APP_VERSION
// WHEN
// migration is run
val count = migrationsManager.startMigration()
assertTrue(asyncRunner.runOne())
// THEN
// total migrations count is returned
// migration functions are called with step as argument
// migrations are invoked in order
// applied migrations are recorded
// new app version code is recorded
assertEquals(migrations.size, count)
inOrder(migrationStep1.run, migrationStep2.run, migrationStep3.run).apply {
verify(migrationStep1.run).invoke(migrationStep1)
verify(migrationStep2.run).invoke(migrationStep2)
verify(migrationStep3.run).invoke(migrationStep3)
}
val allAppliedIds = migrations.map { it.id }
assertEquals(allAppliedIds, migrationsDb.getAppliedMigrations())
assertEquals(NEW_APP_VERSION, migrationsDb.lastMigratedVersion)
}
@Test
@UiThreadTest
fun previously_run_migrations_are_not_run_again() {
// GIVEN
// some migrations were run before
whenever(appInfo.versionCode).thenReturn(NEW_APP_VERSION)
migrationsDb.lastMigratedVersion = OLD_APP_VERSION
migrationsDb.addAppliedMigration(migrationStep1.id, migrationStep2.id)
// WHEN
// migrations are applied
val count = migrationsManager.startMigration()
assertTrue(asyncRunner.runOne())
// THEN
// applied migrations count is returned
// previously applied migrations are not run
// required migrations are applied
// applied migrations are recorded
// new app version code is recorded
assertEquals(1, count)
verify(migrationStep1.run, never()).invoke(anyOrNull())
verify(migrationStep2.run, never()).invoke(anyOrNull())
verify(migrationStep3.run).invoke(migrationStep3)
val allAppliedIds = migrations.map { it.id }
assertEquals(allAppliedIds, migrationsDb.getAppliedMigrations())
assertEquals(NEW_APP_VERSION, migrationsDb.lastMigratedVersion)
}
@Test
@UiThreadTest
fun migration_error_is_recorded() {
// GIVEN
// no migrations applied yet
// no prior failed migrations
assertFalse(migrationsDb.isFailed)
assertEquals(MigrationsDb.NO_FAILED_MIGRATION_ID, migrationsDb.failedMigrationId)
// WHEN
// migrations are applied
// one migration throws
val lastMigration = migrations.findLast { it.mandatory } ?: throw IllegalStateException("Test fixture error")
val errorMessage = "error message"
whenever(lastMigration.run.invoke(any())).thenThrow(RuntimeException(errorMessage))
migrationsManager.startMigration()
assertTrue(asyncRunner.runOne())
// THEN
// failure is marked in the migration db
// failure message is recorded
// failed migration id is recorded
assertEquals(MigrationsManager.Status.FAILED, migrationsManager.status.value)
assertTrue(migrationsDb.isFailed)
assertEquals(errorMessage, migrationsDb.failureReason)
assertEquals(lastMigration.id, migrationsDb.failedMigrationId)
}
@Test
@UiThreadTest
fun migrations_are_not_run_if_already_run_for_an_app_version() {
// GIVEN
// migrations were already run for the current app version
whenever(appInfo.versionCode).thenReturn(NEW_APP_VERSION)
migrationsDb.lastMigratedVersion = NEW_APP_VERSION
// WHEN
// app is migrated again
val migrationCount = migrationsManager.startMigration()
// THEN
// migration processing is skipped entirely
// status is set to applied
assertEquals(0, migrationCount)
listOf(migrationStep1, migrationStep2, migrationStep3).forEach {
verify(it.run, never()).invoke(any())
}
assertEquals(MigrationsManager.Status.APPLIED, migrationsManager.status.value)
}
@Test
@UiThreadTest
fun new_app_version_is_marked_as_migrated_if_no_new_migrations_are_available() {
// GIVEN
// migrations were applied in previous version
// new version has no new migrations
whenever(appInfo.versionCode).thenReturn(NEW_APP_VERSION)
migrationsDb.lastMigratedVersion = OLD_APP_VERSION
migrations.forEach {
migrationsDb.addAppliedMigration(it.id)
}
// WHEN
// migration is started
val startedCount = migrationsManager.startMigration()
// THEN
// no new migrations are run
// new version is marked as migrated
assertEquals(0, startedCount)
assertEquals(
NEW_APP_VERSION,
migrationsDb.lastMigratedVersion
)
}
@Test
@UiThreadTest
fun optional_migration_failure_does_not_trigger_a_migration_failure() {
// GIVEN
// pending migrations
// mandatory migrations are passing
// one migration is optional and fails
assertEquals("Fixture should provide 1 optional, failing migration", 1, migrations.count { !it.mandatory })
val optionalFailingMigration = migrations.first { !it.mandatory }
whenever(optionalFailingMigration.run.invoke(any())).thenThrow(RuntimeException())
// WHEN
// migration is started
val startedCount = migrationsManager.startMigration()
asyncRunner.runOne()
assertEquals(migrations.size, startedCount)
// THEN
// mandatory migrations are marked as applied
// optional failed migration is not marked
// no error
// status is applied
// failed migration is available during next migration
val appliedMigrations = migrations.filter { it.mandatory }.map { it.id }
assertTrue("Fixture error", appliedMigrations.isNotEmpty())
assertEquals(appliedMigrations, migrationsDb.getAppliedMigrations())
assertFalse(migrationsDb.isFailed)
assertEquals(MigrationsManager.Status.APPLIED, migrationsManager.status.value)
}
}

View file

@ -0,0 +1,97 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.migrations
import android.content.SharedPreferences
import java.util.TreeMap
/**
* This shared preferences implementation uses in-memory value store
* and it can be used in tests without using global, file-backed storage,
* improving test isolation.
*
* The implementation is not thread-safe.
*/
@Suppress("TooManyFunctions")
class MockSharedPreferences : SharedPreferences {
class MockEditor(val store: MutableMap<String?, Any?>) : SharedPreferences.Editor {
val editorStore: MutableMap<String?, Any?> = TreeMap()
override fun clear(): SharedPreferences.Editor = throw UnsupportedOperationException()
override fun putLong(key: String?, value: Long): SharedPreferences.Editor =
throw UnsupportedOperationException("Implement as needed")
override fun putInt(key: String?, value: Int): SharedPreferences.Editor {
editorStore.put(key, value)
return this
}
override fun remove(key: String?): SharedPreferences.Editor = throw UnsupportedOperationException()
override fun putBoolean(key: String?, value: Boolean): SharedPreferences.Editor {
editorStore.put(key, value)
return this
}
override fun putStringSet(key: String?, values: MutableSet<String>?): SharedPreferences.Editor {
editorStore.put(key, values?.toMutableSet())
return this
}
override fun commit(): Boolean = true
override fun putFloat(key: String?, value: Float): SharedPreferences.Editor =
throw UnsupportedOperationException("Implement as needed")
override fun apply() = store.putAll(editorStore)
override fun putString(key: String?, value: String?): SharedPreferences.Editor {
editorStore.put(key, value)
return this
}
}
val store: MutableMap<String?, Any?> = TreeMap()
override fun contains(key: String?): Boolean = store.containsKey(key)
override fun getBoolean(key: String?, defValue: Boolean): Boolean = store.getOrDefault(key, defValue) as Boolean
override fun unregisterOnSharedPreferenceChangeListener(
listener: SharedPreferences.OnSharedPreferenceChangeListener?
) = throw UnsupportedOperationException()
override fun getInt(key: String?, defValue: Int): Int = store.getOrDefault(key, defValue) as Int
override fun getAll(): MutableMap<String?, Any?> {
return HashMap(store)
}
override fun edit(): SharedPreferences.Editor {
return MockEditor(store)
}
override fun getLong(key: String?, defValue: Long): Long {
throw UnsupportedOperationException()
}
override fun getFloat(key: String?, defValue: Float): Float {
throw UnsupportedOperationException()
}
override fun getStringSet(key: String?, defValues: MutableSet<String>?): MutableSet<String>? {
return store.getOrDefault(key, defValues) as MutableSet<String>?
}
override fun registerOnSharedPreferenceChangeListener(
listener: SharedPreferences.OnSharedPreferenceChangeListener?
) = throw UnsupportedOperationException()
override fun getString(key: String?, defValue: String?): String? = store.getOrDefault(key, defValue) as String?
}

View file

@ -0,0 +1,87 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.migrations
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotSame
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
@Suppress("MagicNumber")
class MockSharedPreferencesTest {
private lateinit var mock: MockSharedPreferences
@Before
fun setUp() {
mock = MockSharedPreferences()
}
@Test
fun getSetStringSet() {
val value = setOf("alpha", "bravo", "charlie")
mock.edit().putStringSet("key", value).apply()
val copy = mock.getStringSet("key", mutableSetOf())
assertNotSame(value, copy)
assertEquals(value, copy)
}
@Test
fun getSetInt() {
val value = 42
val editor = mock.edit()
editor.putInt("key", value)
assertEquals(100, mock.getInt("key", 100))
editor.apply()
assertEquals(42, mock.getInt("key", 100))
}
@Test
fun getSetBoolean() {
val value = true
val editor = mock.edit()
editor.putBoolean("key", value)
assertFalse(mock.getBoolean("key", false))
editor.apply()
assertTrue(mock.getBoolean("key", false))
}
@Test
fun getSetString() {
val value = "a value"
val editor = mock.edit()
editor.putString("key", value)
assertEquals("default", mock.getString("key", "default"))
editor.apply()
assertEquals("a value", mock.getString("key", "default"))
}
@Test
fun getAll() {
// GIVEN
// few properties are stored in shared preferences
mock.edit()
.putInt("int", 1)
.putBoolean("bool", true)
.putString("string", "value")
.putStringSet("stringSet", setOf("alpha", "bravo"))
.apply()
assertEquals(4, mock.store.size)
// WHEN
// all properties are retrieved
val all = mock.all
// THEN
// returned map is a different instance
// map is equal to internal storage
assertNotSame(all, mock.store)
assertEquals(all, mock.store)
}
}

View file

@ -0,0 +1,42 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.network
import android.accounts.AccountManager
import android.content.Context
import android.net.ConnectivityManager
import com.nextcloud.client.account.UserAccountManagerImpl
import com.nextcloud.client.core.ClockImpl
import com.nextcloud.client.network.ConnectivityServiceImpl.GetRequestBuilder
import com.owncloud.android.AbstractOnServerIT
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class ConnectivityServiceImplIT : AbstractOnServerIT() {
@Test
fun testInternetWalled() {
val connectivityManager = targetContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val accountManager = targetContext.getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
val userAccountManager = UserAccountManagerImpl(targetContext, accountManager)
val clientFactory = ClientFactoryImpl(targetContext)
val requestBuilder = GetRequestBuilder()
val walledCheckCache = WalledCheckCache(ClockImpl())
val sut = ConnectivityServiceImpl(
connectivityManager,
userAccountManager,
clientFactory,
requestBuilder,
walledCheckCache
)
assertTrue(sut.connectivity.isConnected)
assertFalse(sut.isInternetWalled)
}
}

View file

@ -0,0 +1,28 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.sso
import androidx.test.espresso.intent.rule.IntentsTestRule
import com.owncloud.android.AbstractIT
import com.owncloud.android.ui.activity.SsoGrantPermissionActivity
import org.junit.Rule
import org.junit.Test
class SSOActivityTests : AbstractIT() {
@Suppress("DEPRECATION")
@get:Rule
var activityRule = IntentsTestRule(SsoGrantPermissionActivity::class.java, true, false)
@Test
fun testActivityTheme() {
val sut = activityRule.launchActivity(null)
assert(sut.binding != null)
assert(sut.materialAlertDialogBuilder != null)
}
}

View file

@ -0,0 +1,75 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper_ozturk@proton.me>
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.extensions
import android.os.Bundle
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.nextcloud.test.model.OtherTestData
import com.nextcloud.test.model.TestData
import com.nextcloud.test.model.TestDataParcelable
import com.nextcloud.utils.extensions.getParcelableArgument
import com.nextcloud.utils.extensions.getSerializableArgument
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
import org.junit.runner.RunWith
@Suppress("FunctionNaming")
@RunWith(AndroidJUnit4::class)
class BundleExtensionTests {
private val key = "testDataKey"
@Test
fun test_get_serializable_argument_when_given_valid_bundle_should_return_expected_data() {
val bundle = Bundle()
val testObject = TestData("Hello")
bundle.putSerializable(key, testObject)
val retrievedObject = bundle.getSerializableArgument(key, TestData::class.java)
assertEquals(testObject, retrievedObject)
}
@Test
fun test_get_serializable_argument_when_given_valid_bundle_and_wrong_class_type_should_return_null() {
val bundle = Bundle()
val testObject = TestData("Hello")
bundle.putSerializable(key, testObject)
val retrievedObject = bundle.getSerializableArgument(key, Array<String>::class.java)
assertNull(retrievedObject)
}
@Test
fun test_get_parcelable_argument_when_given_valid_bundle_and_wrong_class_type_should_return_null() {
val bundle = Bundle()
val testObject = TestData("Hello")
bundle.putSerializable(key, testObject)
val retrievedObject = bundle.getParcelableArgument(key, OtherTestData::class.java)
assertNull(retrievedObject)
}
@Test
fun test_get_parcelable_argument_when_given_valid_bundle_should_return_expected_data() {
val bundle = Bundle()
val testObject = TestDataParcelable("Hello")
bundle.putParcelable(key, testObject)
val retrievedObject = bundle.getParcelableArgument(key, TestDataParcelable::class.java)
assertEquals(testObject, retrievedObject)
}
@Test
fun test_get_serializable_argument_when_given_null_bundle_should_return_null() {
val retrievedObject = (null as Bundle?).getSerializableArgument(key, TestData::class.java)
assertNull(retrievedObject)
}
@Test
fun test_get_parcelable_argument_when_given_null_bundle_should_return_null() {
val retrievedObject = (null as Bundle?).getParcelableArgument(key, TestDataParcelable::class.java)
assertNull(retrievedObject)
}
}

View file

@ -0,0 +1,75 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper_ozturk@proton.me>
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.extensions
import android.content.Intent
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.nextcloud.test.model.OtherTestData
import com.nextcloud.test.model.TestData
import com.nextcloud.test.model.TestDataParcelable
import com.nextcloud.utils.extensions.getParcelableArgument
import com.nextcloud.utils.extensions.getSerializableArgument
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
import org.junit.runner.RunWith
@Suppress("FunctionNaming")
@RunWith(AndroidJUnit4::class)
class IntentExtensionTests {
private val key = "testDataKey"
@Test
fun test_get_serializable_argument_when_given_valid_intent_should_return_expected_data() {
val intent = Intent()
val testObject = TestData("Hello")
intent.putExtra(key, testObject)
val retrievedObject = intent.getSerializableArgument(key, TestData::class.java)
assertEquals(testObject, retrievedObject)
}
@Test
fun test_get_serializable_argument_when_given_valid_intent_and_wrong_class_type_should_return_null() {
val intent = Intent()
val testObject = TestData("Hello")
intent.putExtra(key, testObject)
val retrievedObject = intent.getSerializableArgument(key, Array<String>::class.java)
assertNull(retrievedObject)
}
@Test
fun test_get_parcelable_argument_when_given_valid_intent_and_wrong_class_type_should_return_null() {
val intent = Intent()
val testObject = TestData("Hello")
intent.putExtra(key, testObject)
val retrievedObject = intent.getParcelableArgument(key, OtherTestData::class.java)
assertNull(retrievedObject)
}
@Test
fun test_get_parcelable_argument_when_given_valid_intent_should_return_expected_data() {
val intent = Intent()
val testObject = TestDataParcelable("Hello")
intent.putExtra(key, testObject)
val retrievedObject = intent.getParcelableArgument(key, TestDataParcelable::class.java)
assertEquals(testObject, retrievedObject)
}
@Test
fun test_get_serializable_argument_when_given_null_intent_should_return_null() {
val retrievedObject = (null as Intent?).getSerializableArgument(key, TestData::class.java)
assertNull(retrievedObject)
}
@Test
fun test_get_parcelable_argument_when_given_null_intent_should_return_null() {
val retrievedObject = (null as Intent?).getParcelableArgument(key, TestDataParcelable::class.java)
assertNull(retrievedObject)
}
}

View file

@ -0,0 +1,48 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2021 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.sso
import com.nextcloud.android.sso.InputStreamBinder
import com.nextcloud.android.sso.QueryParam
import org.junit.Assert.assertEquals
import org.junit.Test
class InputStreamBinderTest {
@Test
fun convertMapToNVP() {
val source = mutableMapOf<String, String>()
source["quality"] = "1024p"
source["someOtherParameter"] = "parameterValue"
source["duplicate"] = "1"
source["duplicate"] = "2" // this overwrites previous parameter
val output = InputStreamBinder.convertMapToNVP(source)
assertEquals(source.size, output.size)
assertEquals("1024p", output[0].value)
assertEquals("parameterValue", output[1].value)
assertEquals("2", output[2].value)
}
@Test
fun convertListToNVP() {
val source = mutableListOf<QueryParam>()
source.add(QueryParam("quality", "1024p"))
source.add(QueryParam("someOtherParameter", "parameterValue"))
source.add(QueryParam("duplicate", "1"))
source.add(QueryParam("duplicate", "2")) // here we can have same parameter multiple times
val output = InputStreamBinder.convertListToNVP(source)
assertEquals(source.size, output.size)
assertEquals("1024p", output[0].value)
assertEquals("parameterValue", output[1].value)
assertEquals("1", output[2].value)
assertEquals("2", output[3].value)
}
}

View file

@ -0,0 +1,41 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2021 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.test
import android.Manifest
import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
class GrantStoragePermissionRule private constructor() {
companion object {
@JvmStatic
fun grant(): TestRule = when {
Build.VERSION.SDK_INT < Build.VERSION_CODES.R -> GrantPermissionRule.grant(
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
else -> GrantManageExternalStoragePermissionRule()
}
}
private class GrantManageExternalStoragePermissionRule : TestRule {
override fun apply(base: Statement, description: Description): Statement = object : Statement() {
override fun evaluate() {
InstrumentationRegistry.getInstrumentation().uiAutomation.executeShellCommand(
"appops set --uid ${InstrumentationRegistry.getInstrumentation().targetContext.packageName} " +
"MANAGE_EXTERNAL_STORAGE allow"
)
base.evaluate()
}
}
}
}

View file

@ -0,0 +1,29 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.test
import android.app.Instrumentation
import androidx.test.platform.app.InstrumentationRegistry
import dagger.android.AndroidInjector
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
class InjectionOverrideRule(private val overrideInjectors: Map<Class<*>, AndroidInjector<*>>) : TestRule {
override fun apply(base: Statement, description: Description): Statement = object : Statement() {
override fun evaluate() {
val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
val testApp = instrumentation.targetContext.applicationContext as TestMainApp
overrideInjectors.entries.forEach {
testApp.addTestInjector(it.key, it.value)
}
base.evaluate()
testApp.clearTestInjectors()
}
}
}

View file

@ -0,0 +1,47 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.test
import androidx.test.core.app.launchActivity
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.nextcloud.client.preferences.AppPreferences
import com.owncloud.android.R
import dagger.android.AndroidInjector
import io.mockk.every
import io.mockk.mockk
import org.junit.Rule
import org.junit.Test
class InjectionTestActivityTest {
@get:Rule
val injectionOverrideRule =
InjectionOverrideRule(
mapOf(
InjectionTestActivity::class.java to AndroidInjector<InjectionTestActivity> { activity ->
val appPreferencesMock = mockk<AppPreferences>()
every { appPreferencesMock.lastUploadPath } returns INJECTED_STRING
activity.appPreferences = appPreferencesMock
}
)
)
@Test
fun testInjectionOverride() {
launchActivity<InjectionTestActivity>().use { _ ->
onView(withId(R.id.text)).check(matches(withText(INJECTED_STRING)))
}
}
companion object {
private const val INJECTED_STRING = "injected string"
}
}

View file

@ -0,0 +1,21 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.test
object RandomStringGenerator {
private const val DEFAULT_LENGTH = 8
private val ALLOWED_CHARACTERS = ('A'..'Z') + ('a'..'z') + ('0'..'9')
@JvmOverloads
@JvmStatic
fun make(length: Int = DEFAULT_LENGTH): String {
return (1..length)
.map { ALLOWED_CHARACTERS.random() }
.joinToString("")
}
}

View file

@ -0,0 +1,58 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.test
import com.owncloud.android.BuildConfig
import com.owncloud.android.lib.common.utils.Log_OC
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
/**
* C&p from https://stackoverflow.com/questions/45635833/how-can-i-use-flakytest-annotation-now on 18.03.2020
*/
class RetryTestRule(val retryCount: Int = defaultRetryValue) : TestRule {
companion object {
private val TAG = RetryTestRule::class.java.simpleName
@Suppress("MagicNumber")
private val defaultRetryValue: Int = if (BuildConfig.CI) 5 else 1
}
override fun apply(base: Statement, description: Description): Statement {
return statement(base, description)
}
@Suppress("TooGenericExceptionCaught") // and this exactly what we want here
private fun statement(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
Log_OC.d(TAG, "Evaluating ${description.methodName}")
var caughtThrowable: Throwable? = null
for (i in 0 until retryCount) {
try {
base.evaluate()
return
} catch (t: Throwable) {
caughtThrowable = t
Log_OC.e(TAG, description.methodName + ": run " + (i + 1) + " failed")
}
}
Log_OC.e(TAG, description.methodName + ": giving up after " + retryCount + " failures")
if (caughtThrowable != null) {
throw caughtThrowable
}
}
}
}
}

View file

@ -0,0 +1,59 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.test
import com.owncloud.android.MainApp
import com.owncloud.android.lib.common.utils.Log_OC
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
/**
* The purpose of this class is to allow overriding injections in Android classes (which use parameter injection instead
* of constructor injection).
*
* To automate its usage, pair with [InjectionOverrideRule]; or call [addTestInjector] manually for more control.
*/
class TestMainApp : MainApp() {
val foo = "BAR"
private var overrideInjectors: MutableMap<Class<*>, AndroidInjector<*>> = mutableMapOf()
/**
* If you call this before a test please remember to call [clearTestInjectors] afterwards
*/
fun addTestInjector(clazz: Class<*>, injector: AndroidInjector<*>) {
Log_OC.d(TAG, "addTestInjector: added injector for $clazz")
overrideInjectors[clazz] = injector
}
fun clearTestInjectors() {
overrideInjectors.clear()
}
override fun androidInjector(): AndroidInjector<Any> {
@Suppress("UNCHECKED_CAST")
return InjectorWrapper(dispatchingAndroidInjector, overrideInjectors as Map<Class<*>, AndroidInjector<Any>>)
}
class InjectorWrapper(
private val baseInjector: DispatchingAndroidInjector<Any>,
private val overrideInjectors: Map<Class<*>, AndroidInjector<Any>>
) : AndroidInjector<Any> {
override fun inject(instance: Any) {
baseInjector.inject(instance)
overrideInjectors[instance.javaClass]?.let { customInjector ->
Log_OC.d(TAG, "Injecting ${instance.javaClass} with ${customInjector.javaClass}")
customInjector.inject(instance)
}
}
}
companion object {
private const val TAG = "TestMainApp"
}
}

View file

@ -0,0 +1,40 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper_ozturk@proton.me>
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.test.model
import android.os.Parcel
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import java.io.Serializable
@Parcelize
class OtherTestData : Parcelable
data class TestData(val message: String) : Serializable
data class TestDataParcelable(val message: String) : Parcelable {
constructor(parcel: Parcel) : this(parcel.readString() ?: "")
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(message)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<TestDataParcelable> {
override fun createFromParcel(parcel: Parcel): TestDataParcelable {
return TestDataParcelable(parcel)
}
override fun newArray(size: Int): Array<TestDataParcelable?> {
return arrayOfNulls(size)
}
}
}

View file

@ -0,0 +1,124 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.ui
import android.graphics.BitmapFactory
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.test.espresso.intent.rule.IntentsTestRule
import com.nextcloud.test.TestActivity
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import com.owncloud.android.utils.BitmapUtils
import com.owncloud.android.utils.ScreenshotTest
import org.junit.Rule
import org.junit.Test
class BitmapIT : AbstractIT() {
@get:Rule
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
@Test
@ScreenshotTest
fun roundBitmap() {
val file = getFile("christine.jpg")
val bitmap = BitmapFactory.decodeFile(file.absolutePath)
val activity = testActivityRule.launchActivity(null)
val imageView = ImageView(activity).apply {
setImageBitmap(bitmap)
}
val bitmap2 = BitmapFactory.decodeFile(file.absolutePath)
val imageView2 = ImageView(activity).apply {
setImageBitmap(BitmapUtils.roundBitmap(bitmap2))
}
val linearLayout = LinearLayout(activity).apply {
orientation = LinearLayout.VERTICAL
setBackgroundColor(context.getColor(R.color.grey_200))
}
linearLayout.addView(imageView, 200, 200)
linearLayout.addView(imageView2, 200, 200)
activity.addView(linearLayout)
screenshot(activity)
}
// @Test
// @ScreenshotTest
// fun glideSVG() {
// val activity = testActivityRule.launchActivity(null)
// val accountProvider = UserAccountManagerImpl.fromContext(activity)
// val clientFactory = ClientFactoryImpl(activity)
//
// val linearLayout = LinearLayout(activity).apply {
// orientation = LinearLayout.VERTICAL
// setBackgroundColor(context.getColor(R.color.grey_200))
// }
//
// val file = getFile("christine.jpg")
// val bitmap = BitmapFactory.decodeFile(file.absolutePath)
//
// ImageView(activity).apply {
// setImageBitmap(bitmap)
// linearLayout.addView(this, 50, 50)
// }
//
// downloadIcon(
// client.baseUri.toString() + "/apps/files/img/app.svg",
// activity,
// linearLayout,
// accountProvider,
// clientFactory
// )
//
// downloadIcon(
// client.baseUri.toString() + "/core/img/actions/group.svg",
// activity,
// linearLayout,
// accountProvider,
// clientFactory
// )
//
// activity.addView(linearLayout)
//
// longSleep()
//
// screenshot(activity)
// }
//
// private fun downloadIcon(
// url: String,
// activity: TestActivity,
// linearLayout: LinearLayout,
// accountProvider: UserAccountManager,
// clientFactory: ClientFactory
// ) {
// val view = ImageView(activity).apply {
// linearLayout.addView(this, 50, 50)
// }
// val target = object : SimpleTarget<Drawable>() {
// override fun onResourceReady(resource: Drawable?, glideAnimation: GlideAnimation<in Drawable>?) {
// view.setColorFilter(targetContext.getColor(R.color.dark), PorterDuff.Mode.SRC_ATOP)
// view.setImageDrawable(resource)
// }
// }
//
// testActivityRule.runOnUiThread {
// DisplayUtils.downloadIcon(
// accountProvider,
// clientFactory,
// activity,
// url,
// target,
// R.drawable.ic_user
// )
// }
// }
}

View file

@ -0,0 +1,45 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.ui
import androidx.test.espresso.intent.rule.IntentsTestRule
import com.owncloud.android.AbstractIT
import com.owncloud.android.lib.resources.users.ClearAt
import com.owncloud.android.lib.resources.users.PredefinedStatus
import com.owncloud.android.lib.resources.users.Status
import com.owncloud.android.lib.resources.users.StatusType
import com.owncloud.android.ui.activity.FileDisplayActivity
import org.junit.Rule
import org.junit.Test
class SetStatusDialogFragmentIT : AbstractIT() {
@get:Rule
var activityRule = IntentsTestRule(FileDisplayActivity::class.java, true, false)
@Test
fun open() {
val sut = SetStatusDialogFragment.newInstance(user, Status(StatusType.DND, "Working hard…", "🤖", -1))
val activity = activityRule.launchActivity(null)
sut.show(activity.supportFragmentManager, "")
val predefinedStatus: ArrayList<PredefinedStatus> = arrayListOf(
PredefinedStatus("meeting", "📅", "In a meeting", ClearAt("period", "3600")),
PredefinedStatus("commuting", "🚌", "Commuting", ClearAt("period", "1800")),
PredefinedStatus("remote-work", "🏡", "Working remotely", ClearAt("end-of", "day")),
PredefinedStatus("sick-leave", "🤒", "Out sick", ClearAt("end-of", "day")),
PredefinedStatus("vacationing", "🌴", "Vacationing", null)
)
shortSleep()
activity.runOnUiThread { sut.setPredefinedStatus(predefinedStatus) }
longSleep()
}
}

View file

@ -0,0 +1,52 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 TSI-mc
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nmc.android.ui
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class LauncherActivityIT : AbstractIT() {
@get:Rule
val activityRule = ActivityScenarioRule(LauncherActivity::class.java)
@Test
fun testSplashScreenWithEmptyTitlesShouldHideTitles() {
waitForIdleSync()
onView(withId(R.id.ivSplash)).check(matches(isCompletelyDisplayed()))
onView(withId(R.id.splashScreenBold)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
onView(withId(R.id.splashScreenNormal)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
}
@Test
fun testSplashScreenWithTitlesShouldShowTitles() {
waitForIdleSync()
onView(withId(R.id.ivSplash)).check(matches(isCompletelyDisplayed()))
activityRule.scenario.onActivity {
it.setSplashTitles("Example", "Cloud")
}
val onePercentArea = ViewMatchers.isDisplayingAtLeast(1)
onView(withId(R.id.splashScreenBold)).check(matches(onePercentArea))
onView(withId(R.id.splashScreenNormal)).check(matches(onePercentArea))
}
}

View file

@ -0,0 +1,544 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2018 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import com.facebook.testing.screenshot.Screenshot;
import com.facebook.testing.screenshot.internal.TestNameDetector;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.account.UserAccountManagerImpl;
import com.nextcloud.client.device.BatteryStatus;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.jobs.upload.FileUploadWorker;
import com.nextcloud.client.network.Connectivity;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.client.preferences.AppPreferencesImpl;
import com.nextcloud.client.preferences.DarkMode;
import com.nextcloud.common.NextcloudClient;
import com.nextcloud.test.GrantStoragePermissionRule;
import com.nextcloud.test.RandomStringGenerator;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.db.OCUpload;
import com.owncloud.android.files.services.NameCollisionPolicy;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientFactory;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
import com.owncloud.android.lib.resources.status.CapabilityBooleanType;
import com.owncloud.android.lib.resources.status.GetCapabilitiesRemoteOperation;
import com.owncloud.android.lib.resources.status.OCCapability;
import com.owncloud.android.lib.resources.status.OwnCloudVersion;
import com.owncloud.android.operations.CreateFolderOperation;
import com.owncloud.android.operations.UploadFileOperation;
import com.owncloud.android.utils.FileStorageUtils;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.rules.TestRule;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.test.espresso.contrib.DrawerActions;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import androidx.test.runner.lifecycle.Stage;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
/**
* Common base for all integration tests.
*/
public abstract class AbstractIT {
@Rule
public final TestRule permissionRule = GrantStoragePermissionRule.grant();
protected static OwnCloudClient client;
protected static NextcloudClient nextcloudClient;
protected static Account account;
protected static User user;
protected static Context targetContext;
protected static String DARK_MODE = "";
protected static String COLOR = "";
protected Activity currentActivity;
protected FileDataStorageManager fileDataStorageManager =
new FileDataStorageManager(user, targetContext.getContentResolver());
protected ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
@BeforeClass
public static void beforeAll() {
try {
// clean up
targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
AccountManager platformAccountManager = AccountManager.get(targetContext);
for (Account account : platformAccountManager.getAccounts()) {
if (account.type.equalsIgnoreCase("nextcloud")) {
platformAccountManager.removeAccountExplicitly(account);
}
}
account = createAccount("test@https://nextcloud.localhost");
user = getUser(account);
client = OwnCloudClientFactory.createOwnCloudClient(account, targetContext);
nextcloudClient = OwnCloudClientFactory.createNextcloudClient(user, targetContext);
} catch (OperationCanceledException |
IOException |
AccountUtils.AccountNotFoundException |
AuthenticatorException e) {
throw new RuntimeException("Error setting up clients", e);
}
Bundle arguments = androidx.test.platform.app.InstrumentationRegistry.getArguments();
// color
String colorParameter = arguments.getString("COLOR");
if (!TextUtils.isEmpty(colorParameter)) {
FileDataStorageManager fileDataStorageManager = new FileDataStorageManager(user,
targetContext.getContentResolver());
String colorHex = null;
COLOR = colorParameter;
switch (colorParameter) {
case "red":
colorHex = "#7c0000";
break;
case "green":
colorHex = "#00ff00";
break;
case "white":
colorHex = "#ffffff";
break;
case "black":
colorHex = "#000000";
break;
case "lightgreen":
colorHex = "#aaff00";
break;
default:
break;
}
OCCapability capability = fileDataStorageManager.getCapability(account.name);
capability.setGroupfolders(CapabilityBooleanType.TRUE);
if (colorHex != null) {
capability.setServerColor(colorHex);
}
fileDataStorageManager.saveCapabilities(capability);
}
// dark / light
String darkModeParameter = arguments.getString("DARKMODE");
if (darkModeParameter != null) {
if (darkModeParameter.equalsIgnoreCase("dark")) {
DARK_MODE = "dark";
AppPreferencesImpl.fromContext(targetContext).setDarkThemeMode(DarkMode.DARK);
MainApp.setAppTheme(DarkMode.DARK);
} else {
DARK_MODE = "light";
}
}
if (DARK_MODE.equalsIgnoreCase("light") && COLOR.equalsIgnoreCase("blue")) {
// use already existing names
DARK_MODE = "";
COLOR = "";
}
}
protected void testOnlyOnServer(OwnCloudVersion version) throws AccountUtils.AccountNotFoundException {
OCCapability ocCapability = getCapability();
assumeTrue(ocCapability.getVersion().isNewerOrEqual(version));
}
protected OCCapability getCapability() throws AccountUtils.AccountNotFoundException {
NextcloudClient client = OwnCloudClientFactory.createNextcloudClient(user, targetContext);
OCCapability ocCapability = (OCCapability) new GetCapabilitiesRemoteOperation()
.execute(client)
.getSingleData();
return ocCapability;
}
@Before
public void enableAccessibilityChecks() {
androidx.test.espresso.accessibility.AccessibilityChecks.enable().setRunChecksFromRootView(true);
}
@After
public void after() {
fileDataStorageManager.removeLocalFiles(user, fileDataStorageManager);
fileDataStorageManager.deleteAllFiles();
}
protected FileDataStorageManager getStorageManager() {
return fileDataStorageManager;
}
protected Account[] getAllAccounts() {
return AccountManager.get(targetContext).getAccounts();
}
protected static void createDummyFiles() throws IOException {
File tempPath = new File(FileStorageUtils.getTemporalPath(account.name));
if (!tempPath.exists()) {
assertTrue(tempPath.mkdirs());
}
assertTrue(tempPath.exists());
createFile("empty.txt", 0);
createFile("nonEmpty.txt", 100);
createFile("chunkedFile.txt", 500000);
}
protected static File getDummyFile(String name) throws IOException {
File file = new File(FileStorageUtils.getInternalTemporalPath(account.name, targetContext) + File.separator + name);
if (file.exists()) {
return file;
} else if (name.endsWith("/")) {
file.mkdirs();
return file;
} else {
switch (name) {
case "empty.txt":
return createFile("empty.txt", 0);
case "nonEmpty.txt":
return createFile("nonEmpty.txt", 100);
case "chunkedFile.txt":
return createFile("chunkedFile.txt", 500000);
default:
return createFile(name, 0);
}
}
}
public static File createFile(String name, int iteration) throws IOException {
File file = new File(FileStorageUtils.getTemporalPath(account.name) + File.separator + name);
if (!file.getParentFile().exists()) {
assertTrue(file.getParentFile().mkdirs());
}
file.createNewFile();
FileWriter writer = new FileWriter(file);
for (int i = 0; i < iteration; i++) {
writer.write("123123123123123123123123123\n");
}
writer.flush();
writer.close();
return file;
}
protected File getFile(String filename) throws IOException {
InputStream inputStream = getInstrumentation().getContext().getAssets().open(filename);
File temp = File.createTempFile("file", "file");
FileUtils.copyInputStreamToFile(inputStream, temp);
return temp;
}
protected void waitForIdleSync() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
protected void onIdleSync(Runnable recipient) {
InstrumentationRegistry.getInstrumentation().waitForIdle(recipient);
}
protected void openDrawer(IntentsTestRule activityRule) {
Activity sut = activityRule.launchActivity(null);
shortSleep();
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open());
waitForIdleSync();
screenshot(sut);
}
protected Activity getCurrentActivity() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
Collection<Activity> resumedActivities = ActivityLifecycleMonitorRegistry
.getInstance()
.getActivitiesInStage(Stage.RESUMED);
if (resumedActivities.iterator().hasNext()) {
currentActivity = resumedActivities.iterator().next();
}
});
return currentActivity;
}
protected static void shortSleep() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
protected void longSleep() {
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
protected void sleep(int second) {
try {
Thread.sleep(1000L * second);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public OCFile createFolder(String remotePath) {
RemoteOperationResult check = new ExistenceCheckRemoteOperation(remotePath, false).execute(client);
if (!check.isSuccess()) {
assertTrue(new CreateFolderOperation(remotePath, user, targetContext, getStorageManager())
.execute(client)
.isSuccess());
}
return getStorageManager().getFileByDecryptedRemotePath(remotePath.endsWith("/") ? remotePath : remotePath + "/");
}
public void uploadFile(File file, String remotePath) {
OCUpload ocUpload = new OCUpload(file.getAbsolutePath(), remotePath, account.name);
uploadOCUpload(ocUpload);
}
public void uploadOCUpload(OCUpload ocUpload) {
ConnectivityService connectivityServiceMock = new ConnectivityService() {
@Override
public boolean isConnected() {
return false;
}
@Override
public boolean isInternetWalled() {
return false;
}
@Override
public Connectivity getConnectivity() {
return Connectivity.CONNECTED_WIFI;
}
};
PowerManagementService powerManagementServiceMock = new PowerManagementService() {
@NonNull
@Override
public BatteryStatus getBattery() {
return new BatteryStatus();
}
@Override
public boolean isPowerSavingEnabled() {
return false;
}
@Override
public boolean isPowerSavingExclusionAvailable() {
return false;
}
};
UserAccountManager accountManager = UserAccountManagerImpl.fromContext(targetContext);
UploadsStorageManager uploadsStorageManager = new UploadsStorageManager(accountManager,
targetContext.getContentResolver());
UploadFileOperation newUpload = new UploadFileOperation(
uploadsStorageManager,
connectivityServiceMock,
powerManagementServiceMock,
user,
null,
ocUpload,
NameCollisionPolicy.DEFAULT,
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
targetContext,
false,
false,
getStorageManager()
);
newUpload.addRenameUploadListener(() -> {
// dummy
});
newUpload.setRemoteFolderToBeCreated();
RemoteOperationResult result = newUpload.execute(client);
assertTrue(result.getLogMessage(), result.isSuccess());
}
protected void enableRTL() {
Locale locale = new Locale("ar");
Resources resources = InstrumentationRegistry.getInstrumentation().getTargetContext().getResources();
Configuration config = resources.getConfiguration();
config.setLocale(locale);
resources.updateConfiguration(config, null);
}
protected void resetLocale() {
Locale locale = new Locale("en");
Resources resources = InstrumentationRegistry.getInstrumentation().getTargetContext().getResources();
Configuration config = resources.getConfiguration();
config.setLocale(locale);
resources.updateConfiguration(config, null);
}
protected void screenshot(View view) {
screenshot(view, "");
}
protected void screenshotViaName(Activity activity, String name) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
Screenshot.snapActivity(activity).setName(name).record();
}
}
protected void screenshot(View view, String prefix) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
Screenshot.snap(view).setName(createName(prefix)).record();
}
}
protected void screenshot(Activity sut) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
Screenshot.snapActivity(sut).setName(createName()).record();
}
}
protected void screenshot(DialogFragment dialogFragment, String prefix) {
screenshot(Objects.requireNonNull(dialogFragment.requireDialog().getWindow()).getDecorView(), prefix);
}
private String createName() {
return createName("");
}
public String createName(String name, String prefix) {
if (!TextUtils.isEmpty(prefix)) {
name = name + "_" + prefix;
}
if (!DARK_MODE.isEmpty()) {
name = name + "_" + DARK_MODE;
}
if (!COLOR.isEmpty()) {
name = name + "_" + COLOR;
}
return name;
}
private String createName(String prefix) {
String name = TestNameDetector.getTestClass() + "_" + TestNameDetector.getTestName();
return createName(name, prefix);
}
public static String getUserId(User user) {
return AccountManager.get(targetContext).getUserData(user.toPlatformAccount(), KEY_USER_ID);
}
public String getRandomName() {
return getRandomName(5);
}
public String getRandomName(int length) {
return RandomStringGenerator.make(length);
}
protected static User getUser(Account account) {
Optional<User> optionalUser = UserAccountManagerImpl.fromContext(targetContext).getUser(account.name);
return optionalUser.orElseThrow(IllegalAccessError::new);
}
protected static Account createAccount(String name) {
AccountManager platformAccountManager = AccountManager.get(targetContext);
Account temp = new Account(name, MainApp.getAccountType(targetContext));
int atPos = name.lastIndexOf('@');
platformAccountManager.addAccountExplicitly(temp, "password", null);
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_BASE_URL,
name.substring(atPos + 1));
platformAccountManager.setUserData(temp, KEY_USER_ID, name.substring(0, atPos));
Account account = UserAccountManagerImpl.fromContext(targetContext).getAccountByName(name);
if (account == null) {
throw new ActivityNotFoundException();
}
return account;
}
protected static boolean removeAccount(Account account) {
return AccountManager.get(targetContext).removeAccountExplicitly(account);
}
}

View file

@ -0,0 +1,276 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.ActivityNotFoundException;
import android.net.Uri;
import android.os.Bundle;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.account.UserAccountManagerImpl;
import com.nextcloud.client.device.BatteryStatus;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.jobs.upload.FileUploadWorker;
import com.nextcloud.client.network.Connectivity;
import com.nextcloud.client.network.ConnectivityService;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.db.OCUpload;
import com.owncloud.android.files.services.NameCollisionPolicy;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientFactory;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.resources.e2ee.ToggleEncryptionRemoteOperation;
import com.owncloud.android.lib.resources.files.ReadFolderRemoteOperation;
import com.owncloud.android.lib.resources.files.RemoveFileRemoteOperation;
import com.owncloud.android.lib.resources.files.model.RemoteFile;
import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.operations.UploadFileOperation;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.junit.After;
import org.junit.Assert;
import org.junit.BeforeClass;
import java.io.File;
import java.io.IOException;
import java.util.Optional;
import androidx.annotation.NonNull;
import androidx.test.platform.app.InstrumentationRegistry;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* Common base for all integration tests.
*/
public abstract class AbstractOnServerIT extends AbstractIT {
@BeforeClass
public static void beforeAll() {
try {
// clean up
targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
AccountManager platformAccountManager = AccountManager.get(targetContext);
for (Account account : platformAccountManager.getAccounts()) {
if (account.type.equalsIgnoreCase("nextcloud")) {
platformAccountManager.removeAccountExplicitly(account);
}
}
Bundle arguments = androidx.test.platform.app.InstrumentationRegistry.getArguments();
Uri baseUrl = Uri.parse(arguments.getString("TEST_SERVER_URL"));
String loginName = arguments.getString("TEST_SERVER_USERNAME");
String password = arguments.getString("TEST_SERVER_PASSWORD");
Account temp = new Account(loginName + "@" + baseUrl, MainApp.getAccountType(targetContext));
UserAccountManager accountManager = UserAccountManagerImpl.fromContext(targetContext);
if (!accountManager.exists(temp)) {
platformAccountManager.addAccountExplicitly(temp, password, null);
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_ACCOUNT_VERSION,
Integer.toString(UserAccountManager.ACCOUNT_VERSION));
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_VERSION, "14.0.0.0");
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_BASE_URL, baseUrl.toString());
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_USER_ID, loginName); // same as userId
}
final UserAccountManager userAccountManager = UserAccountManagerImpl.fromContext(targetContext);
account = userAccountManager.getAccountByName(loginName + "@" + baseUrl);
if (account == null) {
throw new ActivityNotFoundException();
}
Optional<User> optionalUser = userAccountManager.getUser(account.name);
user = optionalUser.orElseThrow(IllegalAccessError::new);
client = OwnCloudClientFactory.createOwnCloudClient(account, targetContext);
nextcloudClient = OwnCloudClientFactory.createNextcloudClient(user, targetContext);
createDummyFiles();
waitForServer(client, baseUrl);
// deleteAllFilesOnServer(); // makes sure that no file/folder is in root
} catch (OperationCanceledException |
IOException |
AccountUtils.AccountNotFoundException |
AuthenticatorException e) {
throw new RuntimeException("Error setting up clients", e);
}
}
@After
public void after() {
deleteAllFilesOnServer();
super.after();
}
public static void deleteAllFilesOnServer() {
RemoteOperationResult result = new ReadFolderRemoteOperation("/").execute(client);
assertTrue(result.getLogMessage(), result.isSuccess());
for (Object object : result.getData()) {
RemoteFile remoteFile = (RemoteFile) object;
if (!remoteFile.getRemotePath().equals("/")) {
if (remoteFile.isEncrypted()) {
ToggleEncryptionRemoteOperation operation = new ToggleEncryptionRemoteOperation(remoteFile.getLocalId(),
remoteFile.getRemotePath(),
false);
boolean operationResult = operation
.execute(client)
.isSuccess();
assertTrue(operationResult);
}
boolean removeResult = false;
for (int i = 0; i < 5; i++) {
removeResult = new RemoveFileRemoteOperation(remoteFile.getRemotePath())
.execute(client)
.isSuccess();
if (removeResult) {
break;
}
shortSleep();
}
assertTrue(removeResult);
}
}
}
private static void waitForServer(OwnCloudClient client, Uri baseUrl) {
GetMethod get = new GetMethod(baseUrl + "/status.php");
try {
int i = 0;
while (client.executeMethod(get) != HttpStatus.SC_OK && i < 3) {
System.out.println("wait…");
Thread.sleep(60 * 1000);
i++;
}
if (i == 3) {
Assert.fail("Server not ready!");
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void uploadOCUpload(OCUpload ocUpload) {
uploadOCUpload(ocUpload, FileUploadWorker.LOCAL_BEHAVIOUR_COPY);
}
public void uploadOCUpload(OCUpload ocUpload, int localBehaviour) {
ConnectivityService connectivityServiceMock = new ConnectivityService() {
@Override
public boolean isConnected() {
return false;
}
@Override
public boolean isInternetWalled() {
return false;
}
@Override
public Connectivity getConnectivity() {
return Connectivity.CONNECTED_WIFI;
}
};
PowerManagementService powerManagementServiceMock = new PowerManagementService() {
@NonNull
@Override
public BatteryStatus getBattery() {
return new BatteryStatus();
}
@Override
public boolean isPowerSavingEnabled() {
return false;
}
@Override
public boolean isPowerSavingExclusionAvailable() {
return false;
}
};
UserAccountManager accountManager = UserAccountManagerImpl.fromContext(targetContext);
UploadsStorageManager uploadsStorageManager = new UploadsStorageManager(accountManager,
targetContext.getContentResolver());
UploadFileOperation newUpload = new UploadFileOperation(
uploadsStorageManager,
connectivityServiceMock,
powerManagementServiceMock,
user,
null,
ocUpload,
NameCollisionPolicy.DEFAULT,
localBehaviour,
targetContext,
false,
false,
getStorageManager()
);
newUpload.addRenameUploadListener(() -> {
// dummy
});
newUpload.setRemoteFolderToBeCreated();
RemoteOperationResult result = newUpload.execute(client);
assertTrue(result.getLogMessage(), result.isSuccess());
OCFile parentFolder = getStorageManager()
.getFileByEncryptedRemotePath(new File(ocUpload.getRemotePath()).getParent() + "/");
String uploadedFileName = new File(ocUpload.getRemotePath()).getName();
OCFile uploadedFile = getStorageManager().
getFileByDecryptedRemotePath(parentFolder.getDecryptedRemotePath() + uploadedFileName);
assertNotNull(uploadedFile.getRemoteId());
assertNotNull(uploadedFile.getPermissions());
if (localBehaviour == FileUploadWorker.LOCAL_BEHAVIOUR_COPY ||
localBehaviour == FileUploadWorker.LOCAL_BEHAVIOUR_MOVE) {
assertTrue(new File(uploadedFile.getStoragePath()).exists());
}
}
protected void refreshFolder(String path) {
assertTrue(new RefreshFolderOperation(getStorageManager().getFileByEncryptedRemotePath(path),
System.currentTimeMillis(),
false,
false,
getStorageManager(),
user,
targetContext
).execute(client).isSuccess());
}
}

View file

@ -0,0 +1,108 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android;
import android.net.Uri;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.db.OCUpload;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.operations.DownloadFileOperation;
import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.operations.RemoveFileOperation;
import com.owncloud.android.utils.FileStorageUtils;
import org.junit.After;
import org.junit.Test;
import java.io.File;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
/**
* Tests related to file uploads.
*/
public class DownloadIT extends AbstractOnServerIT {
private static final String FOLDER = "/testUpload/";
@After
public void after() {
RemoteOperationResult result = new RefreshFolderOperation(getStorageManager().getFileByPath("/"),
System.currentTimeMillis() / 1000L,
false,
true,
getStorageManager(),
user,
targetContext)
.execute(client);
// cleanup only if folder exists
if (result.isSuccess() && getStorageManager().getFileByDecryptedRemotePath(FOLDER) != null) {
new RemoveFileOperation(getStorageManager().getFileByDecryptedRemotePath(FOLDER),
false,
user,
false,
targetContext,
getStorageManager())
.execute(client);
}
}
@Test
public void verifyDownload() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
FOLDER + "nonEmpty.txt",
account.name);
uploadOCUpload(ocUpload);
OCUpload ocUpload2 = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
FOLDER + "nonEmpty2.txt",
account.name);
uploadOCUpload(ocUpload2);
refreshFolder("/");
refreshFolder(FOLDER);
OCFile file1 = fileDataStorageManager.getFileByDecryptedRemotePath(FOLDER + "nonEmpty.txt");
OCFile file2 = fileDataStorageManager.getFileByDecryptedRemotePath(FOLDER + "nonEmpty2.txt");
verifyDownload(file1, file2);
assertTrue(new DownloadFileOperation(user, file1, targetContext).execute(client).isSuccess());
assertTrue(new DownloadFileOperation(user, file2, targetContext).execute(client).isSuccess());
refreshFolder(FOLDER);
file1 = fileDataStorageManager.getFileByDecryptedRemotePath(FOLDER + "nonEmpty.txt");
file2 = fileDataStorageManager.getFileByDecryptedRemotePath(FOLDER + "nonEmpty2.txt");
verifyDownload(file1, file2);
}
private void verifyDownload(OCFile file1, OCFile file2) {
assertNotNull(file1);
assertNotNull(file2);
assertNotSame(file1.getStoragePath(), file2.getStoragePath());
assertTrue(new File(file1.getStoragePath()).exists());
assertTrue(new File(file2.getStoragePath()).exists());
// test against hardcoded path to make sure that it is correct
assertEquals("/storage/emulated/0/Android/media/com.nextcloud.client/nextcloud/" +
Uri.encode(account.name, "@") + "/testUpload/nonEmpty.txt",
file1.getStoragePath());
assertEquals("/storage/emulated/0/Android/media/com.nextcloud.client/nextcloud/" +
Uri.encode(account.name, "@") + "/testUpload/nonEmpty2.txt",
file2.getStoragePath());
}
}

View file

@ -0,0 +1,32 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android
import com.owncloud.android.datamodel.OCFile
import java.security.SecureRandom
open class EncryptionIT : AbstractIT() {
fun testFolder(): OCFile {
val rootPath = "/"
val folderPath = "/TestFolder/"
OCFile(rootPath).apply {
storageManager.saveFile(this)
}
return OCFile(folderPath).apply {
decryptedRemotePath = folderPath
isEncrypted = true
fileLength = SecureRandom().nextLong()
setFolder()
parentId = storageManager.getFileByDecryptedRemotePath(rootPath)!!.fileId
storageManager.saveFile(this)
}
}
}

View file

@ -0,0 +1,150 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2018 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.operations.CreateFolderOperation;
import com.owncloud.android.operations.RemoveFileOperation;
import com.owncloud.android.operations.RenameFileOperation;
import com.owncloud.android.operations.SynchronizeFolderOperation;
import com.owncloud.android.operations.common.SyncOperation;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.io.IOException;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
/**
* Tests related to file operations.
*/
@RunWith(AndroidJUnit4.class)
public class FileIT extends AbstractOnServerIT {
@Test
public void testCreateFolder() {
String path = "/testFolder/";
// folder does not exist yet
assertNull(getStorageManager().getFileByPath(path));
SyncOperation syncOp = new CreateFolderOperation(path, user, targetContext, getStorageManager());
RemoteOperationResult result = syncOp.execute(client);
assertTrue(result.toString(), result.isSuccess());
// folder exists
OCFile file = getStorageManager().getFileByPath(path);
assertTrue(file.isFolder());
// cleanup
assertTrue(new RemoveFileOperation(file, false, user, false, targetContext, getStorageManager())
.execute(client)
.isSuccess());
}
@Test
public void testCreateNonExistingSubFolder() {
String path = "/subFolder/1/2/3/4/5/";
// folder does not exist yet
assertNull(getStorageManager().getFileByPath(path));
SyncOperation syncOp = new CreateFolderOperation(path, user, targetContext, getStorageManager());
RemoteOperationResult result = syncOp.execute(client);
assertTrue(result.toString(), result.isSuccess());
// folder exists
OCFile file = getStorageManager().getFileByPath(path);
assertTrue(file.isFolder());
// cleanup
new RemoveFileOperation(file,
false,
user,
false,
targetContext,
getStorageManager())
.execute(client);
}
@Test
public void testRemoteIdNull() {
getStorageManager().deleteAllFiles();
assertEquals(0, getStorageManager().getAllFiles().size());
OCFile test = new OCFile("/123.txt");
getStorageManager().saveFile(test);
assertEquals(1, getStorageManager().getAllFiles().size());
getStorageManager().deleteAllFiles();
assertEquals(0, getStorageManager().getAllFiles().size());
}
@Test
public void testRenameFolder() throws IOException {
String folderPath = "/testRenameFolder/";
// create folder
createFolder(folderPath);
// upload file inside it
uploadFile(getDummyFile("nonEmpty.txt"), folderPath + "text.txt");
// sync folder
assertTrue(new SynchronizeFolderOperation(targetContext,
folderPath,
user,
System.currentTimeMillis(),
fileDataStorageManager)
.execute(targetContext)
.isSuccess());
// check if file exists
String storagePath1 = fileDataStorageManager.getFileByDecryptedRemotePath(folderPath).getStoragePath();
assertTrue(new File(storagePath1).exists());
String storagePath2 = fileDataStorageManager
.getFileByDecryptedRemotePath(folderPath + "text.txt")
.getStoragePath();
assertTrue(new File(storagePath2).exists());
shortSleep();
// Rename
assertTrue(
new RenameFileOperation(folderPath, "test123", fileDataStorageManager)
.execute(targetContext)
.isSuccess()
);
// after rename check new location
assertTrue(
new File(fileDataStorageManager.getFileByDecryptedRemotePath("/test123/").getStoragePath())
.exists()
);
assertTrue(
new File(fileDataStorageManager.getFileByDecryptedRemotePath("/test123/text.txt").getStoragePath())
.exists()
);
// old files do no exist
assertNull(fileDataStorageManager.getFileByDecryptedRemotePath(folderPath));
assertNull(fileDataStorageManager.getFileByDecryptedRemotePath(folderPath + "text.txt"));
// local files also do not exist
assertFalse(new File(storagePath1).exists());
assertFalse(new File(storagePath2).exists());
}
}

View file

@ -0,0 +1,137 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2018 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.operations.CreateFolderOperation;
import com.owncloud.android.operations.common.SyncOperation;
import com.owncloud.android.ui.activity.FileDisplayActivity;
import com.owncloud.android.ui.activity.SettingsActivity;
import com.owncloud.android.ui.activity.SyncedFoldersActivity;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import androidx.test.core.app.ActivityScenario;
import androidx.test.espresso.action.ViewActions;
import androidx.test.espresso.contrib.DrawerActions;
import androidx.test.espresso.contrib.RecyclerViewActions;
import androidx.test.espresso.matcher.PreferenceMatchers;
import androidx.test.filters.LargeTest;
import tools.fastlane.screengrab.Screengrab;
import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy;
import tools.fastlane.screengrab.locale.LocaleTestRule;
import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.Espresso.pressBack;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.core.AnyOf.anyOf;
import static org.junit.Assert.assertTrue;
@LargeTest
@RunWith(JUnit4.class)
public class ScreenshotsIT extends AbstractOnServerIT {
@ClassRule
public static final LocaleTestRule localeTestRule = new LocaleTestRule();
@BeforeClass
public static void beforeScreenshot() {
Screengrab.setDefaultScreenshotStrategy(new UiAutomatorScreenshotStrategy());
}
@Test
public void gridViewScreenshot() {
ActivityScenario.launch(FileDisplayActivity.class);
onView(anyOf(withText(R.string.action_switch_grid_view), withId(R.id.switch_grid_view_button))).perform(click());
shortSleep();
Screengrab.screenshot("01_gridView");
onView(anyOf(withText(R.string.action_switch_list_view), withId(R.id.switch_grid_view_button))).perform(click());
Assert.assertTrue(true); // if we reach this, everything is ok
}
@Test
public void listViewScreenshot() {
String path = "/Camera/";
// folder does not exist yet
if (getStorageManager().getFileByEncryptedRemotePath(path) == null) {
SyncOperation syncOp = new CreateFolderOperation(path, user, targetContext, getStorageManager());
RemoteOperationResult result = syncOp.execute(client);
assertTrue(result.isSuccess());
}
ActivityScenario.launch(FileDisplayActivity.class);
// go into work folder
onView(withId(R.id.list_root)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
Screengrab.screenshot("02_listView");
Assert.assertTrue(true); // if we reach this, everything is ok
}
@Test
public void drawerScreenshot() {
ActivityScenario.launch(FileDisplayActivity.class);
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open());
Screengrab.screenshot("03_drawer");
onView(withId(R.id.drawer_layout)).perform(DrawerActions.close());
Assert.assertTrue(true); // if we reach this, everything is ok
}
@Test
public void multipleAccountsScreenshot() {
ActivityScenario.launch(FileDisplayActivity.class);
onView(withId(R.id.switch_account_button)).perform(click());
Screengrab.screenshot("04_accounts");
pressBack();
Assert.assertTrue(true); // if we reach this, everything is ok
}
@Test
public void autoUploadScreenshot() {
ActivityScenario.launch(SyncedFoldersActivity.class);
Screengrab.screenshot("05_autoUpload");
Assert.assertTrue(true); // if we reach this, everything is ok
}
@Test
public void davdroidScreenshot() {
ActivityScenario.launch(SettingsActivity.class);
onData(PreferenceMatchers.withTitle(R.string.prefs_category_more)).perform(ViewActions.scrollTo());
shortSleep();
Screengrab.screenshot("06_davdroid");
Assert.assertTrue(true); // if we reach this, everything is ok
}
}

View file

@ -0,0 +1,517 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android;
import com.nextcloud.client.account.UserAccountManagerImpl;
import com.nextcloud.client.device.BatteryStatus;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.jobs.upload.FileUploadWorker;
import com.nextcloud.client.network.Connectivity;
import com.nextcloud.client.network.ConnectivityService;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.db.OCUpload;
import com.owncloud.android.files.services.NameCollisionPolicy;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.resources.files.model.GeoLocation;
import com.owncloud.android.lib.resources.files.model.ImageDimension;
import com.owncloud.android.lib.resources.status.NextcloudVersion;
import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.operations.RemoveFileOperation;
import com.owncloud.android.operations.UploadFileOperation;
import com.owncloud.android.utils.FileStorageUtils;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.concurrent.TimeUnit;
import androidx.annotation.NonNull;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertTrue;
/**
* Tests related to file uploads.
*/
public class UploadIT extends AbstractOnServerIT {
private static final String FOLDER = "/testUpload/";
private UploadsStorageManager uploadsStorageManager =
new UploadsStorageManager(UserAccountManagerImpl.fromContext(targetContext),
targetContext.getContentResolver());
private ConnectivityService connectivityServiceMock = new ConnectivityService() {
@Override
public boolean isConnected() {
return false;
}
@Override
public boolean isInternetWalled() {
return false;
}
@Override
public Connectivity getConnectivity() {
return Connectivity.CONNECTED_WIFI;
}
};
private PowerManagementService powerManagementServiceMock = new PowerManagementService() {
@Override
public boolean isPowerSavingEnabled() {
return false;
}
@Override
public boolean isPowerSavingExclusionAvailable() {
return false;
}
@NonNull
@Override
public BatteryStatus getBattery() {
return new BatteryStatus(false, 0);
}
};
@Before
public void before() throws IOException {
// make sure that every file is available, even after tests that remove source file
createDummyFiles();
}
@Test
public void testEmptyUpload() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
FOLDER + "empty.txt",
account.name);
uploadOCUpload(ocUpload);
}
@Test
public void testNonEmptyUpload() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
FOLDER + "nonEmpty.txt",
account.name);
uploadOCUpload(ocUpload);
}
@Test
public void testUploadWithCopy() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
FOLDER + "nonEmpty.txt",
account.name);
uploadOCUpload(ocUpload, FileUploadWorker.LOCAL_BEHAVIOUR_COPY);
File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(FOLDER + "nonEmpty.txt");
assertTrue(originalFile.exists());
assertTrue(new File(uploadedFile.getStoragePath()).exists());
verifyStoragePath(uploadedFile);
}
@Test
public void testUploadWithMove() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
FOLDER + "nonEmpty.txt",
account.name);
uploadOCUpload(ocUpload, FileUploadWorker.LOCAL_BEHAVIOUR_MOVE);
File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(FOLDER + "nonEmpty.txt");
assertFalse(originalFile.exists());
assertTrue(new File(uploadedFile.getStoragePath()).exists());
verifyStoragePath(uploadedFile);
}
@Test
public void testUploadWithForget() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
FOLDER + "nonEmpty.txt",
account.name);
uploadOCUpload(ocUpload, FileUploadWorker.LOCAL_BEHAVIOUR_FORGET);
File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(FOLDER + "nonEmpty.txt");
assertTrue(originalFile.exists());
assertFalse(new File(uploadedFile.getStoragePath()).exists());
assertTrue(uploadedFile.getStoragePath().isEmpty());
}
@Test
public void testUploadWithDelete() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
FOLDER + "nonEmpty.txt",
account.name);
uploadOCUpload(ocUpload, FileUploadWorker.LOCAL_BEHAVIOUR_DELETE);
File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(FOLDER + "nonEmpty.txt");
assertFalse(originalFile.exists());
assertFalse(new File(uploadedFile.getStoragePath()).exists());
assertTrue(uploadedFile.getStoragePath().isEmpty());
}
@Test
public void testChunkedUpload() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/chunkedFile.txt",
FOLDER + "chunkedFile.txt", account.name);
uploadOCUpload(ocUpload);
}
@Test
public void testUploadInNonExistingFolder() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
FOLDER + "2/3/4/1.txt", account.name);
uploadOCUpload(ocUpload);
}
@Test
public void testUploadOnChargingOnlyButNotCharging() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
FOLDER + "notCharging.txt", account.name);
ocUpload.setWhileChargingOnly(true);
UploadFileOperation newUpload = new UploadFileOperation(
uploadsStorageManager,
connectivityServiceMock,
powerManagementServiceMock,
user,
null,
ocUpload,
NameCollisionPolicy.DEFAULT,
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
targetContext,
false,
true,
getStorageManager()
);
newUpload.setRemoteFolderToBeCreated();
newUpload.addRenameUploadListener(() -> {
// dummy
});
RemoteOperationResult result = newUpload.execute(client);
assertFalse(result.toString(), result.isSuccess());
assertEquals(RemoteOperationResult.ResultCode.DELAYED_FOR_CHARGING, result.getCode());
}
@Test
public void testUploadOnChargingOnlyAndCharging() {
PowerManagementService powerManagementServiceMock = new PowerManagementService() {
@Override
public boolean isPowerSavingEnabled() {
return false;
}
@Override
public boolean isPowerSavingExclusionAvailable() {
return false;
}
@NonNull
@Override
public BatteryStatus getBattery() {
return new BatteryStatus(true, 100);
}
};
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
FOLDER + "charging.txt", account.name);
ocUpload.setWhileChargingOnly(true);
UploadFileOperation newUpload = new UploadFileOperation(
uploadsStorageManager,
connectivityServiceMock,
powerManagementServiceMock,
user,
null,
ocUpload,
NameCollisionPolicy.DEFAULT,
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
targetContext,
false,
true,
getStorageManager()
);
newUpload.setRemoteFolderToBeCreated();
newUpload.addRenameUploadListener(() -> {
// dummy
});
RemoteOperationResult result = newUpload.execute(client);
assertTrue(result.toString(), result.isSuccess());
}
@Test
public void testUploadOnWifiOnlyButNoWifi() {
ConnectivityService connectivityServiceMock = new ConnectivityService() {
@Override
public boolean isConnected() {
return false;
}
@Override
public boolean isInternetWalled() {
return false;
}
@Override
public Connectivity getConnectivity() {
return new Connectivity(true, false, false, true);
}
};
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
FOLDER + "noWifi.txt", account.name);
ocUpload.setUseWifiOnly(true);
UploadFileOperation newUpload = new UploadFileOperation(
uploadsStorageManager,
connectivityServiceMock,
powerManagementServiceMock,
user,
null,
ocUpload,
NameCollisionPolicy.DEFAULT,
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
targetContext,
true,
false,
getStorageManager()
);
newUpload.setRemoteFolderToBeCreated();
newUpload.addRenameUploadListener(() -> {
// dummy
});
RemoteOperationResult result = newUpload.execute(client);
assertFalse(result.toString(), result.isSuccess());
assertEquals(RemoteOperationResult.ResultCode.DELAYED_FOR_WIFI, result.getCode());
}
@Test
public void testUploadOnWifiOnlyAndWifi() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
FOLDER + "wifi.txt", account.name);
ocUpload.setWhileChargingOnly(true);
UploadFileOperation newUpload = new UploadFileOperation(
uploadsStorageManager,
connectivityServiceMock,
powerManagementServiceMock,
user,
null,
ocUpload,
NameCollisionPolicy.DEFAULT,
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
targetContext,
true,
false,
getStorageManager()
);
newUpload.setRemoteFolderToBeCreated();
newUpload.addRenameUploadListener(() -> {
// dummy
});
RemoteOperationResult result = newUpload.execute(client);
assertTrue(result.toString(), result.isSuccess());
// cleanup
new RemoveFileOperation(getStorageManager().getFileByDecryptedRemotePath(FOLDER),
false,
user,
false,
targetContext,
getStorageManager())
.execute(client);
}
@Test
public void testUploadOnWifiOnlyButMeteredWifi() {
ConnectivityService connectivityServiceMock = new ConnectivityService() {
@Override
public boolean isConnected() {
return false;
}
@Override
public boolean isInternetWalled() {
return false;
}
@Override
public Connectivity getConnectivity() {
return new Connectivity(true, true, true, true);
}
};
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
FOLDER + "noWifi.txt",
account.name);
ocUpload.setUseWifiOnly(true);
UploadFileOperation newUpload = new UploadFileOperation(
uploadsStorageManager,
connectivityServiceMock,
powerManagementServiceMock,
user,
null,
ocUpload,
NameCollisionPolicy.DEFAULT,
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
targetContext,
true,
false,
getStorageManager()
);
newUpload.setRemoteFolderToBeCreated();
newUpload.addRenameUploadListener(() -> {
// dummy
});
RemoteOperationResult result = newUpload.execute(client);
assertFalse(result.toString(), result.isSuccess());
assertEquals(RemoteOperationResult.ResultCode.DELAYED_FOR_WIFI, result.getCode());
}
@Test
public void testCreationAndUploadTimestamp() throws IOException, AccountUtils.AccountNotFoundException {
testOnlyOnServer(NextcloudVersion.nextcloud_27);
File file = getDummyFile("empty.txt");
String remotePath = "/testFile.txt";
OCUpload ocUpload = new OCUpload(file.getAbsolutePath(), remotePath, account.name);
assertTrue(
new UploadFileOperation(
uploadsStorageManager,
connectivityServiceMock,
powerManagementServiceMock,
user,
null,
ocUpload,
NameCollisionPolicy.DEFAULT,
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
targetContext,
false,
false,
getStorageManager()
)
.setRemoteFolderToBeCreated()
.execute(client)
.isSuccess()
);
long creationTimestamp = Files.readAttributes(file.toPath(), BasicFileAttributes.class)
.creationTime()
.to(TimeUnit.SECONDS);
long uploadTimestamp = System.currentTimeMillis() / 1000;
// RefreshFolderOperation
assertTrue(new RefreshFolderOperation(getStorageManager().getFileByDecryptedRemotePath("/"),
System.currentTimeMillis() / 1000,
false,
false,
getStorageManager(),
user,
targetContext).execute(client).isSuccess());
List<OCFile> files = getStorageManager().getFolderContent(getStorageManager().getFileByDecryptedRemotePath("/"),
false);
OCFile ocFile = files.get(0);
assertEquals(remotePath, ocFile.getRemotePath());
assertEquals(creationTimestamp, ocFile.getCreationTimestamp());
assertTrue(uploadTimestamp - 10 < ocFile.getUploadTimestamp() ||
uploadTimestamp + 10 > ocFile.getUploadTimestamp());
}
@Test
public void testMetadata() throws IOException, AccountUtils.AccountNotFoundException {
testOnlyOnServer(NextcloudVersion.nextcloud_27);
File file = getFile("gps.jpg");
String remotePath = "/metadata.jpg";
OCUpload ocUpload = new OCUpload(file.getAbsolutePath(), remotePath, account.name);
assertTrue(
new UploadFileOperation(
uploadsStorageManager,
connectivityServiceMock,
powerManagementServiceMock,
user,
null,
ocUpload,
NameCollisionPolicy.DEFAULT,
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
targetContext,
false,
false,
getStorageManager()
)
.setRemoteFolderToBeCreated()
.execute(client)
.isSuccess()
);
// RefreshFolderOperation
assertTrue(new RefreshFolderOperation(getStorageManager().getFileByDecryptedRemotePath("/"),
System.currentTimeMillis() / 1000,
false,
false,
getStorageManager(),
user,
targetContext).execute(client).isSuccess());
List<OCFile> files = getStorageManager().getFolderContent(getStorageManager().getFileByDecryptedRemotePath("/"),
false);
OCFile ocFile = null;
for (OCFile f : files) {
if (f.getFileName().equals("metadata.jpg")) {
ocFile = f;
break;
}
}
assertNotNull(ocFile);
assertEquals(remotePath, ocFile.getRemotePath());
assertEquals(new GeoLocation(64, -46), ocFile.getGeoLocation());
assertEquals(new ImageDimension(300f, 200f), ocFile.getImageDimension());
}
private void verifyStoragePath(OCFile file) {
assertEquals(FileStorageUtils.getSavePath(account.name) + FOLDER + file.getDecryptedFileName(),
file.getStoragePath());
}
}

View file

@ -0,0 +1,67 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.authentication
import android.graphics.Color
import org.junit.Assert
import org.junit.Test
class AuthenticatorActivityIT {
@Test(expected = IndexOutOfBoundsException::class)
fun testException() {
Color.parseColor("")
}
@Test
@Suppress("TooGenericExceptionCaught")
fun tryCatch() {
val color = try {
Color.parseColor("1")
} catch (e: Exception) {
Color.BLACK
}
Assert.assertNotNull(color)
}
@Test
@Suppress("TooGenericExceptionCaught")
fun tryCatch2() {
val color = try {
Color.parseColor("")
} catch (e: Exception) {
Color.BLACK
}
Assert.assertNotNull(color)
}
@Test
@Suppress("TooGenericExceptionCaught")
fun tryCatch3() {
val color = try {
Color.parseColor(null)
} catch (e: Exception) {
Color.BLACK
}
Assert.assertNotNull(color)
}
@Test
@Suppress("TooGenericExceptionCaught")
fun tryCatch4() {
val color = try {
Color.parseColor("abc")
} catch (e: Exception) {
Color.BLACK
}
Assert.assertNotNull(color)
}
}

View file

@ -0,0 +1,64 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.authentication
import androidx.test.core.app.launchActivity
import com.nextcloud.client.core.Clock
import com.nextcloud.client.preferences.AppPreferences
import com.nextcloud.test.TestActivity
import com.owncloud.android.ui.activity.SettingsActivity
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
/**
* This class should really be unit tests, but PassCodeManager needs a refactor
* to decouple the locking logic from the platform classes
*/
class PassCodeManagerIT {
@MockK
lateinit var appPreferences: AppPreferences
@MockK
lateinit var clockImpl: Clock
lateinit var sut: PassCodeManager
@Before
fun before() {
MockKAnnotations.init(this, relaxed = true)
sut = PassCodeManager(appPreferences, clockImpl)
}
@Test
fun testResumeDuplicateActivity() {
// set locked state
every { appPreferences.lockPreference } returns SettingsActivity.LOCK_PASSCODE
every { appPreferences.lockTimestamp } returns 200
every { clockImpl.millisSinceBoot } returns 10000
launchActivity<TestActivity>().use { scenario ->
scenario.onActivity { activity ->
// resume activity twice
var askedForPin = sut.onActivityResumed(activity)
assertTrue("Passcode not requested on first launch", askedForPin)
sut.onActivityResumed(activity)
// stop it once
sut.onActivityStopped(activity)
// resume again. should ask for passcode
askedForPin = sut.onActivityResumed(activity)
assertTrue("Passcode not requested on subsequent launch after stop", askedForPin)
}
}
}
}

View file

@ -0,0 +1,83 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.datamodel
import com.owncloud.android.AbstractIT
import org.junit.Assert.assertEquals
import org.junit.Test
class ArbitraryDataProviderIT : AbstractIT() {
@Test
fun testEmpty() {
val key = "DUMMY_KEY"
arbitraryDataProvider.storeOrUpdateKeyValue(user.accountName, key, "")
assertEquals("", arbitraryDataProvider.getValue(user.accountName, key))
}
@Test
fun testString() {
val key = "DUMMY_KEY"
var value = "123"
arbitraryDataProvider.storeOrUpdateKeyValue(user.accountName, key, value)
assertEquals(value, arbitraryDataProvider.getValue(user.accountName, key))
value = ""
arbitraryDataProvider.storeOrUpdateKeyValue(user.accountName, key, value)
assertEquals(value, arbitraryDataProvider.getValue(user.accountName, key))
value = "-1"
arbitraryDataProvider.storeOrUpdateKeyValue(user.accountName, key, value)
assertEquals(value, arbitraryDataProvider.getValue(user.accountName, key))
}
@Test
fun testBoolean() {
val key = "DUMMY_KEY"
var value = true
arbitraryDataProvider.storeOrUpdateKeyValue(user.accountName, key, value)
assertEquals(value, arbitraryDataProvider.getBooleanValue(user.accountName, key))
value = false
arbitraryDataProvider.storeOrUpdateKeyValue(user.accountName, key, value)
assertEquals(value, arbitraryDataProvider.getBooleanValue(user.accountName, key))
}
@Test
fun testInteger() {
val key = "DUMMY_KEY"
var value = 1
arbitraryDataProvider.storeOrUpdateKeyValue(user.accountName, key, value.toString())
assertEquals(value, arbitraryDataProvider.getIntegerValue(user.accountName, key))
value = -1
arbitraryDataProvider.storeOrUpdateKeyValue(user.accountName, key, value.toString())
assertEquals(value, arbitraryDataProvider.getIntegerValue(user.accountName, key))
}
@Test
fun testIncrement() {
val key = "INCREMENT"
// key does not exist
assertEquals(-1, arbitraryDataProvider.getIntegerValue(user.accountName, key))
// increment -> 1
arbitraryDataProvider.incrementValue(user.accountName, key)
assertEquals(1, arbitraryDataProvider.getIntegerValue(user.accountName, key))
// increment -> 2
arbitraryDataProvider.incrementValue(user.accountName, key)
assertEquals(2, arbitraryDataProvider.getIntegerValue(user.accountName, key))
// delete
arbitraryDataProvider.deleteKeyForAccount(user.accountName, key)
assertEquals(-1, arbitraryDataProvider.getIntegerValue(user.accountName, key))
}
}

View file

@ -0,0 +1,80 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2021 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.datamodel
import android.content.ContentResolver
import android.net.Uri
import android.os.Build
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.argThat
import org.mockito.kotlin.eq
import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class ContentResolverHelperIT {
companion object {
private val URI = Uri.parse("http://foo.bar")
private val PROJECTION = arrayOf("Foo")
private const val SELECTION = "selection"
private const val SORT_COLUMN = "sortColumn"
private const val SORT_DIRECTION = ContentResolverHelper.SORT_DIRECTION_ASCENDING
private const val SORT_DIRECTION_INT = ContentResolver.QUERY_SORT_DIRECTION_ASCENDING
private const val LIMIT = 10
}
@Mock
lateinit var resolver: ContentResolver
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
fun contentResolver_onAndroid26_usesNewAPI() {
ContentResolverHelper
.queryResolver(resolver, URI, PROJECTION, SELECTION, null, SORT_COLUMN, SORT_DIRECTION, LIMIT)
verify(resolver).query(
eq(URI),
eq(PROJECTION),
argThat { bundle ->
bundle.getString(ContentResolver.QUERY_ARG_SQL_SELECTION) == SELECTION &&
bundle.getInt(ContentResolver.QUERY_ARG_LIMIT) == LIMIT &&
bundle.getStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS)!!
.contentEquals(arrayOf(SORT_COLUMN)) &&
bundle.getInt(ContentResolver.QUERY_ARG_SORT_DIRECTION) == SORT_DIRECTION_INT
},
null
)
}
@Test
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.N_MR1)
fun contentResolver_onAndroidBelow26_usesOldAPI() {
ContentResolverHelper
.queryResolver(resolver, URI, PROJECTION, SELECTION, null, SORT_COLUMN, SORT_DIRECTION, LIMIT)
verify(resolver).query(
eq(URI),
eq(PROJECTION),
eq(SELECTION),
eq(null),
eq("$SORT_COLUMN $SORT_DIRECTION LIMIT $LIMIT"),
eq(null)
)
}
}

View file

@ -0,0 +1,47 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.datamodel;
import com.owncloud.android.db.ProviderMeta;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class FileDataStorageManagerContentProviderClientIT extends FileDataStorageManagerIT {
public void before() {
sut = new FileDataStorageManager(user,
targetContext
.getContentResolver()
.acquireContentProviderClient(ProviderMeta.ProviderTableMeta.CONTENT_URI)
);
super.before();
}
@Test
public void saveFile() {
String path = "/1.txt";
OCFile file = new OCFile(path);
file.setRemoteId("00000008ocjycgrudn78");
// TODO check via reflection that every parameter is set
file.setFileLength(1024000);
file.setModificationTimestamp(1582019340);
sut.saveNewFile(file);
OCFile read = sut.getFileByPath(path);
assertNotNull(read);
assertEquals(file.getRemotePath(), read.getRemotePath());
}
}

View file

@ -0,0 +1,56 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.datamodel
import org.junit.Assert
import org.junit.Test
class FileDataStorageManagerContentResolverIT : FileDataStorageManagerIT() {
companion object {
private const val MANY_FILES_AMOUNT = 5000
}
override fun before() {
sut = FileDataStorageManager(user, targetContext.contentResolver)
super.before()
}
/**
* only on FileDataStorageManager
*/
@Test
fun testFolderWithManyFiles() {
// create folder
val folderA = OCFile("/folderA/")
folderA.setFolder().parentId = sut.getFileByDecryptedRemotePath("/")!!.fileId
sut.saveFile(folderA)
Assert.assertTrue(sut.fileExists("/folderA/"))
Assert.assertEquals(0, sut.getFolderContent(folderA, false).size)
val folderAId = sut.getFileByDecryptedRemotePath("/folderA/")!!.fileId
// create files
val newFiles = (1..MANY_FILES_AMOUNT).map {
val file = OCFile("/folderA/file$it")
file.parentId = folderAId
sut.saveFile(file)
val storedFile = sut.getFileByDecryptedRemotePath("/folderA/file$it")
Assert.assertNotNull(storedFile)
storedFile
}
// save files in folder
sut.saveFolder(
folderA,
newFiles,
ArrayList()
)
// check file count is correct
Assert.assertEquals(MANY_FILES_AMOUNT, sut.getFolderContent(folderA, false).size)
}
}

View file

@ -0,0 +1,358 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.datamodel;
import android.content.ContentValues;
import com.owncloud.android.AbstractOnServerIT;
import com.owncloud.android.db.ProviderMeta;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation;
import com.owncloud.android.lib.resources.files.SearchRemoteOperation;
import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation;
import com.owncloud.android.lib.resources.files.model.RemoteFile;
import com.owncloud.android.lib.resources.status.CapabilityBooleanType;
import com.owncloud.android.lib.resources.status.GetCapabilitiesRemoteOperation;
import com.owncloud.android.lib.resources.status.OCCapability;
import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.utils.FileStorageUtils;
import junit.framework.TestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static com.owncloud.android.lib.resources.files.SearchRemoteOperation.SearchType.GALLERY_SEARCH;
import static com.owncloud.android.lib.resources.files.SearchRemoteOperation.SearchType.PHOTO_SEARCH;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
abstract public class FileDataStorageManagerIT extends AbstractOnServerIT {
protected FileDataStorageManager sut;
private OCCapability capability;
@Before
public void before() {
// make sure everything is removed
sut.deleteAllFiles();
sut.deleteVirtuals(VirtualFolderType.GALLERY);
assertEquals(0, sut.getAllFiles().size());
capability = (OCCapability) new GetCapabilitiesRemoteOperation(null)
.execute(client)
.getSingleData();
}
@After
public void after() {
super.after();
sut.deleteAllFiles();
sut.deleteVirtuals(VirtualFolderType.GALLERY);
}
@Test
public void simpleTest() {
OCFile file = sut.getFileByDecryptedRemotePath("/");
assertNotNull(file);
assertTrue(file.fileExists());
assertNull(sut.getFileByDecryptedRemotePath("/123123"));
}
@Test
public void getAllFiles_NoAvailable() {
assertEquals(0, sut.getAllFiles().size());
}
@Test
public void testFolderContent() throws IOException {
assertEquals(0, sut.getAllFiles().size());
assertTrue(new CreateFolderRemoteOperation("/1/1/", true).execute(client).isSuccess());
assertTrue(new CreateFolderRemoteOperation("/1/2/", true).execute(client).isSuccess());
assertTrue(new UploadFileRemoteOperation(getDummyFile("chunkedFile.txt").getAbsolutePath(),
"/1/1/chunkedFile.txt",
"text/plain",
System.currentTimeMillis() / 1000)
.execute(client).isSuccess());
assertTrue(new UploadFileRemoteOperation(getDummyFile("chunkedFile.txt").getAbsolutePath(),
"/1/1/chunkedFile2.txt",
"text/plain",
System.currentTimeMillis() / 1000)
.execute(client).isSuccess());
File imageFile = getFile("imageFile.png");
assertTrue(new UploadFileRemoteOperation(imageFile.getAbsolutePath(),
"/1/1/imageFile.png",
"image/png",
System.currentTimeMillis() / 1000)
.execute(client).isSuccess());
// sync
assertNull(sut.getFileByDecryptedRemotePath("/1/1/"));
assertTrue(new RefreshFolderOperation(sut.getFileByDecryptedRemotePath("/"),
System.currentTimeMillis() / 1000,
false,
false,
sut,
user,
targetContext).execute(client).isSuccess());
assertTrue(new RefreshFolderOperation(sut.getFileByDecryptedRemotePath("/1/"),
System.currentTimeMillis() / 1000,
false,
false,
sut,
user,
targetContext).execute(client).isSuccess());
assertTrue(new RefreshFolderOperation(sut.getFileByDecryptedRemotePath("/1/1/"),
System.currentTimeMillis() / 1000,
false,
false,
sut,
user,
targetContext).execute(client).isSuccess());
assertEquals(3, sut.getFolderContent(sut.getFileByDecryptedRemotePath("/1/1/"), false).size());
}
/**
* This test creates an image, does a photo search (now returned image is not yet in file hierarchy), then root
* folder is refreshed and it is verified that the same image file is used in database
*/
@Test
public void testPhotoSearch() throws IOException {
String remotePath = "/imageFile.png";
VirtualFolderType virtualType = VirtualFolderType.GALLERY;
assertEquals(0, sut.getFolderContent(sut.getFileByDecryptedRemotePath("/"), false).size());
assertEquals(1, sut.getAllFiles().size());
File imageFile = getFile("imageFile.png");
assertTrue(new UploadFileRemoteOperation(imageFile.getAbsolutePath(),
remotePath,
"image/png",
System.currentTimeMillis() / 1000)
.execute(client).isSuccess());
assertNull(sut.getFileByDecryptedRemotePath(remotePath));
// search
SearchRemoteOperation searchRemoteOperation = new SearchRemoteOperation("image/%",
PHOTO_SEARCH,
false,
capability);
RemoteOperationResult<List<RemoteFile>> searchResult = searchRemoteOperation.execute(client);
TestCase.assertTrue(searchResult.isSuccess());
TestCase.assertEquals(1, searchResult.getResultData().size());
OCFile ocFile = FileStorageUtils.fillOCFile(searchResult.getResultData().get(0));
sut.saveFile(ocFile);
List<ContentValues> contentValues = new ArrayList<>();
ContentValues cv = new ContentValues();
cv.put(ProviderMeta.ProviderTableMeta.VIRTUAL_TYPE, virtualType.toString());
cv.put(ProviderMeta.ProviderTableMeta.VIRTUAL_OCFILE_ID, ocFile.getFileId());
contentValues.add(cv);
sut.saveVirtuals(contentValues);
assertEquals(remotePath, ocFile.getRemotePath());
assertEquals(0, sut.getFolderContent(sut.getFileByDecryptedRemotePath("/"), false).size());
assertEquals(1, sut.getVirtualFolderContent(virtualType, false).size());
assertEquals(2, sut.getAllFiles().size());
// update root
assertTrue(new RefreshFolderOperation(sut.getFileByDecryptedRemotePath("/"),
System.currentTimeMillis() / 1000,
false,
false,
sut,
user,
targetContext).execute(client).isSuccess());
assertEquals(1, sut.getFolderContent(sut.getFileByDecryptedRemotePath("/"), false).size());
assertEquals(1, sut.getVirtualFolderContent(virtualType, false).size());
assertEquals(2, sut.getAllFiles().size());
assertEquals(sut.getVirtualFolderContent(virtualType, false).get(0),
sut.getFolderContent(sut.getFileByDecryptedRemotePath("/"), false).get(0));
}
/**
* This test creates an image and a video, does a gallery search (now returned image and video is not yet in file
* hierarchy), then root folder is refreshed and it is verified that the same image file is used in database
*/
@Test
public void testGallerySearch() throws IOException {
sut = new FileDataStorageManager(user,
targetContext
.getContentResolver()
.acquireContentProviderClient(ProviderMeta.ProviderTableMeta.CONTENT_URI)
);
String imagePath = "/imageFile.png";
VirtualFolderType virtualType = VirtualFolderType.GALLERY;
assertEquals(0, sut.getFolderContent(sut.getFileByDecryptedRemotePath("/"), false).size());
assertEquals(1, sut.getAllFiles().size());
File imageFile = getFile("imageFile.png");
assertTrue(new UploadFileRemoteOperation(imageFile.getAbsolutePath(),
imagePath,
"image/png",
(System.currentTimeMillis() - 10000) / 1000)
.execute(client).isSuccess());
// Check that file does not yet exist in local database
assertNull(sut.getFileByDecryptedRemotePath(imagePath));
String videoPath = "/videoFile.mp4";
File videoFile = getFile("videoFile.mp4");
assertTrue(new UploadFileRemoteOperation(videoFile.getAbsolutePath(),
videoPath,
"video/mpeg",
(System.currentTimeMillis() + 10000) / 1000)
.execute(client).isSuccess());
// Check that file does not yet exist in local database
assertNull(sut.getFileByDecryptedRemotePath(videoPath));
// search
SearchRemoteOperation searchRemoteOperation = new SearchRemoteOperation("",
GALLERY_SEARCH,
false,
capability);
RemoteOperationResult<List<RemoteFile>> searchResult = searchRemoteOperation.execute(client);
TestCase.assertTrue(searchResult.isSuccess());
TestCase.assertEquals(2, searchResult.getResultData().size());
// newest file must be video path (as sorted by recently modified)
OCFile ocFile = FileStorageUtils.fillOCFile( searchResult.getResultData().get(0));
sut.saveFile(ocFile);
assertEquals(videoPath, ocFile.getRemotePath());
List<ContentValues> contentValues = new ArrayList<>();
ContentValues cv = new ContentValues();
cv.put(ProviderMeta.ProviderTableMeta.VIRTUAL_TYPE, virtualType.toString());
cv.put(ProviderMeta.ProviderTableMeta.VIRTUAL_OCFILE_ID, ocFile.getFileId());
contentValues.add(cv);
// second is image file, as older
OCFile ocFile2 = FileStorageUtils.fillOCFile(searchResult.getResultData().get(1));
sut.saveFile(ocFile2);
assertEquals(imagePath, ocFile2.getRemotePath());
ContentValues cv2 = new ContentValues();
cv2.put(ProviderMeta.ProviderTableMeta.VIRTUAL_TYPE, virtualType.toString());
cv2.put(ProviderMeta.ProviderTableMeta.VIRTUAL_OCFILE_ID, ocFile2.getFileId());
contentValues.add(cv2);
sut.saveVirtuals(contentValues);
assertEquals(0, sut.getFolderContent(sut.getFileByDecryptedRemotePath("/"), false).size());
assertEquals(2, sut.getVirtualFolderContent(virtualType, false).size());
assertEquals(3, sut.getAllFiles().size());
// update root
assertTrue(new RefreshFolderOperation(sut.getFileByDecryptedRemotePath("/"),
System.currentTimeMillis() / 1000,
false,
false,
sut,
user,
targetContext).execute(client).isSuccess());
assertEquals(2, sut.getFolderContent(sut.getFileByDecryptedRemotePath("/"), false).size());
assertEquals(2, sut.getVirtualFolderContent(virtualType, false).size());
assertEquals(3, sut.getAllFiles().size());
assertEquals(sut.getVirtualFolderContent(virtualType, false).get(0),
sut.getFolderContent(sut.getFileByDecryptedRemotePath("/"), false).get(0));
}
@Test
public void testSaveNewFile() {
assertTrue(new CreateFolderRemoteOperation("/1/1/", true).execute(client).isSuccess());
assertTrue(new RefreshFolderOperation(sut.getFileByDecryptedRemotePath("/"),
System.currentTimeMillis() / 1000,
false,
false,
sut,
user,
targetContext).execute(client).isSuccess());
assertTrue(new RefreshFolderOperation(sut.getFileByDecryptedRemotePath("/1/"),
System.currentTimeMillis() / 1000,
false,
false,
sut,
user,
targetContext).execute(client).isSuccess());
assertTrue(new RefreshFolderOperation(sut.getFileByDecryptedRemotePath("/1/1/"),
System.currentTimeMillis() / 1000,
false,
false,
sut,
user,
targetContext).execute(client).isSuccess());
OCFile newFile = new OCFile("/1/1/1.txt");
newFile.setRemoteId("12345678");
sut.saveNewFile(newFile);
}
@Test(expected = IllegalArgumentException.class)
public void testSaveNewFile_NonExistingParent() {
assertTrue(new CreateFolderRemoteOperation("/1/1/", true).execute(client).isSuccess());
OCFile newFile = new OCFile("/1/1/1.txt");
sut.saveNewFile(newFile);
}
@Test
public void testOCCapability() {
OCCapability capability = new OCCapability();
capability.setUserStatus(CapabilityBooleanType.TRUE);
sut.saveCapabilities(capability);
OCCapability newCapability = sut.getCapability(user);
assertEquals(capability.getUserStatus(), newCapability.getUserStatus());
}
}

View file

@ -0,0 +1,36 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.datamodel
import com.owncloud.android.AbstractIT
import com.owncloud.android.lib.resources.status.CapabilityBooleanType
import com.owncloud.android.lib.resources.status.OCCapability
import org.junit.Assert.assertEquals
import org.junit.Test
class OCCapabilityIT : AbstractIT() {
@Test
fun saveCapability() {
val fileDataStorageManager = FileDataStorageManager(user, targetContext.contentResolver)
val capability = OCCapability()
capability.etag = "123"
capability.userStatus = CapabilityBooleanType.TRUE
capability.userStatusSupportsEmoji = CapabilityBooleanType.TRUE
capability.dropAccount = CapabilityBooleanType.TRUE
fileDataStorageManager.saveCapabilities(capability)
val newCapability = fileDataStorageManager.getCapability(user.accountName)
assertEquals(capability.etag, newCapability.etag)
assertEquals(capability.userStatus, newCapability.userStatus)
assertEquals(capability.userStatusSupportsEmoji, newCapability.userStatusSupportsEmoji)
assertEquals(capability.dropAccount, newCapability.dropAccount)
}
}

View file

@ -0,0 +1,91 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Alper Ozturk <alper_ozturk@proton.me>
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.datamodel
import com.owncloud.android.R
import com.owncloud.android.lib.common.network.WebdavEntry.MountType
import org.junit.After
import org.junit.Before
import org.junit.Test
class OCFileIconTests {
private val path = "/path/to/a/file.txt"
private var sut: OCFile? = null
@Before
fun setup() {
sut = OCFile(path)
}
@Test
fun testGetFileOverlayIconWhenFileIsAutoUploadFolderShouldReturnFolderOverlayUploadIcon() {
val fileOverlayIcon = sut?.getFileOverlayIconId(true)
val expectedDrawable = R.drawable.ic_folder_overlay_upload
assert(fileOverlayIcon == expectedDrawable)
}
@Test
fun testGetFileOverlayIconWhenFileIsEncryptedShouldReturnFolderOverlayKeyIcon() {
sut?.isEncrypted = true
val fileOverlayIcon = sut?.getFileOverlayIconId(false)
val expectedDrawable = R.drawable.ic_folder_overlay_key
assert(fileOverlayIcon == expectedDrawable)
}
@Test
fun testGetFileOverlayIconWhenFileIsGroupFolderShouldReturnFolderOverlayAccountGroupIcon() {
sut?.mountType = MountType.GROUP
val fileOverlayIcon = sut?.getFileOverlayIconId(false)
val expectedDrawable = R.drawable.ic_folder_overlay_account_group
assert(fileOverlayIcon == expectedDrawable)
}
@Test
fun testGetFileOverlayIconWhenFileIsSharedViaLinkShouldReturnFolderOverlayLinkIcon() {
sut?.isSharedViaLink = true
val fileOverlayIcon = sut?.getFileOverlayIconId(false)
val expectedDrawable = R.drawable.ic_folder_overlay_link
assert(fileOverlayIcon == expectedDrawable)
}
@Test
fun testGetFileOverlayIconWhenFileIsSharedShouldReturnFolderOverlayShareIcon() {
sut?.isSharedWithSharee = true
val fileOverlayIcon = sut?.getFileOverlayIconId(false)
val expectedDrawable = R.drawable.ic_folder_overlay_share
assert(fileOverlayIcon == expectedDrawable)
}
@Test
fun testGetFileOverlayIconWhenFileIsExternalShouldReturnFolderOverlayExternalIcon() {
sut?.mountType = MountType.EXTERNAL
val fileOverlayIcon = sut?.getFileOverlayIconId(false)
val expectedDrawable = R.drawable.ic_folder_overlay_external
assert(fileOverlayIcon == expectedDrawable)
}
@Test
fun testGetFileOverlayIconWhenFileIsLockedShouldReturnFolderOverlayLockIcon() {
sut?.isLocked = true
val fileOverlayIcon = sut?.getFileOverlayIconId(false)
val expectedDrawable = R.drawable.ic_folder_overlay_lock
assert(fileOverlayIcon == expectedDrawable)
}
@Test
fun testGetFileOverlayIconWhenFileIsFolderShouldReturnNull() {
val fileOverlayIcon = sut?.getFileOverlayIconId(false)
assert(fileOverlayIcon == null)
}
@After
fun destroy() {
sut = null
}
}

View file

@ -0,0 +1,113 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2016 David A. Velasco
* SPDX-FileCopyrightText: 2016 2016 ownCloud Inc
* SPDX-License-Identifier: GPL-2.0-only
*/
package com.owncloud.android.datamodel;
import android.os.Parcel;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* Instrumented unit test, to be run in an Android emulator or device.
* At the moment, it's a sample to validate the automatic test environment, in the scope of instrumented unit tests.
* Don't take it as an example of completeness.
* See http://developer.android.com/intl/es/training/testing/unit-testing/instrumented-unit-tests.html .
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
public class OCFileUnitTest {
private final static String PATH = "/path/to/a/file.txt";
private static final long ID = 12345L;
private static final long PARENT_ID = 567890L;
private static final String STORAGE_PATH = "/mnt/sd/localpath/to/a/file.txt";
private static final String MIME_TYPE = "text/plain";
private static final long FILE_LENGTH = 9876543210L;
private static final long CREATION_TIMESTAMP = 8765432109L;
private static final long MODIFICATION_TIMESTAMP = 7654321098L;
private static final long MODIFICATION_TIMESTAMP_AT_LAST_SYNC_FOR_DATA = 6543210987L;
private static final long LAST_SYNC_DATE_FOR_PROPERTIES = 5432109876L;
private static final long LAST_SYNC_DATE_FOR_DATA = 4321098765L;
private static final String ETAG = "adshfas98ferqw8f9yu2";
private static final String PUBLIC_LINK = "https://nextcloud.localhost/owncloud/987427448712984sdas29";
private static final String PERMISSIONS = "SRKNVD";
private static final String REMOTE_ID = "jadñgiadf8203:9jrp98v2mn3er2089fh";
private static final String ETAG_IN_CONFLICT = "2adshfas98ferqw8f9yu";
private OCFile mFile;
@Before
public void createDefaultOCFile() {
mFile = new OCFile(PATH);
}
@Test
public void writeThenReadAsParcelable() {
// Set up mFile with not-default values
mFile.setFileId(ID);
mFile.setParentId(PARENT_ID);
mFile.setStoragePath(STORAGE_PATH);
mFile.setMimeType(MIME_TYPE);
mFile.setFileLength(FILE_LENGTH);
mFile.setCreationTimestamp(CREATION_TIMESTAMP);
mFile.setModificationTimestamp(MODIFICATION_TIMESTAMP);
mFile.setModificationTimestampAtLastSyncForData(MODIFICATION_TIMESTAMP_AT_LAST_SYNC_FOR_DATA);
mFile.setLastSyncDateForProperties(LAST_SYNC_DATE_FOR_PROPERTIES);
mFile.setLastSyncDateForData(LAST_SYNC_DATE_FOR_DATA);
mFile.setEtag(ETAG);
mFile.setSharedViaLink(true);
mFile.setSharedWithSharee(true);
mFile.setPermissions(PERMISSIONS);
mFile.setRemoteId(REMOTE_ID);
mFile.setUpdateThumbnailNeeded(true);
mFile.setDownloading(true);
mFile.setEtagInConflict(ETAG_IN_CONFLICT);
// Write the file data in a Parcel
Parcel parcel = Parcel.obtain();
mFile.writeToParcel(parcel, mFile.describeContents());
// Read the data from the parcel
parcel.setDataPosition(0);
OCFile fileReadFromParcel = OCFile.CREATOR.createFromParcel(parcel);
// Verify that the received data are correct
assertThat(fileReadFromParcel.getRemotePath(), is(PATH));
assertThat(fileReadFromParcel.getFileId(), is(ID));
assertThat(fileReadFromParcel.getParentId(), is(PARENT_ID));
assertThat(fileReadFromParcel.getStoragePath(), is(STORAGE_PATH));
assertThat(fileReadFromParcel.getMimeType(), is(MIME_TYPE));
assertThat(fileReadFromParcel.getFileLength(), is(FILE_LENGTH));
assertThat(fileReadFromParcel.getCreationTimestamp(), is(CREATION_TIMESTAMP));
assertThat(fileReadFromParcel.getModificationTimestamp(), is(MODIFICATION_TIMESTAMP));
assertThat(
fileReadFromParcel.getModificationTimestampAtLastSyncForData(),
is(MODIFICATION_TIMESTAMP_AT_LAST_SYNC_FOR_DATA)
);
assertThat(fileReadFromParcel.getLastSyncDateForProperties(), is(LAST_SYNC_DATE_FOR_PROPERTIES));
assertThat(fileReadFromParcel.getLastSyncDateForData(), is(LAST_SYNC_DATE_FOR_DATA));
assertThat(fileReadFromParcel.getEtag(), is(ETAG));
assertThat(fileReadFromParcel.isSharedViaLink(), is(true));
assertThat(fileReadFromParcel.isSharedWithSharee(), is(true));
assertThat(fileReadFromParcel.getPermissions(), is(PERMISSIONS));
assertThat(fileReadFromParcel.getRemoteId(), is(REMOTE_ID));
assertThat(fileReadFromParcel.isUpdateThumbnailNeeded(), is(true));
assertThat(fileReadFromParcel.isDownloading(), is(true));
assertThat(fileReadFromParcel.getEtagInConflict(), is(ETAG_IN_CONFLICT));
}
}

View file

@ -0,0 +1,229 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2017 JARP <jarp@customer-187-174-218-184.uninet-ide.com.mx
* SPDX-FileCopyrightText: 2019 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2021 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.datamodel;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
import com.nextcloud.client.account.CurrentAccountProvider;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.account.UserAccountManagerImpl;
import com.nextcloud.test.RandomStringGenerator;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.MainApp;
import com.owncloud.android.db.OCUpload;
import com.owncloud.android.db.UploadResult;
import com.owncloud.android.files.services.NameCollisionPolicy;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.operations.UploadFileOperation;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.ArrayList;
import java.util.Random;
import java.util.UUID;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* Created by JARP on 6/7/17.
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
public class UploadStorageManagerTest extends AbstractIT {
private UploadsStorageManager uploadsStorageManager;
private CurrentAccountProvider currentAccountProvider = () -> null;
private UserAccountManager userAccountManager;
private User user2;
@Before
public void setUp() {
Context instrumentationCtx = ApplicationProvider.getApplicationContext();
ContentResolver contentResolver = instrumentationCtx.getContentResolver();
uploadsStorageManager = new UploadsStorageManager(currentAccountProvider, contentResolver);
userAccountManager = UserAccountManagerImpl.fromContext(targetContext);
Account temp = new Account("test2@test.com", MainApp.getAccountType(targetContext));
if (!userAccountManager.exists(temp)) {
AccountManager platformAccountManager = AccountManager.get(targetContext);
platformAccountManager.addAccountExplicitly(temp, "testPassword", null);
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_ACCOUNT_VERSION,
Integer.toString(UserAccountManager.ACCOUNT_VERSION));
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_VERSION, "14.0.0.0");
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_BASE_URL, "test.com");
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_USER_ID, "test"); // same as userId
}
final UserAccountManager userAccountManager = UserAccountManagerImpl.fromContext(targetContext);
user2 = userAccountManager.getUser("test2@test.com").orElseThrow(ActivityNotFoundException::new);
}
@Test
public void testDeleteAllUploads() {
// Clean
for (User user : userAccountManager.getAllUsers()) {
uploadsStorageManager.removeUserUploads(user);
}
int accountRowsA = 3;
int accountRowsB = 4;
insertUploads(account, accountRowsA);
insertUploads(user2.toPlatformAccount(), accountRowsB);
assertEquals("Expected 4 removed uploads files",
4,
uploadsStorageManager.removeUserUploads(user2));
}
@Test
public void largeTest() {
int size = 3000;
ArrayList<OCUpload> uploads = new ArrayList<>();
deleteAllUploads();
assertEquals(0, uploadsStorageManager.getAllStoredUploads().length);
for (int i = 0; i < size; i++) {
OCUpload upload = createUpload(account);
uploads.add(upload);
uploadsStorageManager.storeUpload(upload);
}
OCUpload[] storedUploads = uploadsStorageManager.getAllStoredUploads();
assertEquals(size, uploadsStorageManager.getAllStoredUploads().length);
for (int i = 0; i < size; i++) {
assertTrue(contains(uploads, storedUploads[i]));
}
}
@Test
public void testIsSame() {
OCUpload upload1 = new OCUpload("/test", "/test", account.name);
upload1.setUseWifiOnly(true);
OCUpload upload2 = new OCUpload("/test", "/test", account.name);
upload2.setUseWifiOnly(true);
assertTrue(upload1.isSame(upload2));
upload2.setUseWifiOnly(false);
assertFalse(upload1.isSame(upload2));
assertFalse(upload1.isSame(null));
assertFalse(upload1.isSame(new OCFile("/test")));
}
private boolean contains(ArrayList<OCUpload> uploads, OCUpload storedUpload) {
for (int i = 0; i < uploads.size(); i++) {
if (storedUpload.isSame(uploads.get(i))) {
return true;
}
}
return false;
}
@Test(expected = IllegalArgumentException.class)
public void corruptedUpload() {
OCUpload corruptUpload = new OCUpload(File.separator + "LocalPath",
OCFile.PATH_SEPARATOR + "RemotePath",
account.name);
corruptUpload.setLocalPath(null);
uploadsStorageManager.storeUpload(corruptUpload);
uploadsStorageManager.getAllStoredUploads();
}
@Test
public void getById() {
OCUpload upload = createUpload(account);
long id = uploadsStorageManager.storeUpload(upload);
OCUpload newUpload = uploadsStorageManager.getUploadById(id);
assertNotNull(newUpload);
assertEquals(upload.getLocalAction(), newUpload.getLocalAction());
assertEquals(upload.getFolderUnlockToken(), newUpload.getFolderUnlockToken());
}
@Test
public void getByIdNull() {
OCUpload newUpload = uploadsStorageManager.getUploadById(-1);
assertNull(newUpload);
}
private void insertUploads(Account account, int rowsToInsert) {
for (int i = 0; i < rowsToInsert; i++) {
uploadsStorageManager.storeUpload(createUpload(account));
}
}
public String generateUniqueNumber() {
UUID uuid = UUID.randomUUID();
return uuid.toString();
}
private OCUpload createUpload(Account account) {
OCUpload upload = new OCUpload(File.separator + "very long long long long long long long long long long long " +
"long long long long long long long long long long long long long long " +
"long long long long long long long long long long long long long long " +
"long long long long long long long LocalPath " +
generateUniqueNumber(),
OCFile.PATH_SEPARATOR + "very long long long long long long long long long " +
"long long long long long long long long long long long long long long " +
"long long long long long long long long long long long long long long " +
"long long long long long long long long long long long long RemotePath " +
generateUniqueNumber(),
account.name);
upload.setFileSize(new Random().nextInt(20000) * 10000);
upload.setUploadStatus(UploadsStorageManager.UploadStatus.UPLOAD_IN_PROGRESS);
upload.setLocalAction(2);
upload.setNameCollisionPolicy(NameCollisionPolicy.ASK_USER);
upload.setCreateRemoteFolder(false);
upload.setUploadEndTimestamp(System.currentTimeMillis());
upload.setLastResult(UploadResult.DELAYED_FOR_WIFI);
upload.setCreatedBy(UploadFileOperation.CREATED_BY_USER);
upload.setUseWifiOnly(true);
upload.setWhileChargingOnly(false);
upload.setFolderUnlockToken(RandomStringGenerator.make(10));
return upload;
}
private void deleteAllUploads() {
uploadsStorageManager.removeAllUploads();
assertEquals(0, uploadsStorageManager.getAllStoredUploads().length);
}
@After
public void tearDown() {
deleteAllUploads();
userAccountManager.removeUser(user2);
}
}

View file

@ -0,0 +1,370 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Álvaro Brey Vilas
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.owncloud.android.files
import androidx.test.core.app.launchActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.nextcloud.client.account.User
import com.nextcloud.client.jobs.download.FileDownloadWorker
import com.nextcloud.client.jobs.upload.FileUploadHelper
import com.nextcloud.test.TestActivity
import com.nextcloud.utils.EditorUtils
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import com.owncloud.android.datamodel.ArbitraryDataProvider
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.resources.files.model.FileLockType
import com.owncloud.android.lib.resources.status.CapabilityBooleanType
import com.owncloud.android.lib.resources.status.OCCapability
import com.owncloud.android.services.OperationsService
import com.owncloud.android.ui.activity.ComponentsGetter
import com.owncloud.android.utils.MimeType
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.security.SecureRandom
@RunWith(AndroidJUnit4::class)
class FileMenuFilterIT : AbstractIT() {
@MockK
private lateinit var mockComponentsGetter: ComponentsGetter
@MockK
private lateinit var mockStorageManager: FileDataStorageManager
@MockK
private lateinit var mockFileUploaderBinder: FileUploadHelper
@MockK
private lateinit var mockFileDownloadProgressListener: FileDownloadWorker.FileDownloadProgressListener
@MockK
private lateinit var mockOperationsServiceBinder: OperationsService.OperationsServiceBinder
@MockK
private lateinit var mockArbitraryDataProvider: ArbitraryDataProvider
private lateinit var editorUtils: EditorUtils
@Before
fun setup() {
MockKAnnotations.init(this)
every { mockFileUploaderBinder.isUploading(any(), any()) } returns false
every { mockComponentsGetter.fileUploaderHelper } returns mockFileUploaderBinder
every { mockFileDownloadProgressListener.isDownloading(any(), any()) } returns false
every { mockComponentsGetter.fileDownloadProgressListener } returns mockFileDownloadProgressListener
every { mockOperationsServiceBinder.isSynchronizing(any(), any()) } returns false
every { mockComponentsGetter.operationsServiceBinder } returns mockOperationsServiceBinder
every { mockStorageManager.getFileById(any()) } returns OCFile("/")
every { mockStorageManager.getFolderContent(any(), any()) } returns ArrayList<OCFile>()
every { mockArbitraryDataProvider.getValue(any<User>(), any()) } returns ""
editorUtils = EditorUtils(mockArbitraryDataProvider)
}
@Test
fun filter_noLockingCapability_lockItemsInvisible() {
val capability = OCCapability().apply {
endToEndEncryption = CapabilityBooleanType.UNKNOWN
}
val file = OCFile("/foo.md")
testLockingVisibilities(
capability,
file,
ExpectedLockVisibilities(lockFile = false, unlockFile = false)
)
}
@Test
fun filter_lockingCapability_fileUnlocked_lockVisible() {
val capability = OCCapability().apply {
endToEndEncryption = CapabilityBooleanType.UNKNOWN
filesLockingVersion = "1.0"
}
val file = OCFile("/foo.md")
testLockingVisibilities(
capability,
file,
ExpectedLockVisibilities(lockFile = true, unlockFile = false)
)
}
@Test
fun filter_lockingCapability_fileLocked_lockedByAndProps() {
val capability = OCCapability().apply {
endToEndEncryption = CapabilityBooleanType.UNKNOWN
filesLockingVersion = "1.0"
}
val file = OCFile("/foo.md").apply {
isLocked = true
lockType = FileLockType.MANUAL
lockOwnerId = user.accountName.split("@")[0]
lockOwnerDisplayName = "TEST"
lockTimestamp = 1000 // irrelevant
lockTimeout = 1000 // irrelevant
}
testLockingVisibilities(
capability,
file,
ExpectedLockVisibilities(lockFile = false, unlockFile = true)
)
}
@Test
fun filter_lockingCapability_fileLockedByOthers_lockedByAndProps() {
val capability = OCCapability().apply {
endToEndEncryption = CapabilityBooleanType.UNKNOWN
filesLockingVersion = "1.0"
}
val file = OCFile("/foo.md").apply {
isLocked = true
lockType = FileLockType.MANUAL
lockOwnerId = "A_DIFFERENT_USER"
lockOwnerDisplayName = "A_DIFFERENT_USER"
lockTimestamp = 1000 // irrelevant
lockTimeout = 1000 // irrelevant
}
testLockingVisibilities(
capability,
file,
ExpectedLockVisibilities(lockFile = false, unlockFile = false)
)
}
@Test
fun filter_unset_encryption() {
val capability = OCCapability().apply {
endToEndEncryption = CapabilityBooleanType.TRUE
}
val encryptedFolder = OCFile("/encryptedFolder/").apply {
isEncrypted = true
mimeType = MimeType.DIRECTORY
fileLength = SecureRandom().nextLong()
}
val encryptedEmptyFolder = OCFile("/encryptedFolder/").apply {
isEncrypted = true
mimeType = MimeType.DIRECTORY
}
val normalFolder = OCFile("/folder/").apply {
mimeType = MimeType.DIRECTORY
fileLength = SecureRandom().nextLong()
}
val normalEmptyFolder = OCFile("/folder/").apply {
mimeType = MimeType.DIRECTORY
}
configureCapability(capability)
launchActivity<TestActivity>().use {
it.onActivity { activity ->
val filterFactory =
FileMenuFilter.Factory(mockStorageManager, activity, editorUtils)
var sut = filterFactory.newInstance(encryptedFolder, mockComponentsGetter, true, user)
var toHide = sut.getToHide(false)
// encrypted folder, with content
assertTrue(toHide.contains(R.id.action_unset_encrypted))
assertTrue(toHide.contains(R.id.action_encrypted))
assertTrue(toHide.contains(R.id.action_remove_file))
// encrypted, but empty folder
sut = filterFactory.newInstance(encryptedEmptyFolder, mockComponentsGetter, true, user)
toHide = sut.getToHide(false)
assertTrue(toHide.contains(R.id.action_unset_encrypted))
assertTrue(toHide.contains(R.id.action_remove_file))
assertTrue(toHide.contains(R.id.action_encrypted))
// regular folder, with content
sut = filterFactory.newInstance(normalFolder, mockComponentsGetter, true, user)
toHide = sut.getToHide(false)
assertTrue(toHide.contains(R.id.action_unset_encrypted))
assertTrue(toHide.contains(R.id.action_encrypted))
assertFalse(toHide.contains(R.id.action_remove_file))
// regular folder, without content
sut = filterFactory.newInstance(normalEmptyFolder, mockComponentsGetter, true, user)
toHide = sut.getToHide(false)
assertTrue(toHide.contains(R.id.action_unset_encrypted))
assertFalse(toHide.contains(R.id.action_encrypted))
assertFalse(toHide.contains(R.id.action_remove_file))
}
}
}
@Test
fun filter_stream() {
val capability = OCCapability().apply {
endToEndEncryption = CapabilityBooleanType.TRUE
}
val encryptedVideo = OCFile("/e2e/1.mpg").apply {
isEncrypted = true
mimeType = "video/mpeg"
}
val normalVideo = OCFile("/folder/2.mpg").apply {
mimeType = "video/mpeg"
fileLength = SecureRandom().nextLong()
}
configureCapability(capability)
launchActivity<TestActivity>().use {
it.onActivity { activity ->
val filterFactory =
FileMenuFilter.Factory(mockStorageManager, activity, editorUtils)
var sut = filterFactory.newInstance(encryptedVideo, mockComponentsGetter, true, user)
var toHide = sut.getToHide(false)
// encrypted video, with content
assertTrue(toHide.contains(R.id.action_stream_media))
// regular video, with content
sut = filterFactory.newInstance(normalVideo, mockComponentsGetter, true, user)
toHide = sut.getToHide(false)
assertFalse(toHide.contains(R.id.action_stream_media))
}
}
}
@Test
fun filter_select_all() {
configureCapability(OCCapability())
// not in single file fragment -> multi selection is possible under certain circumstances
launchActivity<TestActivity>().use {
it.onActivity { activity ->
val filterFactory = FileMenuFilter.Factory(mockStorageManager, activity, editorUtils)
val files = listOf(OCFile("/foo.bin"), OCFile("/bar.bin"), OCFile("/baz.bin"))
// single file, not in multi selection
// *Select all* and *Deselect all* should stay hidden
var sut = filterFactory.newInstance(files.first(), mockComponentsGetter, true, user)
var toHide = sut.getToHide(false)
assertTrue(toHide.contains(R.id.action_select_all_action_menu))
assertTrue(toHide.contains(R.id.action_deselect_all_action_menu))
// multiple files, all selected in multi selection
// *Deselect all* shown, *Select all* not
sut = filterFactory.newInstance(files.size, files, mockComponentsGetter, false, user)
toHide = sut.getToHide(false)
assertTrue(toHide.contains(R.id.action_select_all_action_menu))
assertFalse(toHide.contains(R.id.action_deselect_all_action_menu))
// multiple files, all but one selected
// both *Select all* and *Deselect all* should be shown
sut = filterFactory.newInstance(files.size + 1, files, mockComponentsGetter, false, user)
toHide = sut.getToHide(false)
assertFalse(toHide.contains(R.id.action_select_all_action_menu))
assertFalse(toHide.contains(R.id.action_deselect_all_action_menu))
}
}
}
fun filter_select_all_singleFileFragment() {
configureCapability(OCCapability())
// in single file fragment (e.g. FileDetailFragment or PreviewImageFragment), selecting multiple files
// is not possible -> *Select all* and *Deselect all* options should be hidden
launchActivity<TestActivity>().use {
it.onActivity { activity ->
val filterFactory = FileMenuFilter.Factory(mockStorageManager, activity, editorUtils)
val files = listOf(OCFile("/foo.bin"), OCFile("/bar.bin"), OCFile("/baz.bin"))
// single file
var sut = filterFactory.newInstance(files.first(), mockComponentsGetter, true, user)
var toHide = sut.getToHide(true)
assertTrue(toHide.contains(R.id.action_select_all_action_menu))
assertTrue(toHide.contains(R.id.action_deselect_all_action_menu))
// multiple files, all selected
sut = filterFactory.newInstance(files.size, files, mockComponentsGetter, false, user)
toHide = sut.getToHide(true)
assertTrue(toHide.contains(R.id.action_select_all_action_menu))
assertTrue(toHide.contains(R.id.action_deselect_all_action_menu))
// multiple files, all but one selected
sut = filterFactory.newInstance(files.size + 1, files, mockComponentsGetter, false, user)
toHide = sut.getToHide(true)
assertTrue(toHide.contains(R.id.action_select_all_action_menu))
assertTrue(toHide.contains(R.id.action_deselect_all_action_menu))
}
}
}
private data class ExpectedLockVisibilities(
val lockFile: Boolean,
val unlockFile: Boolean
)
private fun configureCapability(capability: OCCapability) {
every { mockStorageManager.getCapability(any<User>()) } returns capability
every { mockStorageManager.getCapability(any<String>()) } returns capability
}
private fun testLockingVisibilities(
capability: OCCapability,
file: OCFile,
expectedLockVisibilities: ExpectedLockVisibilities
) {
configureCapability(capability)
launchActivity<TestActivity>().use {
it.onActivity { activity ->
val filterFactory =
FileMenuFilter.Factory(mockStorageManager, activity, editorUtils)
val sut = filterFactory.newInstance(file, mockComponentsGetter, true, user)
val toHide = sut.getToHide(false)
assertEquals(
expectedLockVisibilities.lockFile,
!toHide.contains(R.id.action_lock_file)
)
assertEquals(
expectedLockVisibilities.unlockFile,
!toHide.contains(R.id.action_unlock_file)
)
}
}
}
}

View file

@ -0,0 +1,493 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.files.services
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.account.UserAccountManagerImpl
import com.nextcloud.client.device.BatteryStatus
import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.jobs.upload.FileUploadHelper
import com.nextcloud.client.jobs.upload.FileUploadWorker
import com.nextcloud.client.network.Connectivity
import com.nextcloud.client.network.ConnectivityService
import com.owncloud.android.AbstractOnServerIT
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.UploadsStorageManager
import com.owncloud.android.db.OCUpload
import com.owncloud.android.lib.common.operations.OperationCancelledException
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation
import com.owncloud.android.lib.resources.files.model.RemoteFile
import com.owncloud.android.operations.UploadFileOperation
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
abstract class FileUploaderIT : AbstractOnServerIT() {
private var uploadsStorageManager: UploadsStorageManager? = null
private val connectivityServiceMock: ConnectivityService = object : ConnectivityService {
override fun isConnected(): Boolean {
return false
}
override fun isInternetWalled(): Boolean = false
override fun getConnectivity(): Connectivity = Connectivity.CONNECTED_WIFI
}
private val powerManagementServiceMock: PowerManagementService = object : PowerManagementService {
override val isPowerSavingEnabled: Boolean
get() = false
override val isPowerSavingExclusionAvailable: Boolean
get() = false
override val battery: BatteryStatus
get() = BatteryStatus()
}
@Before
fun setUp() {
val contentResolver = targetContext.contentResolver
val accountManager: UserAccountManager = UserAccountManagerImpl.fromContext(targetContext)
uploadsStorageManager = UploadsStorageManager(accountManager, contentResolver)
}
/**
* uploads a file, overwrites it with an empty one, check if overwritten
*/
// disabled, flaky test
// @Test
// fun testKeepLocalAndOverwriteRemote() {
// val file = getDummyFile("chunkedFile.txt")
// val ocUpload = OCUpload(file.absolutePath, "/testFile.txt", account.name)
//
// assertTrue(
// UploadFileOperation(
// uploadsStorageManager,
// connectivityServiceMock,
// powerManagementServiceMock,
// user,
// null,
// ocUpload,
// FileUploader.NameCollisionPolicy.DEFAULT,
// FileUploader.LOCAL_BEHAVIOUR_COPY,
// targetContext,
// false,
// false
// )
// .setRemoteFolderToBeCreated()
// .execute(client, storageManager)
// .isSuccess
// )
//
// val result = ReadFileRemoteOperation("/testFile.txt").execute(client)
// assertTrue(result.isSuccess)
//
// assertEquals(file.length(), (result.data[0] as RemoteFile).length)
//
// val ocUpload2 = OCUpload(getDummyFile("empty.txt").absolutePath, "/testFile.txt", account.name)
//
// assertTrue(
// UploadFileOperation(
// uploadsStorageManager,
// connectivityServiceMock,
// powerManagementServiceMock,
// user,
// null,
// ocUpload2,
// FileUploader.NameCollisionPolicy.OVERWRITE,
// FileUploader.LOCAL_BEHAVIOUR_COPY,
// targetContext,
// false,
// false
// )
// .execute(client, storageManager)
// .isSuccess
// )
//
// val result2 = ReadFileRemoteOperation("/testFile.txt").execute(client)
// assertTrue(result2.isSuccess)
//
// assertEquals(0, (result2.data[0] as RemoteFile).length)
// }
/**
* uploads a file, overwrites it with an empty one, check if overwritten
*/
@Test
fun testKeepLocalAndOverwriteRemoteStatic() {
val file = getDummyFile("chunkedFile.txt")
FileUploadHelper().uploadNewFiles(
user,
arrayOf(file.absolutePath),
arrayOf("/testFile.txt"),
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
true,
UploadFileOperation.CREATED_BY_USER,
false,
false,
NameCollisionPolicy.DEFAULT
)
longSleep()
val result = ReadFileRemoteOperation("/testFile.txt").execute(client)
assertTrue(result.isSuccess)
assertEquals(file.length(), (result.data[0] as RemoteFile).length)
val ocFile2 = OCFile("/testFile.txt")
ocFile2.storagePath = getDummyFile("empty.txt").absolutePath
FileUploadHelper().uploadUpdatedFile(
user,
arrayOf(ocFile2),
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
NameCollisionPolicy.OVERWRITE
)
shortSleep()
val result2 = ReadFileRemoteOperation("/testFile.txt").execute(client)
assertTrue(result2.isSuccess)
assertEquals(0, (result2.data[0] as RemoteFile).length)
}
/**
* uploads a file, uploads another one with automatically (2) added, check
*/
@Test
fun testKeepBoth() {
var renameListenerWasTriggered = false
val file = getDummyFile("chunkedFile.txt")
val ocUpload = OCUpload(file.absolutePath, "/testFile.txt", account.name)
assertTrue(
UploadFileOperation(
uploadsStorageManager,
connectivityServiceMock,
powerManagementServiceMock,
user,
null,
ocUpload,
NameCollisionPolicy.DEFAULT,
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
targetContext,
false,
false,
storageManager
)
.setRemoteFolderToBeCreated()
.execute(client)
.isSuccess
)
val result = ReadFileRemoteOperation("/testFile.txt").execute(client)
assertTrue(result.isSuccess)
assertEquals(file.length(), (result.data[0] as RemoteFile).length)
val file2 = getDummyFile("empty.txt")
val ocUpload2 = OCUpload(file2.absolutePath, "/testFile.txt", account.name)
assertTrue(
UploadFileOperation(
uploadsStorageManager,
connectivityServiceMock,
powerManagementServiceMock,
user,
null,
ocUpload2,
NameCollisionPolicy.RENAME,
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
targetContext,
false,
false,
storageManager
)
.addRenameUploadListener {
renameListenerWasTriggered = true
}
.execute(client)
.isSuccess
)
val result2 = ReadFileRemoteOperation("/testFile.txt").execute(client)
assertTrue(result2.isSuccess)
assertEquals(file.length(), (result2.data[0] as RemoteFile).length)
val result3 = ReadFileRemoteOperation("/testFile (2).txt").execute(client)
assertTrue(result3.isSuccess)
assertEquals(file2.length(), (result3.data[0] as RemoteFile).length)
assertTrue(renameListenerWasTriggered)
}
/**
* uploads a file, uploads another one with automatically (2) added, check
*/
@Test
fun testKeepBothStatic() {
val file = getDummyFile("nonEmpty.txt")
FileUploadHelper().uploadNewFiles(
user,
arrayOf(file.absolutePath),
arrayOf("/testFile.txt"),
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
true,
UploadFileOperation.CREATED_BY_USER,
false,
false,
NameCollisionPolicy.DEFAULT
)
longSleep()
val result = ReadFileRemoteOperation("/testFile.txt").execute(client)
assertTrue(result.isSuccess)
assertEquals(file.length(), (result.data[0] as RemoteFile).length)
val ocFile2 = OCFile("/testFile.txt")
ocFile2.storagePath = getDummyFile("empty.txt").absolutePath
FileUploadHelper().uploadUpdatedFile(
user,
arrayOf(ocFile2),
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
NameCollisionPolicy.RENAME
)
shortSleep()
val result2 = ReadFileRemoteOperation("/testFile.txt").execute(client)
assertTrue(result2.isSuccess)
assertEquals(file.length(), (result2.data[0] as RemoteFile).length)
val result3 = ReadFileRemoteOperation("/testFile (2).txt").execute(client)
assertTrue(result3.isSuccess)
assertEquals(ocFile2.fileLength, (result3.data[0] as RemoteFile).length)
}
/**
* uploads a file with "keep server" option set, so do nothing
*/
@Test
fun testKeepServer() {
val file = getDummyFile("chunkedFile.txt")
val ocUpload = OCUpload(file.absolutePath, "/testFile.txt", account.name)
assertTrue(
UploadFileOperation(
uploadsStorageManager,
connectivityServiceMock,
powerManagementServiceMock,
user,
null,
ocUpload,
NameCollisionPolicy.DEFAULT,
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
targetContext,
false,
false,
storageManager
)
.setRemoteFolderToBeCreated()
.execute(client)
.isSuccess
)
val result = ReadFileRemoteOperation("/testFile.txt").execute(client)
assertTrue(result.isSuccess)
assertEquals(file.length(), (result.data[0] as RemoteFile).length)
val ocUpload2 = OCUpload(getDummyFile("empty.txt").absolutePath, "/testFile.txt", account.name)
assertFalse(
UploadFileOperation(
uploadsStorageManager,
connectivityServiceMock,
powerManagementServiceMock,
user,
null,
ocUpload2,
NameCollisionPolicy.CANCEL,
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
targetContext,
false,
false,
storageManager
)
.execute(client).isSuccess
)
val result2 = ReadFileRemoteOperation("/testFile.txt").execute(client)
assertTrue(result2.isSuccess)
assertEquals(file.length(), (result2.data[0] as RemoteFile).length)
}
/**
* uploads a file with "keep server" option set, so do nothing
*/
@Test
fun testKeepServerStatic() {
val file = getDummyFile("chunkedFile.txt")
FileUploadHelper().uploadNewFiles(
user,
arrayOf(file.absolutePath),
arrayOf("/testFile.txt"),
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
true,
UploadFileOperation.CREATED_BY_USER,
false,
false,
NameCollisionPolicy.DEFAULT
)
longSleep()
val result = ReadFileRemoteOperation("/testFile.txt").execute(client)
assertTrue(result.isSuccess)
assertEquals(file.length(), (result.data[0] as RemoteFile).length)
val ocFile2 = OCFile("/testFile.txt")
ocFile2.storagePath = getDummyFile("empty.txt").absolutePath
FileUploadHelper().uploadUpdatedFile(
user,
arrayOf(ocFile2),
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
NameCollisionPolicy.CANCEL
)
shortSleep()
val result2 = ReadFileRemoteOperation("/testFile.txt").execute(client)
assertTrue(result2.isSuccess)
assertEquals(file.length(), (result2.data[0] as RemoteFile).length)
}
/**
* uploads a file with "skip if exists" option set, so do nothing if file exists
*/
@Test
fun testCancelServer() {
val file = getDummyFile("chunkedFile.txt")
val ocUpload = OCUpload(file.absolutePath, "/testFile.txt", account.name)
assertTrue(
UploadFileOperation(
uploadsStorageManager,
connectivityServiceMock,
powerManagementServiceMock,
user,
null,
ocUpload,
NameCollisionPolicy.CANCEL,
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
targetContext,
false,
false,
storageManager
)
.setRemoteFolderToBeCreated()
.execute(client)
.isSuccess
)
val result = ReadFileRemoteOperation("/testFile.txt").execute(client)
assertTrue(result.isSuccess)
assertEquals(file.length(), (result.data[0] as RemoteFile).length)
val ocUpload2 = OCUpload(getDummyFile("empty.txt").absolutePath, "/testFile.txt", account.name)
val uploadResult = UploadFileOperation(
uploadsStorageManager,
connectivityServiceMock,
powerManagementServiceMock,
user,
null,
ocUpload2,
NameCollisionPolicy.CANCEL,
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
targetContext,
false,
false,
storageManager
)
.execute(client)
assertFalse(uploadResult.isSuccess)
assertTrue(uploadResult.exception is OperationCancelledException)
val result2 = ReadFileRemoteOperation("/testFile.txt").execute(client)
assertTrue(result2.isSuccess)
assertEquals(file.length(), (result2.data[0] as RemoteFile).length)
}
/**
* uploads a file with "skip if exists" option set, so do nothing if file exists
*/
@Test
fun testKeepCancelStatic() {
val file = getDummyFile("chunkedFile.txt")
FileUploadHelper().uploadNewFiles(
user,
arrayOf(file.absolutePath),
arrayOf("/testFile.txt"),
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
true,
UploadFileOperation.CREATED_BY_USER,
false,
false,
NameCollisionPolicy.DEFAULT
)
longSleep()
val result = ReadFileRemoteOperation("/testFile.txt").execute(client)
assertTrue(result.isSuccess)
assertEquals(file.length(), (result.data[0] as RemoteFile).length)
val ocFile2 = OCFile("/testFile.txt")
ocFile2.storagePath = getDummyFile("empty.txt").absolutePath
FileUploadHelper().uploadUpdatedFile(
user,
arrayOf(ocFile2),
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
NameCollisionPolicy.CANCEL
)
shortSleep()
val result2 = ReadFileRemoteOperation("/testFile.txt").execute(client)
assertTrue(result2.isSuccess)
assertEquals(file.length(), (result2.data[0] as RemoteFile).length)
}
}

View file

@ -0,0 +1,10 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.files.services
class LegacyFileUploaderIT : FileUploaderIT()

View file

@ -0,0 +1,71 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2021 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.operations
import com.owncloud.android.AbstractOnServerIT
import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation
import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation
import com.owncloud.android.lib.resources.shares.OCShare
import com.owncloud.android.lib.resources.shares.ShareType
import junit.framework.TestCase
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
@Suppress("MagicNumber")
class GetSharesForFileOperationIT : AbstractOnServerIT() {
@Test
fun shares() {
val remotePath = "/share/"
assertTrue(CreateFolderRemoteOperation(remotePath, true).execute(client).isSuccess)
// share folder to user "admin"
TestCase.assertTrue(
CreateShareRemoteOperation(
remotePath,
ShareType.USER,
"admin",
false,
"",
OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER
)
.execute(client).isSuccess
)
// share folder via public link
TestCase.assertTrue(
CreateShareRemoteOperation(
remotePath,
ShareType.PUBLIC_LINK,
"",
true,
"",
OCShare.READ_PERMISSION_FLAG
)
.execute(client).isSuccess
)
// share folder to group
assertTrue(
CreateShareRemoteOperation(
remotePath,
ShareType.GROUP,
"users",
false,
"",
OCShare.NO_PERMISSION
)
.execute(client).isSuccess
)
val shareResult = GetSharesForFileOperation(remotePath, false, false, storageManager).execute(client)
assertTrue(shareResult.isSuccess)
assertEquals(3, (shareResult.data as ArrayList<OCShare>).size)
}
}

View file

@ -0,0 +1,88 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.operations;
import com.owncloud.android.AbstractOnServerIT;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.db.OCUpload;
import org.junit.Test;
import java.io.IOException;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertTrue;
public class RemoveFileOperationIT extends AbstractOnServerIT {
@Test
public void deleteFolder() {
String parent = "/test/";
String path = parent + "folder1/";
assertTrue(new CreateFolderOperation(path, user, targetContext, getStorageManager()).execute(client)
.isSuccess());
OCFile folder = getStorageManager().getFileByPath(path);
assertNotNull(folder);
assertTrue(new RemoveFileOperation(folder,
false,
user,
false,
targetContext,
getStorageManager())
.execute(client)
.isSuccess());
OCFile parentFolder = getStorageManager().getFileByPath(parent);
assertNotNull(parentFolder);
assertTrue(new RemoveFileOperation(parentFolder,
false,
user,
false,
targetContext,
getStorageManager())
.execute(client)
.isSuccess());
}
@Test
public void deleteFile() throws IOException {
String parent = "/test/";
String path = parent + "empty.txt";
OCUpload ocUpload = new OCUpload(getDummyFile("empty.txt").getAbsolutePath(), path, account.name);
uploadOCUpload(ocUpload);
OCFile file = getStorageManager().getFileByPath(path);
assertNotNull(file);
assertTrue(new RemoveFileOperation(file,
false,
user,
false,
targetContext,
getStorageManager())
.execute(client)
.isSuccess());
OCFile parentFolder = getStorageManager().getFileByPath(parent);
assertNotNull(parentFolder);
assertTrue(new RemoveFileOperation(parentFolder,
false,
user,
false,
targetContext,
getStorageManager())
.execute(client)
.isSuccess());
}
}

View file

@ -0,0 +1,207 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Torsten Grote <t@grobox.de>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.providers
import android.content.Context
import android.database.ContentObserver
import android.database.Cursor
import android.net.Uri
import android.provider.DocumentsContract.Document.COLUMN_DOCUMENT_ID
import android.provider.DocumentsContract.Document.COLUMN_MIME_TYPE
import android.provider.DocumentsContract.Document.MIME_TYPE_DIR
import android.provider.DocumentsContract.EXTRA_LOADING
import android.provider.DocumentsContract.buildChildDocumentsUriUsingTree
import android.provider.DocumentsContract.buildDocumentUriUsingTree
import android.provider.DocumentsContract.buildTreeDocumentUri
import android.provider.DocumentsContract.getDocumentId
import androidx.annotation.VisibleForTesting
import androidx.documentfile.provider.DocumentFile
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation
import com.owncloud.android.providers.DocumentsStorageProvider.DOCUMENTID_SEPARATOR
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import java.io.IOException
import java.io.InputStream
import kotlin.coroutines.resume
// Uploads can sometimes take a bit of time, so 15sec is still considered recent enough
private const val RECENT_MILLISECONDS = 15_000
object DocumentsProviderUtils {
internal fun DocumentFile.getOCFile(storageManager: FileDataStorageManager): OCFile? {
val id = getDocumentId(uri)
val separated: List<String> = id.split(DOCUMENTID_SEPARATOR.toRegex())
return storageManager.getFileById(separated[1].toLong())
}
internal fun DocumentFile.assertRegularFile(
name: String? = null,
size: Long? = null,
mimeType: String? = null,
parent: DocumentFile? = null
) {
name?.let { assertEquals(it, this.name) }
assertTrue(exists())
assertTrue(isFile)
assertFalse(isDirectory)
assertFalse(isVirtual)
size?.let { assertEquals(it, length()) }
mimeType?.let { assertEquals(it, type) }
parent?.let { assertEquals(it.uri.toString(), parentFile!!.uri.toString()) }
}
internal fun DocumentFile.assertRegularFolder(name: String? = null, parent: DocumentFile? = null) {
name?.let { assertEquals(it, this.name) }
assertTrue(exists())
assertFalse(isFile)
assertTrue(isDirectory)
assertFalse(isVirtual)
parent?.let { assertEquals(it.uri.toString(), parentFile!!.uri.toString()) }
}
internal fun DocumentFile.assertRecentlyModified() {
val diff = System.currentTimeMillis() - lastModified()
assertTrue("File $name older than expected: $diff", diff < RECENT_MILLISECONDS)
}
internal fun assertExistsOnServer(client: OwnCloudClient, remotePath: String, shouldExist: Boolean) {
val result = ExistenceCheckRemoteOperation(remotePath, !shouldExist).execute(client)
assertTrue("$result", result.isSuccess)
}
internal fun assertListFilesEquals(expected: Collection<DocumentFile>, actual: Collection<DocumentFile>) {
// assertEquals(
// "Actual: ${actual.map { it.name.toString() }}",
// expected.map { it.uri.toString() }.apply { sorted() },
// actual.map { it.uri.toString() }.apply { sorted() },
// )
// FIXME replace with commented out stronger assertion above
// when parallel [UploadFileOperation]s don't bring back deleted files
val expectedSet = HashSet<String>(expected.map { it.uri.toString() })
val actualSet = HashSet<String>(actual.map { it.uri.toString() })
assertTrue(actualSet.containsAll(expectedSet))
actualSet.removeAll(expectedSet)
actualSet.forEach {
Log_OC.e("TEST", "Error: Found unexpected file: $it")
}
}
internal fun assertReadEquals(data: ByteArray, inputStream: InputStream?) {
assertNotNull(inputStream)
inputStream!!.use {
assertArrayEquals(data, it.readBytes())
}
}
/**
* Same as [DocumentFile.findFile] only that it re-queries when the first result was stale.
*
* Most documents providers including Nextcloud are listing the full directory content
* when querying for a specific file in a directory,
* so there is no point in trying to optimize the query by not listing all children.
*/
suspend fun DocumentFile.findFileBlocking(context: Context, displayName: String): DocumentFile? {
val files = listFilesBlocking(context)
for (doc in files) {
if (displayName == doc.name) return doc
}
return null
}
/**
* Works like [DocumentFile.listFiles] except that it waits until the DocumentProvider has a result.
* This prevents getting an empty list even though there are children to be listed.
*/
suspend fun DocumentFile.listFilesBlocking(context: Context) = withContext(Dispatchers.IO) {
val resolver = context.contentResolver
val childrenUri = buildChildDocumentsUriUsingTree(uri, getDocumentId(uri))
val projection = arrayOf(COLUMN_DOCUMENT_ID, COLUMN_MIME_TYPE)
val result = ArrayList<DocumentFile>()
try {
getLoadedCursor {
resolver.query(childrenUri, projection, null, null, null)
}
} catch (e: TimeoutCancellationException) {
throw IOException(e)
}.use { cursor ->
while (cursor.moveToNext()) {
val documentId = cursor.getString(0)
val isDirectory = cursor.getString(1) == MIME_TYPE_DIR
val file = if (isDirectory) {
val treeUri = buildTreeDocumentUri(uri.authority, documentId)
DocumentFile.fromTreeUri(context, treeUri)!!
} else {
val documentUri = buildDocumentUriUsingTree(uri, documentId)
DocumentFile.fromSingleUri(context, documentUri)!!
}
result.add(file)
}
}
result
}
/**
* Returns a cursor for the given query while ensuring that the cursor was loaded.
*
* When the SAF backend is a cloud storage provider (e.g. Nextcloud),
* it can happen that the query returns an outdated (e.g. empty) cursor
* which will only be updated in response to this query.
*
* See: https://commonsware.com/blog/2019/12/14/scoped-storage-stories-listfiles-woe.html
*
* This method uses a [suspendCancellableCoroutine] to wait for the result of a [ContentObserver]
* registered on the cursor in case the cursor is still loading ([EXTRA_LOADING]).
* If the cursor is not loading, it will be returned right away.
*
* @param timeout an optional time-out in milliseconds
* @throws TimeoutCancellationException if there was no result before the time-out
* @throws IOException if the query returns null
*/
@Suppress("EXPERIMENTAL_API_USAGE")
@VisibleForTesting
internal suspend fun getLoadedCursor(timeout: Long = 15_000, query: () -> Cursor?) =
withTimeout(timeout) {
suspendCancellableCoroutine<Cursor> { cont ->
val cursor = query() ?: throw IOException("Initial query returned no results")
cont.invokeOnCancellation { cursor.close() }
val loading = cursor.extras.getBoolean(EXTRA_LOADING, false)
if (loading) {
Log_OC.e("TEST", "Cursor was loading, wait for update...")
cursor.registerContentObserver(
object : ContentObserver(null) {
override fun onChange(selfChange: Boolean, uri: Uri?) {
cursor.close()
val newCursor = query()
if (newCursor == null) {
cont.cancel(IOException("Re-query returned no results"))
} else {
cont.resume(newCursor)
}
}
}
)
} else {
// not loading, return cursor right away
cont.resume(cursor)
}
}
}
}

View file

@ -0,0 +1,261 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Torsten Grote <t@grobox.de>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.providers
import android.provider.DocumentsContract
import androidx.documentfile.provider.DocumentFile
import com.nextcloud.test.RandomStringGenerator
import com.owncloud.android.AbstractOnServerIT
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile.ROOT_PATH
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.providers.DocumentsProviderUtils.assertExistsOnServer
import com.owncloud.android.providers.DocumentsProviderUtils.assertListFilesEquals
import com.owncloud.android.providers.DocumentsProviderUtils.assertReadEquals
import com.owncloud.android.providers.DocumentsProviderUtils.assertRecentlyModified
import com.owncloud.android.providers.DocumentsProviderUtils.assertRegularFile
import com.owncloud.android.providers.DocumentsProviderUtils.assertRegularFolder
import com.owncloud.android.providers.DocumentsProviderUtils.findFileBlocking
import com.owncloud.android.providers.DocumentsProviderUtils.getOCFile
import com.owncloud.android.providers.DocumentsProviderUtils.listFilesBlocking
import com.owncloud.android.providers.DocumentsStorageProvider.DOCUMENTID_SEPARATOR
import kotlinx.coroutines.runBlocking
import org.apache.commons.httpclient.HttpStatus
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity
import org.apache.jackrabbit.webdav.client.methods.PutMethod
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import kotlin.random.Random
private const val MAX_FILE_NAME_LENGTH = 225
class DocumentsStorageProviderIT : AbstractOnServerIT() {
private val context = targetContext
private val contentResolver = context.contentResolver
private val authority = context.getString(R.string.document_provider_authority)
private val rootFileId = storageManager.getFileByEncryptedRemotePath(ROOT_PATH).fileId
private val documentId = "${DocumentsStorageProvider.rootIdForUser(user)}${DOCUMENTID_SEPARATOR}$rootFileId"
private val uri = DocumentsContract.buildTreeDocumentUri(authority, documentId)
private val rootDir get() = DocumentFile.fromTreeUri(context, uri)!!
@Before
fun before() {
// DocumentsProvider#onCreate() is called when the application is started
// which is *after* AbstractOnServerIT adds the accounts (when the app is freshly installed).
// So we need to query our roots here to ensure that the internal storage map is initialized.
contentResolver.query(DocumentsContract.buildRootsUri(authority), null, null, null)
assertTrue("Storage root does not exist", rootDir.exists())
assertTrue(rootDir.isDirectory)
}
/**
* Delete all files in [rootDir] after each test.
*
* We can't use [AbstractOnServerIT.after] as this is only deleting remote files.
*/
@After
override fun after() = runBlocking {
rootDir.listFilesBlocking(context).forEach {
Log_OC.e("TEST", "Deleting ${it.name}...")
it.delete()
}
}
@Test
fun testCreateDeleteFiles() = runBlocking {
// no files in root initially
assertListFilesEquals(emptyList(), rootDir.listFilesBlocking(context))
// create first file
val name1 = RandomStringGenerator.make()
val type1 = "text/html"
val file1 = rootDir.createFile(type1, name1)!!
// check assumptions
/* FIXME: mimeType */
file1.assertRegularFile(name1, 0L, null, rootDir)
file1.assertRecentlyModified()
// file1 is found in root
assertListFilesEquals(listOf(file1), rootDir.listFilesBlocking(context).toList())
// file1 was uploaded
val ocFile1 = file1.getOCFile(storageManager)!!
assertExistsOnServer(client, ocFile1.remotePath, true)
// create second long file with long file name
val name2 = RandomStringGenerator.make(MAX_FILE_NAME_LENGTH)
val type2 = "application/octet-stream"
val file2 = rootDir.createFile(type2, name2)!!
// file2 was uploaded
val ocFile2 = file2.getOCFile(storageManager)!!
assertExistsOnServer(client, ocFile2.remotePath, true)
// check assumptions
file2.assertRegularFile(name2, 0L, type2, rootDir)
file2.assertRecentlyModified()
// both files get listed in root
assertListFilesEquals(listOf(file1, file2), rootDir.listFiles().toList())
// delete first file
assertTrue(file1.delete())
assertFalse(file1.exists())
assertExistsOnServer(client, ocFile1.remotePath, false)
// only second file gets listed in root
assertListFilesEquals(listOf(file2), rootDir.listFiles().toList())
// delete also second file
assertTrue(file2.delete())
assertFalse(file2.exists())
assertExistsOnServer(client, ocFile2.remotePath, false)
// no more files in root
assertListFilesEquals(emptyList(), rootDir.listFilesBlocking(context))
}
@Test
fun testReadWriteFiles() {
// create random file
val file1 = rootDir.createFile("application/octet-stream", RandomStringGenerator.make())!!
file1.assertRegularFile(size = 0L)
// write random bytes to file
@Suppress("MagicNumber")
val dataSize = Random.nextInt(1, 99) * 1024
val data1 = Random.nextBytes(dataSize)
contentResolver.openOutputStream(file1.uri, "wt").use {
it!!.write(data1)
}
// read back random bytes
assertReadEquals(data1, contentResolver.openInputStream(file1.uri))
// file size was updated correctly
file1.assertRegularFile(size = data1.size.toLong())
}
@Test
fun testCreateDeleteFolders() = runBlocking {
// create a new folder
val dirName1 = RandomStringGenerator.make()
val dir1 = rootDir.createDirectory(dirName1)!!
dir1.assertRegularFolder(dirName1, rootDir)
// FIXME about a minute gets lost somewhere after CFO sets the correct time
@Suppress("MagicNumber")
assertTrue(System.currentTimeMillis() - dir1.lastModified() < 60_000)
// dir1.assertRecentlyModified()
// ensure folder was uploaded to server
val ocDir1 = dir1.getOCFile(storageManager)!!
assertExistsOnServer(client, ocDir1.remotePath, true)
// create file in folder
val file1 = dir1.createFile("text/html", RandomStringGenerator.make())!!
file1.assertRegularFile(parent = dir1)
val ocFile1 = file1.getOCFile(storageManager)!!
assertExistsOnServer(client, ocFile1.remotePath, true)
// we find the new file in the created folder and get it in the list
assertEquals(file1.uri.toString(), dir1.findFileBlocking(context, file1.name!!)!!.uri.toString())
assertListFilesEquals(listOf(file1), dir1.listFilesBlocking(context))
// delete folder
dir1.delete()
assertFalse(dir1.exists())
assertExistsOnServer(client, ocDir1.remotePath, false)
// ensure file got deleted with it
// since Room was introduced, the file is not automatically updated for some reason.
// however, it is correctly deleted from server, and smoke testing shows it works just fine.
// suspecting a race condition of some sort
// assertFalse(file1.exists())
assertExistsOnServer(client, ocFile1.remotePath, false)
}
@Suppress("MagicNumber")
@Test(timeout = 5 * 60 * 1000)
fun testServerChangedFileContent() {
// create random file
val file1 = rootDir.createFile("text/plain", RandomStringGenerator.make())!!
file1.assertRegularFile(size = 0L)
val createdETag = file1.getOCFile(storageManager)!!.etagOnServer
assertTrue(createdETag.isNotEmpty())
val content1 = "initial content".toByteArray()
// write content bytes to file
contentResolver.openOutputStream(file1.uri, "wt").use {
it!!.write(content1)
}
// refresh
while (file1.getOCFile(storageManager)!!.etagOnServer == createdETag) {
shortSleep()
rootDir.listFiles()
}
val remotePath = file1.getOCFile(storageManager)!!.remotePath
val content2 = "new content".toByteArray()
// modify content on server side
val putMethod = PutMethod(client.getFilesDavUri(remotePath))
putMethod.requestEntity = ByteArrayRequestEntity(content2)
assertEquals(HttpStatus.SC_NO_CONTENT, client.executeMethod(putMethod))
client.exhaustResponse(putMethod.responseBodyAsStream)
putMethod.releaseConnection() // let the connection available for other methods
// read back content bytes
val bytes = contentResolver.openInputStream(file1.uri)?.readBytes() ?: ByteArray(0)
assertEquals(String(content2), String(bytes))
}
@Test
fun testServerSuccessive() {
// create random file
val file1 = rootDir.createFile("text/plain", RandomStringGenerator.make())!!
file1.assertRegularFile(size = 0L)
val createdETag = file1.getOCFile(storageManager)!!.etagOnServer
assertTrue(createdETag.isNotEmpty())
val content1 = "initial content".toByteArray()
// write content bytes to file
contentResolver.openOutputStream(file1.uri, "wt").use {
it!!.write(content1)
}
// refresh
while (file1.getOCFile(storageManager)!!.etagOnServer == createdETag) {
shortSleep()
rootDir.listFiles()
}
val content2 = "new content".toByteArray()
contentResolver.openOutputStream(file1.uri, "wt").use {
it!!.write(content2)
}
// read back content bytes
val bytes = contentResolver.openInputStream(file1.uri)?.readBytes() ?: ByteArray(0)
assertEquals(String(content2), String(bytes))
}
}

View file

@ -0,0 +1,100 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2021 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.providers
import android.content.ContentValues
import com.owncloud.android.db.ProviderMeta
import com.owncloud.android.utils.MimeTypeUtil
import org.junit.Test
@Suppress("FunctionNaming")
class FileContentProviderVerificationIT {
companion object {
private const val INVALID_COLUMN = "Invalid column"
private const val FILE_LENGTH = 120
}
@Test(expected = IllegalArgumentException::class)
fun verifyColumnName_Exception() {
FileContentProvider.VerificationUtils.verifyColumnName(INVALID_COLUMN)
}
@Test
fun verifyColumnName_OK() {
FileContentProvider.VerificationUtils.verifyColumnName(ProviderMeta.ProviderTableMeta.FILE_NAME)
}
@Test
fun verifyColumn_ContentValues_OK() {
// with valid columns
val contentValues = ContentValues()
contentValues.put(ProviderMeta.ProviderTableMeta.FILE_CONTENT_LENGTH, FILE_LENGTH)
contentValues.put(ProviderMeta.ProviderTableMeta.FILE_CONTENT_TYPE, MimeTypeUtil.MIMETYPE_TEXT_MARKDOWN)
FileContentProvider.VerificationUtils.verifyColumns(contentValues)
// empty
FileContentProvider.VerificationUtils.verifyColumns(ContentValues())
}
@Test(expected = IllegalArgumentException::class)
fun verifyColumn_ContentValues_invalidColumn() {
// with invalid columns
val contentValues = ContentValues()
contentValues.put(INVALID_COLUMN, FILE_LENGTH)
contentValues.put(ProviderMeta.ProviderTableMeta.FILE_CONTENT_TYPE, MimeTypeUtil.MIMETYPE_TEXT_MARKDOWN)
FileContentProvider.VerificationUtils.verifyColumns(contentValues)
}
@Test
fun verifySortOrder_OK() {
// null
FileContentProvider.VerificationUtils.verifySortOrder(null)
// empty
FileContentProvider.VerificationUtils.verifySortOrder("")
// valid sort
FileContentProvider.VerificationUtils.verifySortOrder(ProviderMeta.ProviderTableMeta.FILE_DEFAULT_SORT_ORDER)
}
@Test(expected = IllegalArgumentException::class)
fun verifySortOrder_InvalidColumn() {
// with invalid column
FileContentProvider.VerificationUtils.verifySortOrder("$INVALID_COLUMN desc")
}
@Test(expected = IllegalArgumentException::class)
fun verifySortOrder_InvalidGrammar() {
// with invalid grammar
FileContentProvider.VerificationUtils.verifySortOrder("${ProviderMeta.ProviderTableMeta._ID} ;--foo")
}
@Test
fun verifyWhere_OK() {
FileContentProvider.VerificationUtils.verifyWhere(null)
FileContentProvider.VerificationUtils.verifyWhere(
"${ProviderMeta.ProviderTableMeta._ID}=? AND ${ProviderMeta.ProviderTableMeta.FILE_ACCOUNT_OWNER}=?"
)
FileContentProvider.VerificationUtils.verifyWhere(
"${ProviderMeta.ProviderTableMeta._ID} = 1" +
" AND (1 = 1)" +
" AND ${ProviderMeta.ProviderTableMeta.FILE_ACCOUNT_OWNER} LIKE ?"
)
}
@Test(expected = IllegalArgumentException::class)
fun verifyWhere_InvalidColumnName() {
FileContentProvider.VerificationUtils.verifyWhere("$INVALID_COLUMN= ?")
}
@Test(expected = IllegalArgumentException::class)
fun verifyWhere_InvalidGrammar() {
FileContentProvider.VerificationUtils.verifyWhere("1=1 -- SELECT * FROM")
}
}

View file

@ -0,0 +1,32 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.providers
import androidx.test.espresso.intent.rule.IntentsTestRule
import com.nextcloud.test.TestActivity
import com.owncloud.android.AbstractOnServerIT
import org.junit.Rule
import org.junit.Test
class UsersAndGroupsSearchProviderIT : AbstractOnServerIT() {
@get:Rule
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
@Test
fun searchUser() {
val activity = testActivityRule.launchActivity(null)
shortSleep()
activity.runOnUiThread {
// fragment.search("Admin")
}
longSleep()
}
}

View file

@ -0,0 +1,136 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui
import android.os.Build
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.web.sugar.Web
import androidx.test.espresso.web.webdriver.DriverAtoms
import androidx.test.espresso.web.webdriver.Locator
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.account.UserAccountManagerImpl
import com.nextcloud.test.GrantStoragePermissionRule
import com.nextcloud.test.RetryTestRule
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import com.owncloud.android.authentication.AuthenticatorActivity
import org.junit.AfterClass
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@LargeTest
class LoginIT : AbstractIT() {
@get:Rule
val permissionRule = GrantStoragePermissionRule.grant()
@get:Rule
var retryTestRule = RetryTestRule()
@Before
fun setUp() {
tearDown()
ActivityScenario.launch(AuthenticatorActivity::class.java)
}
@Test
@Throws(InterruptedException::class)
@Suppress("MagicNumber", "SwallowedException")
/**
* The CI/CD pipeline is encountering issues related to the Android version for this functionality.
* Therefore the test will only be executed on Android versions 10 and above.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
fun login() {
val arguments = InstrumentationRegistry.getArguments()
val baseUrl = arguments.getString("TEST_SERVER_URL")!!
val loginName = arguments.getString("TEST_SERVER_USERNAME")!!
val password = arguments.getString("TEST_SERVER_PASSWORD")!!
Espresso.onView(ViewMatchers.withId(R.id.login)).perform(ViewActions.click())
Espresso.onView(ViewMatchers.withId(R.id.host_url_input)).perform(ViewActions.typeText(baseUrl))
Espresso.onView(ViewMatchers.withId(R.id.host_url_input)).perform(ViewActions.typeTextIntoFocusedView("\n"))
Thread.sleep(3000)
Web.onWebView().forceJavascriptEnabled()
// click on login
try {
// NC 25+
Web.onWebView()
.withElement(DriverAtoms.findElement(Locator.XPATH, "//form[@id='login-form']/input[@type='submit']"))
.perform(DriverAtoms.webClick())
} catch (e: RuntimeException) {
// NC < 25
Web.onWebView()
.withElement(DriverAtoms.findElement(Locator.XPATH, "//p[@id='redirect-link']/a"))
.perform(DriverAtoms.webClick())
}
// username
Web.onWebView()
.withElement(DriverAtoms.findElement(Locator.XPATH, "//input[@id='user']"))
.perform(DriverAtoms.webKeys(loginName))
// password
Web.onWebView()
.withElement(DriverAtoms.findElement(Locator.XPATH, "//input[@id='password']"))
.perform(DriverAtoms.webKeys(password))
// click login
try {
// NC 25+
Web.onWebView()
.withElement(DriverAtoms.findElement(Locator.XPATH, "//button[@type='submit']"))
.perform(DriverAtoms.webClick())
} catch (e: RuntimeException) {
// NC < 25
Web.onWebView()
.withElement(DriverAtoms.findElement(Locator.XPATH, "//input[@type='submit']"))
.perform(DriverAtoms.webClick())
}
Thread.sleep(2000)
// grant access
Web.onWebView()
.withElement(DriverAtoms.findElement(Locator.XPATH, "//input[@type='submit']"))
.perform(DriverAtoms.webClick())
Thread.sleep((5 * 1000).toLong())
// check for account
val targetContext = InstrumentationRegistry.getInstrumentation().targetContext
val accountManager: UserAccountManager = UserAccountManagerImpl.fromContext(targetContext)
Assert.assertEquals(1, accountManager.accounts.size.toLong())
val account = accountManager.accounts[0]
// account.name is loginName@baseUrl (without protocol)
Assert.assertEquals(loginName, account.name.split("@".toRegex()).toTypedArray()[0])
Assert.assertEquals(
baseUrl.split("//".toRegex()).toTypedArray()[1],
account.name.split("@".toRegex()).toTypedArray()[1]
)
}
companion object {
@AfterClass
fun tearDown() {
val targetContext = InstrumentationRegistry.getInstrumentation().targetContext
val accountManager: UserAccountManager = UserAccountManagerImpl.fromContext(targetContext)
if (accountManager.accounts.isNotEmpty()) {
accountManager.removeAllAccounts()
}
}
}
}

View file

@ -0,0 +1,318 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.activity;
import android.content.Intent;
import com.nextcloud.client.account.UserAccountManagerImpl;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.db.OCUpload;
import com.owncloud.android.ui.dialog.ConflictsResolveDialog;
import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.ScreenshotTest;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import java.util.Objects;
import androidx.fragment.app.DialogFragment;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
public class ConflictsResolveActivityIT extends AbstractIT {
@Rule public IntentsTestRule<ConflictsResolveActivity> activityRule =
new IntentsTestRule<>(ConflictsResolveActivity.class, true, false);
private boolean returnCode;
@Test
@ScreenshotTest
public void screenshotTextFiles() {
OCFile newFile = new OCFile("/newFile.txt");
newFile.setFileLength(56000);
newFile.setModificationTimestamp(1522019340);
newFile.setStoragePath(FileStorageUtils.getSavePath(user.getAccountName()) + "/nonEmpty.txt");
OCFile existingFile = new OCFile("/newFile.txt");
existingFile.setFileLength(1024000);
existingFile.setModificationTimestamp(1582019340);
FileDataStorageManager storageManager = new FileDataStorageManager(user, targetContext.getContentResolver());
storageManager.saveNewFile(existingFile);
Intent intent = new Intent(targetContext, ConflictsResolveActivity.class);
intent.putExtra(ConflictsResolveActivity.EXTRA_FILE, newFile);
intent.putExtra(ConflictsResolveActivity.EXTRA_EXISTING_FILE, existingFile);
ConflictsResolveActivity sut = activityRule.launchActivity(intent);
ConflictsResolveDialog dialog = ConflictsResolveDialog.newInstance(existingFile,
newFile,
UserAccountManagerImpl
.fromContext(targetContext)
.getUser()
);
dialog.showDialog(sut);
getInstrumentation().waitForIdleSync();
shortSleep();
shortSleep();
shortSleep();
shortSleep();
screenshot(Objects.requireNonNull(dialog.requireDialog().getWindow()).getDecorView());
}
// @Test
// @ScreenshotTest // todo run without real server
// public void screenshotImages() throws IOException {
// FileDataStorageManager storageManager = new FileDataStorageManager(user,
// targetContext.getContentResolver());
//
// OCFile newFile = new OCFile("/newFile.txt");
// newFile.setFileLength(56000);
// newFile.setModificationTimestamp(1522019340);
// newFile.setStoragePath(FileStorageUtils.getSavePath(user.getAccountName()) + "/nonEmpty.txt");
//
// File image = getFile("image.jpg");
//
// assertTrue(new UploadFileRemoteOperation(image.getAbsolutePath(),
// "/image.jpg",
// "image/jpg",
// "10000000").execute(client).isSuccess());
//
// assertTrue(new RefreshFolderOperation(storageManager.getFileByPath("/"),
// System.currentTimeMillis(),
// false,
// true,
// storageManager,
// user.toPlatformAccount(),
// targetContext
// ).execute(client).isSuccess());
//
// OCFile existingFile = storageManager.getFileByPath("/image.jpg");
//
// Intent intent = new Intent(targetContext, ConflictsResolveActivity.class);
// intent.putExtra(ConflictsResolveActivity.EXTRA_FILE, newFile);
// intent.putExtra(ConflictsResolveActivity.EXTRA_EXISTING_FILE, existingFile);
//
// ConflictsResolveActivity sut = activityRule.launchActivity(intent);
//
// ConflictsResolveDialog.OnConflictDecisionMadeListener listener = decision -> {
//
// };
//
// ConflictsResolveDialog dialog = ConflictsResolveDialog.newInstance(existingFile,
// newFile,
// UserAccountManagerImpl
// .fromContext(targetContext)
// .getUser()
// );
// dialog.showDialog(sut);
// dialog.listener = listener;
//
// getInstrumentation().waitForIdleSync();
// shortSleep();
//
// screenshot(Objects.requireNonNull(dialog.requireDialog().getWindow()).getDecorView());
// }
@Test
public void cancel() {
returnCode = false;
OCUpload newUpload = new OCUpload(FileStorageUtils.getSavePath(user.getAccountName()) + "/nonEmpty.txt",
"/newFile.txt",
user.getAccountName());
OCFile existingFile = new OCFile("/newFile.txt");
existingFile.setFileLength(1024000);
existingFile.setModificationTimestamp(1582019340);
OCFile newFile = new OCFile("/newFile.txt");
newFile.setFileLength(56000);
newFile.setModificationTimestamp(1522019340);
newFile.setStoragePath(FileStorageUtils.getSavePath(user.getAccountName()) + "/nonEmpty.txt");
FileDataStorageManager storageManager = new FileDataStorageManager(user, targetContext.getContentResolver());
storageManager.saveNewFile(existingFile);
Intent intent = new Intent(targetContext, ConflictsResolveActivity.class);
intent.putExtra(ConflictsResolveActivity.EXTRA_FILE, newFile);
intent.putExtra(ConflictsResolveActivity.EXTRA_EXISTING_FILE, existingFile);
intent.putExtra(ConflictsResolveActivity.EXTRA_CONFLICT_UPLOAD_ID, newUpload.getUploadId());
ConflictsResolveActivity sut = activityRule.launchActivity(intent);
sut.listener = decision -> {
assertEquals(decision, ConflictsResolveDialog.Decision.CANCEL);
returnCode = true;
};
getInstrumentation().waitForIdleSync();
shortSleep();
onView(withText("Cancel")).perform(click());
assertTrue(returnCode);
}
@Test
@ScreenshotTest
public void keepExisting() {
returnCode = false;
OCUpload newUpload = new OCUpload(FileStorageUtils.getSavePath(user.getAccountName()) + "/nonEmpty.txt",
"/newFile.txt",
user.getAccountName());
OCFile existingFile = new OCFile("/newFile.txt");
existingFile.setFileLength(1024000);
existingFile.setModificationTimestamp(1582019340);
OCFile newFile = new OCFile("/newFile.txt");
newFile.setFileLength(56000);
newFile.setModificationTimestamp(1522019340);
newFile.setStoragePath(FileStorageUtils.getSavePath(user.getAccountName()) + "/nonEmpty.txt");
FileDataStorageManager storageManager = new FileDataStorageManager(user, targetContext.getContentResolver());
storageManager.saveNewFile(existingFile);
Intent intent = new Intent(targetContext, ConflictsResolveActivity.class);
intent.putExtra(ConflictsResolveActivity.EXTRA_FILE, newFile);
intent.putExtra(ConflictsResolveActivity.EXTRA_EXISTING_FILE, existingFile);
intent.putExtra(ConflictsResolveActivity.EXTRA_CONFLICT_UPLOAD_ID, newUpload.getUploadId());
ConflictsResolveActivity sut = activityRule.launchActivity(intent);
sut.listener = decision -> {
assertEquals(decision, ConflictsResolveDialog.Decision.KEEP_SERVER);
returnCode = true;
};
getInstrumentation().waitForIdleSync();
onView(withId(R.id.existing_checkbox)).perform(click());
DialogFragment dialog = (DialogFragment) sut.getSupportFragmentManager().findFragmentByTag("conflictDialog");
screenshot(Objects.requireNonNull(dialog.requireDialog().getWindow()).getDecorView());
onView(withText("OK")).perform(click());
assertTrue(returnCode);
}
@Test
@ScreenshotTest
public void keepNew() {
returnCode = false;
OCUpload newUpload = new OCUpload(FileStorageUtils.getSavePath(user.getAccountName()) + "/nonEmpty.txt",
"/newFile.txt",
user.getAccountName());
OCFile existingFile = new OCFile("/newFile.txt");
existingFile.setFileLength(1024000);
existingFile.setModificationTimestamp(1582019340);
existingFile.setRemoteId("00000123abc");
OCFile newFile = new OCFile("/newFile.txt");
newFile.setFileLength(56000);
newFile.setModificationTimestamp(1522019340);
newFile.setStoragePath(FileStorageUtils.getSavePath(user.getAccountName()) + "/nonEmpty.txt");
FileDataStorageManager storageManager = new FileDataStorageManager(user, targetContext.getContentResolver());
storageManager.saveNewFile(existingFile);
Intent intent = new Intent(targetContext, ConflictsResolveActivity.class);
intent.putExtra(ConflictsResolveActivity.EXTRA_FILE, newFile);
intent.putExtra(ConflictsResolveActivity.EXTRA_EXISTING_FILE, existingFile);
intent.putExtra(ConflictsResolveActivity.EXTRA_CONFLICT_UPLOAD_ID, newUpload.getUploadId());
ConflictsResolveActivity sut = activityRule.launchActivity(intent);
sut.listener = decision -> {
assertEquals(decision, ConflictsResolveDialog.Decision.KEEP_LOCAL);
returnCode = true;
};
getInstrumentation().waitForIdleSync();
onView(withId(R.id.new_checkbox)).perform(click());
DialogFragment dialog = (DialogFragment) sut.getSupportFragmentManager().findFragmentByTag("conflictDialog");
screenshot(Objects.requireNonNull(dialog.requireDialog().getWindow()).getDecorView());
onView(withText("OK")).perform(click());
assertTrue(returnCode);
}
@Test
@ScreenshotTest
public void keepBoth() {
returnCode = false;
OCUpload newUpload = new OCUpload(FileStorageUtils.getSavePath(user.getAccountName()) + "/nonEmpty.txt",
"/newFile.txt",
user.getAccountName());
OCFile existingFile = new OCFile("/newFile.txt");
existingFile.setFileLength(1024000);
existingFile.setModificationTimestamp(1582019340);
OCFile newFile = new OCFile("/newFile.txt");
newFile.setFileLength(56000);
newFile.setModificationTimestamp(1522019340);
newFile.setStoragePath(FileStorageUtils.getSavePath(user.getAccountName()) + "/nonEmpty.txt");
FileDataStorageManager storageManager = new FileDataStorageManager(user, targetContext.getContentResolver());
storageManager.saveNewFile(existingFile);
Intent intent = new Intent(targetContext, ConflictsResolveActivity.class);
intent.putExtra(ConflictsResolveActivity.EXTRA_FILE, newFile);
intent.putExtra(ConflictsResolveActivity.EXTRA_EXISTING_FILE, existingFile);
intent.putExtra(ConflictsResolveActivity.EXTRA_CONFLICT_UPLOAD_ID, newUpload.getUploadId());
ConflictsResolveActivity sut = activityRule.launchActivity(intent);
sut.listener = decision -> {
assertEquals(decision, ConflictsResolveDialog.Decision.KEEP_BOTH);
returnCode = true;
};
getInstrumentation().waitForIdleSync();
onView(withId(R.id.existing_checkbox)).perform(click());
onView(withId(R.id.new_checkbox)).perform(click());
DialogFragment dialog = (DialogFragment) sut.getSupportFragmentManager().findFragmentByTag("conflictDialog");
screenshot(Objects.requireNonNull(dialog.requireDialog().getWindow()).getDecorView());
onView(withText("OK")).perform(click());
assertTrue(returnCode);
}
@After
public void after() {
getStorageManager().deleteAllFiles();
}
}

View file

@ -0,0 +1,51 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.activity
import android.content.Intent
import androidx.test.espresso.intent.rule.IntentsTestRule
import com.owncloud.android.AbstractIT
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.utils.ScreenshotTest
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
class ContactsPreferenceActivityIT : AbstractIT() {
@get:Rule
var activityRule = IntentsTestRule(ContactsPreferenceActivity::class.java, true, false)
@Test
@ScreenshotTest
fun openVCF() {
val file = getFile("vcard.vcf")
val vcfFile = OCFile("/contacts.vcf")
vcfFile.storagePath = file.absolutePath
assertTrue(vcfFile.isDown)
val intent = Intent()
intent.putExtra(ContactsPreferenceActivity.EXTRA_FILE, vcfFile)
intent.putExtra(ContactsPreferenceActivity.EXTRA_USER, user)
val sut = activityRule.launchActivity(intent)
shortSleep()
screenshot(sut)
}
@Test
@ScreenshotTest
fun openContactsPreference() {
val sut = activityRule.launchActivity(null)
shortSleep()
screenshot(sut)
}
}

View file

@ -0,0 +1,112 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.activity;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.net.Uri;
import android.os.Bundle;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.account.UserAccountManagerImpl;
import com.nextcloud.test.RetryTestRule;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.anyOf;
import static org.junit.Assert.assertEquals;
public class DrawerActivityIT extends AbstractIT {
@Rule public IntentsTestRule<FileDisplayActivity> activityRule = new IntentsTestRule<>(FileDisplayActivity.class,
true,
false);
@Rule
public final RetryTestRule retryTestRule = new RetryTestRule();
private static Account account1;
private static User user1;
private static Account account2;
private static String account2Name;
private static String account2DisplayName;
@BeforeClass
public static void beforeClass() {
Bundle arguments = androidx.test.platform.app.InstrumentationRegistry.getArguments();
Uri baseUrl = Uri.parse(arguments.getString("TEST_SERVER_URL"));
AccountManager platformAccountManager = AccountManager.get(targetContext);
UserAccountManager userAccountManager = UserAccountManagerImpl.fromContext(targetContext);
for (Account account : platformAccountManager.getAccounts()) {
platformAccountManager.removeAccountExplicitly(account);
}
String loginName = "user1";
String password = "user1";
Account temp = new Account(loginName + "@" + baseUrl, MainApp.getAccountType(targetContext));
platformAccountManager.addAccountExplicitly(temp, password, null);
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_ACCOUNT_VERSION,
Integer.toString(UserAccountManager.ACCOUNT_VERSION));
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_VERSION, "14.0.0.0");
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_BASE_URL, baseUrl.toString());
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_USER_ID, loginName); // same as userId
account1 = userAccountManager.getAccountByName(loginName + "@" + baseUrl);
user1 = userAccountManager.getUser(account1.name).orElseThrow(IllegalAccessError::new);
loginName = "user2";
password = "user2";
temp = new Account(loginName + "@" + baseUrl, MainApp.getAccountType(targetContext));
platformAccountManager.addAccountExplicitly(temp, password, null);
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_ACCOUNT_VERSION,
Integer.toString(UserAccountManager.ACCOUNT_VERSION));
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_VERSION, "14.0.0.0");
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_BASE_URL, baseUrl.toString());
platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_USER_ID, loginName); // same as userId
account2 = userAccountManager.getAccountByName(loginName + "@" + baseUrl);
account2Name = loginName + "@" + baseUrl;
account2DisplayName = "User Two@" + baseUrl;
}
@Test
public void switchAccountViaAccountList() {
FileDisplayActivity sut = activityRule.launchActivity(null);
sut.setUser(user1);
assertEquals(account1, sut.getUser().get().toPlatformAccount());
onView(withId(R.id.switch_account_button)).perform(click());
onView(anyOf(withText(account2Name), withText(account2DisplayName))).perform(click());
waitForIdleSync();
assertEquals(account2, sut.getUser().get().toPlatformAccount());
onView(withId(R.id.switch_account_button)).perform(click());
onView(withText(account1.name)).perform(click());
}
}

View file

@ -0,0 +1,37 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2019 Unpublished <unpublished@gmx.net>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.activity;
import android.app.Activity;
import com.nextcloud.client.onboarding.WhatsNewActivity;
import com.owncloud.android.AbstractIT;
import org.junit.Test;
import androidx.test.core.app.ActivityScenario;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import static androidx.test.runner.lifecycle.Stage.RESUMED;
public class FileDisplayActivityTest extends AbstractIT {
@Test
public void testSetupToolbar() {
try (ActivityScenario<FileDisplayActivity> scenario = ActivityScenario.launch(FileDisplayActivity.class)) {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
Activity activity =
ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED).iterator().next();
if (activity instanceof WhatsNewActivity) {
activity.onBackPressed();
}
});
scenario.recreate();
}
}
}

View file

@ -0,0 +1,147 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2019 Kilian Périsset <kilian.perisset@infomaniak.com>
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.activity;
import android.content.Intent;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.utils.ScreenshotTest;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.rule.ActivityTestRule;
@RunWith(AndroidJUnit4.class)
//@LargeTest
public class FolderPickerActivityIT extends AbstractIT {
@Rule
public ActivityTestRule<FolderPickerActivity> activityRule =
new ActivityTestRule<>(FolderPickerActivity.class);
@Test
public void getActivityFile() {
// Arrange
FolderPickerActivity targetActivity = activityRule.getActivity();
OCFile origin = new OCFile("/test/file.test");
origin.setRemotePath("/remotePath/test");
// Act
targetActivity.setFile(origin);
OCFile target = targetActivity.getFile();
// Assert
Assert.assertEquals(origin, target);
}
@Test
public void getParentFolder_isNotRootFolder() {
// Arrange
FolderPickerActivity targetActivity = activityRule.getActivity();
OCFile origin = new OCFile("/test/");
origin.setFileId(1);
origin.setRemotePath("/test/");
origin.setStoragePath("/test/");
origin.setFolder();
// Act
targetActivity.setFile(origin);
OCFile target = targetActivity.getCurrentFolder();
// Assert
Assert.assertEquals(origin, target);
}
@Test
public void getParentFolder_isRootFolder() {
// Arrange
FolderPickerActivity targetActivity = activityRule.getActivity();
OCFile origin = new OCFile("/");
origin.setFileId(1);
origin.setRemotePath("/");
origin.setStoragePath("/");
origin.setFolder();
// Act
targetActivity.setFile(origin);
OCFile target = targetActivity.getCurrentFolder();
// Assert
Assert.assertEquals(origin, target);
}
@Test
public void nullFile() {
// Arrange
FolderPickerActivity targetActivity = activityRule.getActivity();
OCFile rootFolder = targetActivity.getStorageManager().getFileByPath(OCFile.ROOT_PATH);
// Act
targetActivity.setFile(null);
OCFile target = targetActivity.getCurrentFolder();
// Assert
Assert.assertEquals(rootFolder, target);
}
@Test
public void getParentFolder() {
// Arrange
FolderPickerActivity targetActivity = activityRule.getActivity();
OCFile origin = new OCFile("/test/file.test");
origin.setRemotePath("/test/file.test");
OCFile target = new OCFile("/test/");
// Act
targetActivity.setFile(origin);
// Assert
Assert.assertEquals(origin, target);
}
@Test
@ScreenshotTest
public void open() {
FolderPickerActivity sut = activityRule.getActivity();
OCFile origin = new OCFile("/test/file.txt");
sut.setFile(origin);
sut.runOnUiThread(() -> {
sut.findViewById(R.id.folder_picker_btn_copy).requestFocus();
});
waitForIdleSync();
screenshot(sut);
}
@Test
@ScreenshotTest
public void testMoveOrCopy() {
Intent intent = new Intent();
FolderPickerActivity targetActivity = activityRule.launchActivity(intent);
waitForIdleSync();
screenshot(targetActivity);
}
@Test
@ScreenshotTest
public void testChooseLocationAction() {
Intent intent = new Intent();
intent.putExtra(FolderPickerActivity.EXTRA_ACTION, FolderPickerActivity.CHOOSE_LOCATION);
FolderPickerActivity targetActivity = activityRule.launchActivity(intent);
waitForIdleSync();
screenshot(targetActivity);
}
}

View file

@ -0,0 +1,66 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.activity;
import android.app.Activity;
import com.nextcloud.client.account.User;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.lib.common.Quota;
import com.owncloud.android.lib.common.UserInfo;
import com.owncloud.android.utils.ScreenshotTest;
import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
import androidx.test.espresso.intent.rule.IntentsTestRule;
public class ManageAccountsActivityIT extends AbstractIT {
@Rule
public IntentsTestRule<ManageAccountsActivity> activityRule = new IntentsTestRule<>(ManageAccountsActivity.class,
true,
false);
@Test
@ScreenshotTest
public void open() {
Activity sut = activityRule.launchActivity(null);
shortSleep();
screenshot(sut);
}
@Test
@ScreenshotTest
public void userInfoDetail() {
ManageAccountsActivity sut = activityRule.launchActivity(null);
User user = sut.accountManager.getUser();
UserInfo userInfo = new UserInfo("test",
true,
"Test User",
"test@nextcloud.com",
"+49 123 456",
"Address 123, Berlin",
"https://www.nextcloud.com",
"https://twitter.com/Nextclouders",
new Quota(),
new ArrayList<>());
sut.showUser(user, userInfo);
shortSleep();
shortSleep();
screenshot(getCurrentActivity());
}
}

View file

@ -0,0 +1,173 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.activity
import androidx.annotation.UiThread
import androidx.test.core.app.launchActivity
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import com.owncloud.android.utils.EspressoIdlingResource
import com.owncloud.android.AbstractIT
import com.owncloud.android.lib.resources.notifications.models.Action
import com.owncloud.android.lib.resources.notifications.models.Notification
import com.owncloud.android.lib.resources.notifications.models.RichObject
import com.owncloud.android.utils.ScreenshotTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.util.GregorianCalendar
class NotificationsActivityIT : AbstractIT() {
private val testClassName = "com.owncloud.android.ui.activity.NotificationsActivityIT"
@Before
fun registerIdlingResource() {
IdlingRegistry.getInstance().register(EspressoIdlingResource.countingIdlingResource)
}
@After
fun unregisterIdlingResource() {
IdlingRegistry.getInstance().unregister(EspressoIdlingResource.countingIdlingResource)
}
@Test
@UiThread
@ScreenshotTest
fun empty() {
launchActivity<NotificationsActivity>().use { scenario ->
scenario.onActivity { sut ->
onIdleSync {
EspressoIdlingResource.increment()
sut.populateList(ArrayList())
EspressoIdlingResource.decrement()
val screenShotName = createName(testClassName + "_" + "empty", "")
onView(isRoot()).check(matches(isDisplayed()))
screenshotViaName(sut, screenShotName)
}
}
}
}
@Test
@UiThread
@ScreenshotTest
@SuppressWarnings("MagicNumber")
fun showNotifications() {
val date = GregorianCalendar()
date.set(2005, 4, 17, 10, 35, 30) // random date
val notifications = ArrayList<Notification>()
notifications.add(
Notification(
1,
"files",
"user",
date.time,
"objectType",
"objectId",
"App recommendation: Tasks",
"SubjectRich",
HashMap<String, RichObject>(),
"Sync tasks from various devices with your Nextcloud and edit them online.",
"MessageRich",
HashMap<String, RichObject>(),
"link",
"icon",
ArrayList<Action>()
)
)
val actions = ArrayList<Action>().apply {
add(Action("Send usage", "link", "url", true))
add(Action("Not now", "link", "url", false))
}
notifications.add(
Notification(
1,
"files",
"user",
date.time,
"objectType",
"objectId",
"Help improve Nextcloud",
"SubjectRich",
HashMap<String, RichObject>(),
"Do you want to help us to improve Nextcloud" +
" by providing some anonymize data about your setup and usage?",
"MessageRich",
HashMap<String, RichObject>(),
"link",
"icon",
actions
)
)
val moreAction = ArrayList<Action>().apply {
add(Action("Send usage", "link", "url", true))
add(Action("Not now", "link", "url", false))
add(Action("third action", "link", "url", false))
add(Action("Delay", "link", "url", false))
}
notifications.add(
Notification(
2,
"files",
"user",
date.time,
"objectType",
"objectId",
"Help improve Nextcloud",
"SubjectRich",
HashMap<String, RichObject>(),
"Do you want to help us to improve Nextcloud by providing some anonymize data about your setup and " +
"usage?",
"MessageRich",
HashMap<String, RichObject>(),
"link",
"icon",
moreAction
)
)
launchActivity<NotificationsActivity>().use { scenario ->
scenario.onActivity { sut ->
onIdleSync {
EspressoIdlingResource.increment()
sut.populateList(notifications)
EspressoIdlingResource.decrement()
val screenShotName = createName(testClassName + "_" + "showNotifications", "")
onView(isRoot()).check(matches(isDisplayed()))
screenshotViaName(sut, screenShotName)
}
}
}
}
@Test
@UiThread
@ScreenshotTest
fun error() {
launchActivity<NotificationsActivity>().use { scenario ->
scenario.onActivity { sut ->
onIdleSync {
EspressoIdlingResource.increment()
sut.setEmptyContent("Error", "Error! Please try again later!")
EspressoIdlingResource.decrement()
val screenShotName = createName(testClassName + "_" + "error", "")
onView(isRoot()).check(matches(isDisplayed()))
screenshotViaName(sut, screenShotName)
}
}
}
}
}

View file

@ -0,0 +1,66 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.activity
import android.content.Intent
import androidx.test.espresso.Espresso
import androidx.test.espresso.intent.rule.IntentsTestRule
import com.owncloud.android.AbstractIT
import com.owncloud.android.utils.ScreenshotTest
import org.junit.Rule
import org.junit.Test
class PassCodeActivityIT : AbstractIT() {
@get:Rule
var activityRule = IntentsTestRule(PassCodeActivity::class.java, true, false)
@Test
@ScreenshotTest
fun check() {
val sut = activityRule.launchActivity(Intent(PassCodeActivity.ACTION_CHECK))
waitForIdleSync()
sut.runOnUiThread { sut.binding.txt0.clearFocus() }
Espresso.closeSoftKeyboard()
shortSleep()
waitForIdleSync()
screenshot(sut)
}
@Test
@ScreenshotTest
fun request() {
val sut = activityRule.launchActivity(Intent(PassCodeActivity.ACTION_REQUEST_WITH_RESULT))
waitForIdleSync()
sut.runOnUiThread { sut.binding.txt0.clearFocus() }
Espresso.closeSoftKeyboard()
shortSleep()
waitForIdleSync()
screenshot(sut)
}
@Test
@ScreenshotTest
fun delete() {
val sut = activityRule.launchActivity(Intent(PassCodeActivity.ACTION_CHECK_WITH_RESULT))
waitForIdleSync()
sut.runOnUiThread { sut.binding.txt0.clearFocus() }
Espresso.closeSoftKeyboard()
shortSleep()
waitForIdleSync()
screenshot(sut)
}
}

View file

@ -0,0 +1,35 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.activity
import android.app.Activity
import androidx.test.espresso.intent.rule.IntentsTestRule
import com.owncloud.android.AbstractIT
import com.owncloud.android.utils.ScreenshotTest
import org.junit.Rule
import org.junit.Test
class ReceiveExternalFilesActivityIT : AbstractIT() {
@get:Rule
val activityRule = IntentsTestRule(ReceiveExternalFilesActivity::class.java, true, false)
@Test
@ScreenshotTest
fun open() {
val sut: Activity = activityRule.launchActivity(null)
screenshot(sut)
}
@Test
@ScreenshotTest
fun openMultiAccount() {
val secondAccount = createAccount("secondtest@https://nextcloud.localhost")
open()
removeAccount(secondAccount)
}
}

View file

@ -0,0 +1,98 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.activity
import android.content.Intent
import androidx.test.espresso.intent.rule.IntentsTestRule
import com.nextcloud.test.GrantStoragePermissionRule
import com.owncloud.android.AbstractIT
import com.owncloud.android.utils.FileStorageUtils
import com.owncloud.android.utils.ScreenshotTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.io.File
class UploadFilesActivityIT : AbstractIT() {
@get:Rule
var activityRule = IntentsTestRule(UploadFilesActivity::class.java, true, false)
@get:Rule
var permissionRule = GrantStoragePermissionRule.grant()
private val directories = listOf("A", "B", "C", "D")
.map { File("${FileStorageUtils.getTemporalPath(account.name)}${File.separator}$it") }
@Before
fun setUp() {
directories.forEach { it.mkdirs() }
}
@After
fun tearDown() {
directories.forEach { it.deleteRecursively() }
}
@Test
@ScreenshotTest
fun noneSelected() {
val sut: UploadFilesActivity = activityRule.launchActivity(null)
sut.runOnUiThread {
sut.fileListFragment.setFiles(
directories +
listOf(
File("1.txt"),
File("2.pdf"),
File("3.mp3")
)
)
}
waitForIdleSync()
longSleep()
screenshot(sut.fileListFragment.binding.listRoot)
}
@Test
@ScreenshotTest
fun localFolderPickerMode() {
val sut: UploadFilesActivity = activityRule.launchActivity(
Intent().apply {
putExtra(
UploadFilesActivity.KEY_LOCAL_FOLDER_PICKER_MODE,
true
)
putExtra(
UploadFilesActivity.REQUEST_CODE_KEY,
FileDisplayActivity.REQUEST_CODE__SELECT_FILES_FROM_FILE_SYSTEM
)
}
)
sut.runOnUiThread {
sut.fileListFragment.setFiles(
directories
)
}
waitForIdleSync()
screenshot(sut)
}
fun fileSelected() {
val sut: UploadFilesActivity = activityRule.launchActivity(null)
// TODO select one
screenshot(sut)
}
}

View file

@ -0,0 +1,50 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Andy Scherzinger <info@andy-scherzinger.de>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.activity;
import android.content.Intent;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.lib.common.UserInfo;
import com.owncloud.android.utils.ScreenshotTest;
import org.junit.Rule;
import org.junit.Test;
import androidx.test.espresso.intent.rule.IntentsTestRule;
public class UserInfoActivityIT extends AbstractIT {
@Rule
public IntentsTestRule<UserInfoActivity> activityRule = new IntentsTestRule<>(UserInfoActivity.class,
true,
false);
@Test
@ScreenshotTest
public void fullUserInfoDetail() {
final Intent intent = new Intent(targetContext, UserInfoActivity.class);
intent.putExtra(UserInfoActivity.KEY_ACCOUNT, user);
UserInfo userInfo = new UserInfo("test",
true,
"Firstname Familyname",
"oss@rocks.com",
"+49 7613 672 255",
"Awesome Place Av.",
"https://www.nextcloud.com",
"nextclouders",
null,
null
);
intent.putExtra(UserInfoActivity.KEY_USER_DATA, userInfo);
UserInfoActivity sut = activityRule.launchActivity(intent);
shortSleep();
shortSleep();
screenshot(sut);
}
}

View file

@ -0,0 +1,66 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.adapter
import com.owncloud.android.AbstractIT
import com.owncloud.android.datamodel.OCFile
import org.junit.Assert.assertEquals
import org.junit.Test
@Suppress("MagicNumber")
class OCFileListAdapterIT : AbstractIT() {
@Test
fun testParseMedia() {
// empty start
storageManager.deleteAllFiles()
val startDate: Long = 0
val endDate: Long = 3642752043
assertEquals(0, storageManager.getGalleryItems(startDate, endDate).size)
// create dummy files
OCFile("/test.txt").apply {
mimeType = "text/plain"
}.let {
storageManager.saveFile(it)
}
OCFile("/image.png").apply {
mimeType = "image/png"
modificationTimestamp = 1000000
}.let {
storageManager.saveFile(it)
}
OCFile("/image2.png").apply {
mimeType = "image/png"
modificationTimestamp = 1000050
}.let {
storageManager.saveFile(it)
}
OCFile("/video.mpg").apply {
mimeType = "video/mpg"
modificationTimestamp = 1000045
}.let {
storageManager.saveFile(it)
}
OCFile("/video2.avi").apply {
mimeType = "video/avi"
modificationTimestamp = endDate + 10
}.let {
storageManager.saveFile(it)
}
// list of remoteFiles
assertEquals(5, storageManager.allFiles.size)
assertEquals(3, storageManager.getGalleryItems(startDate, endDate).size)
assertEquals(4, storageManager.allGalleryItems.size)
}
}

View file

@ -0,0 +1,630 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.dialog;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Intent;
import android.net.http.SslCertificate;
import android.net.http.SslError;
import android.os.Looper;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.SslErrorHandler;
import android.widget.TextView;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.gson.Gson;
import com.nextcloud.android.common.ui.color.ColorUtil;
import com.nextcloud.android.common.ui.theme.MaterialSchemes;
import com.nextcloud.android.common.ui.theme.MaterialSchemesImpl;
import com.nextcloud.android.lib.resources.profile.Action;
import com.nextcloud.android.lib.resources.profile.HoverCard;
import com.nextcloud.client.account.RegisteredUser;
import com.nextcloud.client.account.Server;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.device.DeviceInfo;
import com.nextcloud.client.documentscan.AppScanOptionalFeature;
import com.nextcloud.ui.ChooseAccountDialogFragment;
import com.nextcloud.ui.fileactions.FileActionsBottomSheet;
import com.nextcloud.utils.EditorUtils;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.Creator;
import com.owncloud.android.lib.common.DirectEditing;
import com.owncloud.android.lib.common.Editor;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.accounts.AccountTypeUtils;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.resources.status.CapabilityBooleanType;
import com.owncloud.android.lib.resources.status.OCCapability;
import com.owncloud.android.lib.resources.status.OwnCloudVersion;
import com.owncloud.android.lib.resources.users.Status;
import com.owncloud.android.lib.resources.users.StatusType;
import com.owncloud.android.ui.activity.FileDisplayActivity;
import com.owncloud.android.ui.fragment.OCFileListBottomSheetActions;
import com.owncloud.android.ui.fragment.OCFileListBottomSheetDialog;
import com.owncloud.android.ui.fragment.ProfileBottomSheetDialog;
import com.owncloud.android.utils.MimeTypeUtil;
import com.owncloud.android.utils.ScreenshotTest;
import com.owncloud.android.utils.theme.CapabilityUtils;
import com.owncloud.android.utils.theme.MaterialSchemesProvider;
import com.owncloud.android.utils.theme.ViewThemeUtils;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mockito;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import kotlin.Unit;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
public class DialogFragmentIT extends AbstractIT {
private final String SERVER_URL = "https://nextcloud.localhost";
@Rule public IntentsTestRule<FileDisplayActivity> activityRule =
new IntentsTestRule<>(FileDisplayActivity.class, true, false);
private FileDisplayActivity getFileDisplayActivity() {
Intent intent = new Intent(targetContext, FileDisplayActivity.class);
return activityRule.launchActivity(intent);
}
@After
public void quitLooperIfNeeded() {
if (Looper.myLooper() != null) {
Looper.myLooper().quitSafely();
}
}
@Test
@ScreenshotTest
public void testRenameFileDialog() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(new OCFile("/Test/"),
new OCFile("/"));
showDialog(dialog);
}
@Test
@ScreenshotTest
public void testLoadingDialog() {
LoadingDialog dialog = LoadingDialog.newInstance("Wait…");
showDialog(dialog);
}
@Test
@ScreenshotTest
public void testConfirmationDialogWithOneAction() {
ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(R.string.upload_list_empty_text_auto_upload, new String[]{}, R.string.filedetails_sync_file, R.string.common_ok, -1, -1);
showDialog(dialog);
}
@Test
@ScreenshotTest
public void testConfirmationDialogWithTwoAction() {
ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(R.string.upload_list_empty_text_auto_upload, new String[]{}, R.string.filedetails_sync_file, R.string.common_ok, R.string.common_cancel, -1);
showDialog(dialog);
}
@Test
@ScreenshotTest
public void testConfirmationDialogWithThreeAction() {
ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(R.string.upload_list_empty_text_auto_upload, new String[]{}, R.string.filedetails_sync_file, R.string.common_ok, R.string.common_cancel, R.string.common_confirm);
showDialog(dialog);
}
@Test
@ScreenshotTest
public void testConfirmationDialogWithThreeActionRTL() {
enableRTL();
ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(R.string.upload_list_empty_text_auto_upload, new String[] { }, -1, R.string.common_ok, R.string.common_cancel, R.string.common_confirm);
showDialog(dialog);
resetLocale();
}
@Test
@ScreenshotTest
public void testRemoveFileDialog() {
RemoveFilesDialogFragment dialog = RemoveFilesDialogFragment.newInstance(new OCFile("/Test.md"));
showDialog(dialog);
}
@Test
@ScreenshotTest
public void testRemoveFilesDialog() {
ArrayList<OCFile> toDelete = new ArrayList<>();
toDelete.add(new OCFile("/Test.md"));
toDelete.add(new OCFile("/Document.odt"));
RemoveFilesDialogFragment dialog = RemoveFilesDialogFragment.newInstance(toDelete);
showDialog(dialog);
}
@Test
@ScreenshotTest
public void testRemoveFolderDialog() {
RemoveFilesDialogFragment dialog = RemoveFilesDialogFragment.newInstance(new OCFile("/Folder/"));
showDialog(dialog);
}
@Test
@ScreenshotTest
public void testRemoveFoldersDialog() {
ArrayList<OCFile> toDelete = new ArrayList<>();
toDelete.add(new OCFile("/Folder/"));
toDelete.add(new OCFile("/Documents/"));
RemoveFilesDialogFragment dialog = RemoveFilesDialogFragment.newInstance(toDelete);
showDialog(dialog);
}
@Test
@ScreenshotTest
public void testNewFolderDialog() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
CreateFolderDialogFragment sut = CreateFolderDialogFragment.newInstance(new OCFile("/"));
showDialog(sut);
}
@Test
@ScreenshotTest
public void testEnforcedPasswordDialog() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
SharePasswordDialogFragment sut = SharePasswordDialogFragment.newInstance(new OCFile("/"), true, false);
showDialog(sut);
}
@Test
@ScreenshotTest
public void testOptionalPasswordDialog() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
SharePasswordDialogFragment sut = SharePasswordDialogFragment.newInstance(new OCFile("/"), true, true);
showDialog(sut);
}
@Test
@ScreenshotTest
public void testAccountChooserDialog() throws AccountUtils.AccountNotFoundException {
FileDisplayActivity activity = getFileDisplayActivity();
UserAccountManager userAccountManager = activity.getUserAccountManager();
AccountManager accountManager = AccountManager.get(targetContext);
for (Account account : accountManager.getAccountsByType(MainApp.getAccountType(targetContext))) {
accountManager.removeAccountExplicitly(account);
}
Account newAccount = new Account("test@https://nextcloud.localhost", MainApp.getAccountType(targetContext));
accountManager.addAccountExplicitly(newAccount, "password", null);
accountManager.setUserData(newAccount, AccountUtils.Constants.KEY_OC_BASE_URL, SERVER_URL);
accountManager.setUserData(newAccount, AccountUtils.Constants.KEY_USER_ID, "test");
accountManager.setAuthToken(newAccount, AccountTypeUtils.getAuthTokenTypePass(newAccount.type), "password");
User newUser = userAccountManager.getUser(newAccount.name).orElseThrow(RuntimeException::new);
userAccountManager.setCurrentOwnCloudAccount(newAccount.name);
Account newAccount2 = new Account("user1@nextcloud.localhost", MainApp.getAccountType(targetContext));
accountManager.addAccountExplicitly(newAccount2, "password", null);
accountManager.setUserData(newAccount2, AccountUtils.Constants.KEY_OC_BASE_URL, SERVER_URL);
accountManager.setUserData(newAccount2, AccountUtils.Constants.KEY_USER_ID, "user1");
accountManager.setUserData(newAccount2, AccountUtils.Constants.KEY_OC_VERSION, "20.0.0");
accountManager.setAuthToken(newAccount2, AccountTypeUtils.getAuthTokenTypePass(newAccount.type), "password");
FileDataStorageManager fileDataStorageManager = new FileDataStorageManager(newUser,
targetContext.getContentResolver());
OCCapability capability = new OCCapability();
capability.setUserStatus(CapabilityBooleanType.TRUE);
capability.setUserStatusSupportsEmoji(CapabilityBooleanType.TRUE);
fileDataStorageManager.saveCapabilities(capability);
ChooseAccountDialogFragment sut =
ChooseAccountDialogFragment.newInstance(new RegisteredUser(newAccount,
new OwnCloudAccount(newAccount, targetContext),
new Server(URI.create(SERVER_URL),
OwnCloudVersion.nextcloud_20)));
showDialog(activity, sut);
activity.runOnUiThread(() -> sut.setStatus(new Status(StatusType.DND,
"Busy fixing 🐛…",
"",
-1),
targetContext));
waitForIdleSync();
shortSleep();
screenshot(sut, "dnd");
activity.runOnUiThread(() -> sut.setStatus(new Status(StatusType.ONLINE,
"",
"",
-1),
targetContext));
waitForIdleSync();
shortSleep();
screenshot(sut, "online");
activity.runOnUiThread(() -> sut.setStatus(new Status(StatusType.ONLINE,
"Let's have some fun",
"🎉",
-1),
targetContext));
waitForIdleSync();
shortSleep();
screenshot(sut, "fun");
activity.runOnUiThread(() -> sut.setStatus(new Status(StatusType.OFFLINE, "", "", -1), targetContext));
waitForIdleSync();
shortSleep();
screenshot(sut, "offline");
activity.runOnUiThread(() -> sut.setStatus(new Status(StatusType.AWAY, "Vacation", "🌴", -1), targetContext));
waitForIdleSync();
shortSleep();
screenshot(sut, "away");
}
@Test
@ScreenshotTest
public void testAccountChooserDialogWithStatusDisabled() throws AccountUtils.AccountNotFoundException {
AccountManager accountManager = AccountManager.get(targetContext);
for (Account account : accountManager.getAccounts()) {
accountManager.removeAccountExplicitly(account);
}
Account newAccount = new Account("test@https://nextcloud.localhost", MainApp.getAccountType(targetContext));
accountManager.addAccountExplicitly(newAccount, "password", null);
accountManager.setUserData(newAccount, AccountUtils.Constants.KEY_OC_BASE_URL, SERVER_URL);
accountManager.setUserData(newAccount, AccountUtils.Constants.KEY_USER_ID, "test");
accountManager.setAuthToken(newAccount, AccountTypeUtils.getAuthTokenTypePass(newAccount.type), "password");
FileDisplayActivity fda = getFileDisplayActivity();
UserAccountManager userAccountManager = fda.getUserAccountManager();
User newUser = userAccountManager.getUser(newAccount.name).get();
FileDataStorageManager fileDataStorageManager = new FileDataStorageManager(newUser,
targetContext.getContentResolver());
OCCapability capability = new OCCapability();
capability.setUserStatus(CapabilityBooleanType.FALSE);
fileDataStorageManager.saveCapabilities(capability);
ChooseAccountDialogFragment sut =
ChooseAccountDialogFragment.newInstance(new RegisteredUser(newAccount,
new OwnCloudAccount(newAccount, targetContext),
new Server(URI.create(SERVER_URL),
OwnCloudVersion.nextcloud_20)));
showDialog(fda, sut);
}
@Test
@ScreenshotTest
public void testBottomSheet() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
OCFileListBottomSheetActions action = new OCFileListBottomSheetActions() {
@Override
public void createFolder() {
}
@Override
public void uploadFromApp() {
}
@Override
public void uploadFiles() {
}
@Override
public void newDocument() {
}
@Override
public void newSpreadsheet() {
}
@Override
public void newPresentation() {
}
@Override
public void directCameraUpload() {
}
@Override
public void scanDocUpload() {
}
@Override
public void showTemplate(Creator creator, String headline) {
}
@Override
public void createRichWorkspace() {
}
};
DeviceInfo info = new DeviceInfo();
OCFile ocFile = new OCFile("/test.md");
Intent intent = new Intent(targetContext, FileDisplayActivity.class);
FileDisplayActivity fda = activityRule.launchActivity(intent);
// add direct editing info
DirectEditing directEditing = new DirectEditing();
directEditing.getCreators().put("1", new Creator("1",
"text",
"text file",
".md",
"application/octet-stream",
false));
directEditing.getCreators().put("2", new Creator("2",
"md",
"markdown file",
".md",
"application/octet-stream",
false));
directEditing.getEditors().put("text",
new Editor("1",
"Text",
new ArrayList<>(Collections.singletonList(MimeTypeUtil.MIMETYPE_TEXT_MARKDOWN)),
new ArrayList<>(),
false));
String json = new Gson().toJson(directEditing);
new ArbitraryDataProviderImpl(targetContext).storeOrUpdateKeyValue(user.getAccountName(),
ArbitraryDataProvider.DIRECT_EDITING,
json);
// activate templates
OCCapability capability = fda.getCapabilities();
capability.setRichDocuments(CapabilityBooleanType.TRUE);
capability.setRichDocumentsDirectEditing(CapabilityBooleanType.TRUE);
capability.setRichDocumentsTemplatesAvailable(CapabilityBooleanType.TRUE);
capability.setAccountName(user.getAccountName());
CapabilityUtils.updateCapability(capability);
AppScanOptionalFeature appScanOptionalFeature = new AppScanOptionalFeature() {
@NonNull
@Override
public ActivityResultContract<Unit, String> getScanContract() {
throw new UnsupportedOperationException("Document scan is not available");
}
};
MaterialSchemesProvider materialSchemesProvider = new MaterialSchemesProvider() {
@NonNull
@Override
public MaterialSchemes getMaterialSchemesForUser(@NonNull User user) {
return null;
}
@NonNull
@Override
public MaterialSchemes getMaterialSchemesForCapability(@NonNull OCCapability capability) {
return null;
}
@NonNull
@Override
public MaterialSchemes getMaterialSchemesForCurrentUser() {
return new MaterialSchemesImpl(R.color.primary, false);
}
@NonNull
@Override
public MaterialSchemes getDefaultMaterialSchemes() {
return null;
}
@NonNull
@Override
public MaterialSchemes getMaterialSchemesForPrimaryBackground() {
return null;
}
};
ViewThemeUtils viewThemeUtils = new ViewThemeUtils(materialSchemesProvider.getMaterialSchemesForCurrentUser(),
new ColorUtil(targetContext));
EditorUtils editorUtils = new EditorUtils(new ArbitraryDataProviderImpl(targetContext));
OCFileListBottomSheetDialog sut = new OCFileListBottomSheetDialog(fda,
action,
info,
user,
ocFile,
fda.themeUtils,
viewThemeUtils,
editorUtils,
appScanOptionalFeature);
fda.runOnUiThread(sut::show);
getInstrumentation().waitForIdleSync();
shortSleep();
sut.getBehavior().setState(BottomSheetBehavior.STATE_EXPANDED);
getInstrumentation().waitForIdleSync();
shortSleep();
ViewGroup viewGroup = sut.getWindow().findViewById(android.R.id.content);
hideCursors(viewGroup);
screenshot(Objects.requireNonNull(sut.getWindow()).getDecorView());
}
@Test
@ScreenshotTest
public void testProfileBottomSheet() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
// Fixed values for HoverCard
List<Action> actions = new ArrayList<>();
actions.add(new Action("profile",
"View profile",
"https://dev.nextcloud.com/core/img/actions/profile.svg",
"https://dev.nextcloud.com/index.php/u/christine"));
actions.add(new Action("core",
"christine.scott@nextcloud.com",
"https://dev.nextcloud.com/core/img/actions/mail.svg",
"mailto:christine.scott@nextcloud.com"));
actions.add(new Action("spreed",
"Talk to Christine",
"https://dev.nextcloud.com/apps/spreed/img/app-dark.svg",
"https://dev.nextcloud.com/apps/spreed/?callUser=christine"
));
HoverCard hoverCard = new HoverCard("christine", "Christine Scott", actions);
// show dialog
Intent intent = new Intent(targetContext, FileDisplayActivity.class);
FileDisplayActivity fda = activityRule.launchActivity(intent);
ProfileBottomSheetDialog sut = new ProfileBottomSheetDialog(fda,
user,
hoverCard,
fda.viewThemeUtils);
fda.runOnUiThread(sut::show);
waitForIdleSync();
screenshot(sut.getWindow().getDecorView());
}
@Test
@ScreenshotTest
public void testSslUntrustedCertDialog() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
final SslCertificate certificate = new SslCertificate("foo", "bar", "2022/01/10", "2022/01/30");
final SslError sslError = new SslError(SslError.SSL_UNTRUSTED, certificate);
final SslErrorHandler handler = Mockito.mock(SslErrorHandler.class);
SslUntrustedCertDialog sut = SslUntrustedCertDialog.newInstanceForEmptySslError(sslError, handler);
showDialog(sut);
}
@Test
@ScreenshotTest
public void testStoragePermissionDialog() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
StoragePermissionDialogFragment sut = StoragePermissionDialogFragment.Companion.newInstance(false);
showDialog(sut);
}
@Test
@ScreenshotTest
public void testFileActionsBottomSheet() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
OCFile ocFile = new OCFile("/test.md");
final FileActionsBottomSheet sut = FileActionsBottomSheet.newInstance(ocFile, false);
showDialog(sut);
}
private FileDisplayActivity showDialog(DialogFragment dialog) {
Intent intent = new Intent(targetContext, FileDisplayActivity.class);
FileDisplayActivity sut = activityRule.getActivity();
if (sut == null) {
sut = activityRule.launchActivity(intent);
}
return showDialog(sut, dialog);
}
private FileDisplayActivity showDialog(FileDisplayActivity sut, DialogFragment dialog) {
dialog.show(sut.getSupportFragmentManager(), "");
getInstrumentation().waitForIdleSync();
shortSleep();
ViewGroup viewGroup = dialog.requireDialog().getWindow().findViewById(android.R.id.content);
hideCursors(viewGroup);
screenshot(Objects.requireNonNull(dialog.requireDialog().getWindow()).getDecorView());
return sut;
}
private void hideCursors(ViewGroup viewGroup) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
if (child instanceof ViewGroup) {
hideCursors((ViewGroup) child);
}
if (child instanceof TextView) {
((TextView) child).setCursorVisible(false);
}
}
}
}

View file

@ -0,0 +1,94 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.dialog
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.platform.app.InstrumentationRegistry
import com.nextcloud.test.TestActivity
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.utils.ScreenshotTest
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
class SendFilesDialogTest : AbstractIT() {
companion object {
private val FILES_SAME_TYPE = setOf(
OCFile("/1.jpg").apply {
mimeType = "image/jpg"
},
OCFile("/2.jpg").apply {
mimeType = "image/jpg"
}
)
private val FILES_MIXED_TYPE = setOf(
OCFile("/1.jpg").apply {
mimeType = "image/jpg"
},
OCFile("/2.pdf").apply {
mimeType = "application/pdf"
},
OCFile("/3.png").apply {
mimeType = "image/png"
}
)
}
@get:Rule
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
private fun showDialog(files: Set<OCFile>): SendFilesDialog {
val activity = testActivityRule.launchActivity(null)
val fm: FragmentManager = activity.supportFragmentManager
val ft = fm.beginTransaction()
ft.addToBackStack(null)
val sut = SendFilesDialog.newInstance(files)
sut.show(ft, "TAG_SEND_SHARE_DIALOG")
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
shortSleep()
return sut
}
@Test
fun showDialog() {
val sut = showDialog(FILES_SAME_TYPE)
val recyclerview: RecyclerView = sut.requireDialog().findViewById(R.id.send_button_recycler_view)
Assert.assertNotNull("Adapter is null", recyclerview.adapter)
Assert.assertNotEquals("Send button list is empty", 0, recyclerview.adapter!!.itemCount)
}
@Test
@ScreenshotTest
fun showDialog_Screenshot() {
val sut = showDialog(FILES_SAME_TYPE)
sut.requireDialog().window?.decorView.let { screenshot(it) }
}
@Test
fun showDialogDifferentTypes() {
val sut = showDialog(FILES_MIXED_TYPE)
val recyclerview: RecyclerView = sut.requireDialog().findViewById(R.id.send_button_recycler_view)
Assert.assertNotNull("Adapter is null", recyclerview.adapter)
Assert.assertNotEquals("Send button list is empty", 0, recyclerview.adapter!!.itemCount)
}
@Test
@ScreenshotTest
fun showDialogDifferentTypes_Screenshot() {
val sut = showDialog(FILES_MIXED_TYPE)
sut.requireDialog().window?.decorView.let { screenshot(it) }
}
}

View file

@ -0,0 +1,46 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.dialog
import androidx.fragment.app.FragmentManager
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.platform.app.InstrumentationRegistry
import com.nextcloud.test.TestActivity
import com.owncloud.android.AbstractIT
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.resources.status.OCCapability
import com.owncloud.android.utils.ScreenshotTest
import org.junit.Rule
import org.junit.Test
class SendShareDialogTest : AbstractIT() {
@get:Rule
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
@Test
@ScreenshotTest
fun showDialog() {
val activity = testActivityRule.launchActivity(null)
val fm: FragmentManager = activity.supportFragmentManager
val ft = fm.beginTransaction()
ft.addToBackStack(null)
val file = OCFile("/1.jpg").apply {
mimeType = "image/jpg"
}
val sut = SendShareDialog.newInstance(file, false, OCCapability())
sut.show(ft, "TAG_SEND_SHARE_DIALOG")
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
shortSleep()
shortSleep()
sut.requireDialog().window?.decorView.let { screenshot(it) }
}
}

View file

@ -0,0 +1,79 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.dialog
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
import com.nextcloud.test.TestActivity
import com.owncloud.android.AbstractIT
import com.owncloud.android.utils.ScreenshotTest
import org.junit.Rule
import org.junit.Test
class SetupEncryptionDialogFragmentIT : AbstractIT() {
@get:Rule
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
@Test
@ScreenshotTest
fun showMnemonic() {
val activity = testActivityRule.launchActivity(null)
val sut = SetupEncryptionDialogFragment.newInstance(user, 0)
sut.show(activity.supportFragmentManager, "1")
val keyWords = arrayListOf(
"ability",
"able",
"about",
"above",
"absent",
"absorb",
"abstract",
"absurd",
"abuse",
"access",
"accident",
"account",
"accuse"
)
shortSleep()
runOnUiThread {
sut.setMnemonic(keyWords)
sut.showMnemonicInfo()
}
waitForIdleSync()
screenshot(sut.requireDialog().window!!.decorView)
}
@Test
@ScreenshotTest
fun error() {
val activity = testActivityRule.launchActivity(null)
val sut = SetupEncryptionDialogFragment.newInstance(user, 0)
sut.show(activity.supportFragmentManager, "1")
shortSleep()
runOnUiThread {
sut.errorSavingKeys()
}
shortSleep()
waitForIdleSync()
screenshot(sut.requireDialog().window!!.decorView)
}
}

View file

@ -0,0 +1,59 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.dialog;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.ui.activity.FileDisplayActivity;
import com.owncloud.android.utils.ScreenshotTest;
import org.junit.Rule;
import org.junit.Test;
import java.util.Objects;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
public class SyncFileNotEnoughSpaceDialogFragmentTest extends AbstractIT {
@Rule public IntentsTestRule<FileDisplayActivity> activityRule = new IntentsTestRule<>(FileDisplayActivity.class,
true,
false);
@Test
@ScreenshotTest
public void showNotEnoughSpaceDialogForFolder() {
FileDisplayActivity test = activityRule.launchActivity(null);
OCFile ocFile = new OCFile("/Document/");
ocFile.setFileLength(5000000);
ocFile.setFolder();
SyncFileNotEnoughSpaceDialogFragment dialog = SyncFileNotEnoughSpaceDialogFragment.newInstance(ocFile, 1000);
dialog.show(test.getListOfFilesFragment().getFragmentManager(), "1");
getInstrumentation().waitForIdleSync();
screenshot(Objects.requireNonNull(dialog.requireDialog().getWindow()).getDecorView());
}
@Test
@ScreenshotTest
public void showNotEnoughSpaceDialogForFile() {
FileDisplayActivity test = activityRule.launchActivity(null);
OCFile ocFile = new OCFile("/Video.mp4");
ocFile.setFileLength(1000000);
SyncFileNotEnoughSpaceDialogFragment dialog = SyncFileNotEnoughSpaceDialogFragment.newInstance(ocFile, 2000);
dialog.show(test.getListOfFilesFragment().getFragmentManager(), "2");
getInstrumentation().waitForIdleSync();
screenshot(Objects.requireNonNull(dialog.requireDialog().getWindow()).getDecorView());
}
}

View file

@ -0,0 +1,171 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.fragment
import android.graphics.BitmapFactory
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
import com.nextcloud.test.TestActivity
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import com.owncloud.android.lib.resources.users.StatusType
import com.owncloud.android.ui.TextDrawable
import com.owncloud.android.utils.BitmapUtils
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.ScreenshotTest
import org.junit.Rule
import org.junit.Test
class AvatarIT : AbstractIT() {
@get:Rule
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
@Test
@ScreenshotTest
fun showAvatars() {
val avatarRadius = targetContext.resources.getDimension(R.dimen.list_item_avatar_icon_radius)
val width = DisplayUtils.convertDpToPixel(2 * avatarRadius, targetContext)
val sut = testActivityRule.launchActivity(null)
val fragment = AvatarTestFragment()
sut.addFragment(fragment)
runOnUiThread {
fragment.addAvatar("Admin", avatarRadius, width, targetContext)
fragment.addAvatar("Test Server Admin", avatarRadius, width, targetContext)
fragment.addAvatar("Cormier Paulette", avatarRadius, width, targetContext)
fragment.addAvatar("winston brent", avatarRadius, width, targetContext)
fragment.addAvatar("Baker James Lorena", avatarRadius, width, targetContext)
fragment.addAvatar("Baker James Lorena", avatarRadius, width, targetContext)
fragment.addAvatar("email@nextcloud.localhost", avatarRadius, width, targetContext)
}
shortSleep()
waitForIdleSync()
screenshot(sut)
}
@Test
@ScreenshotTest
fun showAvatarsWithStatus() {
val avatarRadius = targetContext.resources.getDimension(R.dimen.list_item_avatar_icon_radius)
val width = DisplayUtils.convertDpToPixel(2 * avatarRadius, targetContext)
val sut = testActivityRule.launchActivity(null)
val fragment = AvatarTestFragment()
val paulette = BitmapFactory.decodeFile(getFile("paulette.jpg").absolutePath)
val christine = BitmapFactory.decodeFile(getFile("christine.jpg").absolutePath)
val textBitmap = BitmapUtils.drawableToBitmap(TextDrawable.createNamedAvatar("Admin", avatarRadius))
sut.addFragment(fragment)
runOnUiThread {
fragment.addBitmap(
BitmapUtils.createAvatarWithStatus(paulette, StatusType.ONLINE, "😘", targetContext),
width * 2,
1,
targetContext
)
fragment.addBitmap(
BitmapUtils.createAvatarWithStatus(christine, StatusType.ONLINE, "☁️", targetContext),
width * 2,
1,
targetContext
)
fragment.addBitmap(
BitmapUtils.createAvatarWithStatus(christine, StatusType.ONLINE, "🌴️", targetContext),
width * 2,
1,
targetContext
)
fragment.addBitmap(
BitmapUtils.createAvatarWithStatus(christine, StatusType.ONLINE, "", targetContext),
width * 2,
1,
targetContext
)
fragment.addBitmap(
BitmapUtils.createAvatarWithStatus(paulette, StatusType.DND, "", targetContext),
width * 2,
1,
targetContext
)
fragment.addBitmap(
BitmapUtils.createAvatarWithStatus(christine, StatusType.AWAY, "", targetContext),
width * 2,
1,
targetContext
)
fragment.addBitmap(
BitmapUtils.createAvatarWithStatus(paulette, StatusType.OFFLINE, "", targetContext),
width * 2,
1,
targetContext
)
fragment.addBitmap(
BitmapUtils.createAvatarWithStatus(textBitmap, StatusType.ONLINE, "😘", targetContext),
width,
2,
targetContext
)
fragment.addBitmap(
BitmapUtils.createAvatarWithStatus(textBitmap, StatusType.ONLINE, "☁️", targetContext),
width,
2,
targetContext
)
fragment.addBitmap(
BitmapUtils.createAvatarWithStatus(textBitmap, StatusType.ONLINE, "🌴️", targetContext),
width,
2,
targetContext
)
fragment.addBitmap(
BitmapUtils.createAvatarWithStatus(textBitmap, StatusType.ONLINE, "", targetContext),
width,
2,
targetContext
)
fragment.addBitmap(
BitmapUtils.createAvatarWithStatus(textBitmap, StatusType.DND, "", targetContext),
width,
2,
targetContext
)
fragment.addBitmap(
BitmapUtils.createAvatarWithStatus(textBitmap, StatusType.AWAY, "", targetContext),
width,
2,
targetContext
)
fragment.addBitmap(
BitmapUtils.createAvatarWithStatus(textBitmap, StatusType.OFFLINE, "", targetContext),
width,
2,
targetContext
)
}
shortSleep()
waitForIdleSync()
screenshot(sut)
}
}

View file

@ -0,0 +1,69 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.fragment
import android.content.Context
import android.graphics.Bitmap
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.fragment.app.Fragment
import com.owncloud.android.R
import com.owncloud.android.ui.TextDrawable
internal class AvatarTestFragment : Fragment() {
lateinit var list1: LinearLayout
lateinit var list2: LinearLayout
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater.inflate(R.layout.avatar_fragment, null)
list1 = view.findViewById(R.id.avatar_list1)
list2 = view.findViewById(R.id.avatar_list2)
return view
}
fun addAvatar(name: String, avatarRadius: Float, width: Int, targetContext: Context) {
val margin = padding
val imageView = ImageView(targetContext)
imageView.setImageDrawable(TextDrawable.createNamedAvatar(name, avatarRadius))
val layoutParams: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams(width, width)
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
layoutParams.setMargins(margin, margin, margin, margin)
imageView.layoutParams = layoutParams
list1.addView(imageView)
}
fun addBitmap(bitmap: Bitmap, width: Int, list: Int, targetContext: Context) {
val margin = padding
val imageView = ImageView(targetContext)
imageView.setImageBitmap(bitmap)
val layoutParams: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams(width, width)
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
layoutParams.setMargins(margin, margin, margin, margin)
imageView.layoutParams = layoutParams
if (list == 1) {
list1.addView(imageView)
} else {
list2.addView(imageView)
}
}
companion object {
private const val padding = 10
}
}

View file

@ -0,0 +1,100 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2021 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.fragment
import android.Manifest
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.rule.GrantPermissionRule
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.ui.activity.ContactsPreferenceActivity
import com.owncloud.android.ui.fragment.contactsbackup.BackupListFragment
import com.owncloud.android.utils.ScreenshotTest
import org.junit.Rule
import org.junit.Test
class BackupListFragmentIT : AbstractIT() {
@get:Rule
val testActivityRule = IntentsTestRule(ContactsPreferenceActivity::class.java, true, false)
@get:Rule
val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.READ_CALENDAR)
@Test
@ScreenshotTest
fun showLoading() {
val sut = testActivityRule.launchActivity(null)
val file = OCFile("/")
val transaction = sut.supportFragmentManager.beginTransaction()
transaction.replace(R.id.frame_container, BackupListFragment.newInstance(file, user))
transaction.commit()
waitForIdleSync()
screenshot(sut)
}
@Test
@ScreenshotTest
fun showContactList() {
val sut = testActivityRule.launchActivity(null)
val transaction = sut.supportFragmentManager.beginTransaction()
val file = getFile("vcard.vcf")
val ocFile = OCFile("/vcard.vcf")
ocFile.storagePath = file.absolutePath
ocFile.mimeType = "text/vcard"
transaction.replace(R.id.frame_container, BackupListFragment.newInstance(ocFile, user))
transaction.commit()
waitForIdleSync()
shortSleep()
screenshot(sut)
}
@Test
@ScreenshotTest
fun showCalendarList() {
val sut = testActivityRule.launchActivity(null)
val transaction = sut.supportFragmentManager.beginTransaction()
val file = getFile("calendar.ics")
val ocFile = OCFile("/Private calender_2020-09-01_10-45-20.ics.ics")
ocFile.storagePath = file.absolutePath
ocFile.mimeType = "text/calendar"
transaction.replace(R.id.frame_container, BackupListFragment.newInstance(ocFile, user))
transaction.commit()
waitForIdleSync()
screenshot(sut)
}
@Test
@ScreenshotTest
fun showCalendarAndContactsList() {
val sut = testActivityRule.launchActivity(null)
val transaction = sut.supportFragmentManager.beginTransaction()
val calendarFile = getFile("calendar.ics")
val calendarOcFile = OCFile("/Private calender_2020-09-01_10-45-20.ics")
calendarOcFile.storagePath = calendarFile.absolutePath
calendarOcFile.mimeType = "text/calendar"
val contactFile = getFile("vcard.vcf")
val contactOcFile = OCFile("/vcard.vcf")
contactOcFile.storagePath = contactFile.absolutePath
contactOcFile.mimeType = "text/vcard"
val files = arrayOf(calendarOcFile, contactOcFile)
transaction.replace(R.id.frame_container, BackupListFragment.newInstance(files, user))
transaction.commit()
waitForIdleSync()
screenshot(sut)
}
}

View file

@ -0,0 +1,198 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.fragment
import androidx.test.espresso.intent.rule.IntentsTestRule
import com.nextcloud.test.TestActivity
import com.nextcloud.ui.ImageDetailFragment
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.resources.activities.model.Activity
import com.owncloud.android.lib.resources.activities.model.RichElement
import com.owncloud.android.lib.resources.activities.model.RichObject
import com.owncloud.android.lib.resources.activities.models.PreviewObject
import com.owncloud.android.utils.ScreenshotTest
import org.junit.Rule
import org.junit.Test
import java.util.GregorianCalendar
class FileDetailFragmentStaticServerIT : AbstractIT() {
@get:Rule
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
var file = getFile("gps.jpg")
val oCFile = OCFile("/").apply {
storagePath = file.absolutePath
fileId = 12
fileDataStorageManager.saveFile(this)
}
@Test
@ScreenshotTest
fun showFileDetailActivitiesFragment() {
val sut = testActivityRule.launchActivity(null)
sut.addFragment(FileDetailActivitiesFragment.newInstance(oCFile, user))
waitForIdleSync()
shortSleep()
shortSleep()
screenshot(sut)
}
@Test
@ScreenshotTest
fun showFileDetailSharingFragment() {
val sut = testActivityRule.launchActivity(null)
sut.addFragment(FileDetailSharingFragment.newInstance(oCFile, user))
waitForIdleSync()
shortSleep()
shortSleep()
screenshot(sut)
}
@Test
@ScreenshotTest
fun showFileDetailDetailsFragment() {
val activity = testActivityRule.launchActivity(null)
val sut = ImageDetailFragment.newInstance(oCFile, user)
activity.addFragment(sut)
shortSleep()
shortSleep()
waitForIdleSync()
activity.runOnUiThread {
sut.hideMap()
}
screenshot(activity)
}
@Test
@ScreenshotTest
@Suppress("MagicNumber")
fun showDetailsActivities() {
val date = GregorianCalendar()
date.set(2005, 4, 17, 10, 35, 30) // random date
val richObjectList: ArrayList<RichObject> = ArrayList()
richObjectList.add(RichObject("file", "abc", "text.txt", "/text.txt", "link", "tag"))
richObjectList.add(RichObject("file", "1", "text.txt", "/text.txt", "link", "tag"))
val previewObjectList1: ArrayList<PreviewObject> = ArrayList()
previewObjectList1.add(PreviewObject(1, "source", "link", true, "text/plain", "view", "text.txt"))
val richObjectList2: ArrayList<RichObject> = ArrayList()
richObjectList2.add(RichObject("user", "admin", "Admin", "", "", ""))
val activities = mutableListOf(
Activity(
1,
date.time,
date.time,
"files",
"file_changed",
"user1",
"user1",
"You changed text.txt",
"",
"icon",
"link",
"files",
"1",
"/text.txt",
previewObjectList1,
RichElement("", richObjectList)
),
Activity(
2,
date.time,
date.time,
"comments",
"comments",
"user1",
"user1",
"admin commented",
"test2",
"icon",
"link",
"files",
"1",
"/text.txt",
emptyList(),
RichElement()
)
)
val sut = FileDetailFragment.newInstance(oCFile, user, 0)
testActivityRule.launchActivity(null).apply {
addFragment(sut)
waitForIdleSync()
runOnUiThread {
sut.fileDetailActivitiesFragment.populateList(activities as List<Any>?, true)
}
longSleep()
screenshot(sut.fileDetailActivitiesFragment.binding.swipeContainingList)
}
}
// @Test
// @ScreenshotTest
fun showDetailsActivitiesNone() {
val activity = testActivityRule.launchActivity(null)
val sut = FileDetailFragment.newInstance(oCFile, user, 0)
activity.addFragment(sut)
waitForIdleSync()
activity.runOnUiThread {
sut.fileDetailActivitiesFragment.populateList(emptyList(), true)
}
shortSleep()
shortSleep()
screenshot(sut.fileDetailActivitiesFragment.binding.list)
}
@Test
@ScreenshotTest
fun showDetailsActivitiesError() {
val activity = testActivityRule.launchActivity(null)
val sut = FileDetailFragment.newInstance(oCFile, user, 0)
activity.addFragment(sut)
waitForIdleSync()
activity.runOnUiThread {
sut.fileDetailActivitiesFragment.disableLoadingActivities()
sut
.fileDetailActivitiesFragment
.setErrorContent(targetContext.resources.getString(R.string.file_detail_activity_error))
}
shortSleep()
shortSleep()
screenshot(sut.fileDetailActivitiesFragment.binding.emptyList.emptyListView)
}
@Test
@ScreenshotTest
fun showDetailsSharing() {
val sut = testActivityRule.launchActivity(null)
sut.addFragment(FileDetailFragment.newInstance(oCFile, user, 1))
waitForIdleSync()
shortSleep()
shortSleep()
screenshot(sut)
}
}

View file

@ -0,0 +1,830 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2021 TSI-mc
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.fragment
import android.view.View
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.accessibility.AccessibilityChecks
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultBaseUtils.matchesCheckNames
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils.matchesViews
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.nextcloud.test.RetryTestRule
import com.nextcloud.test.TestActivity
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.resources.shares.OCShare
import com.owncloud.android.lib.resources.shares.OCShare.Companion.CREATE_PERMISSION_FLAG
import com.owncloud.android.lib.resources.shares.OCShare.Companion.DELETE_PERMISSION_FLAG
import com.owncloud.android.lib.resources.shares.OCShare.Companion.MAXIMUM_PERMISSIONS_FOR_FILE
import com.owncloud.android.lib.resources.shares.OCShare.Companion.MAXIMUM_PERMISSIONS_FOR_FOLDER
import com.owncloud.android.lib.resources.shares.OCShare.Companion.NO_PERMISSION
import com.owncloud.android.lib.resources.shares.OCShare.Companion.READ_PERMISSION_FLAG
import com.owncloud.android.lib.resources.shares.OCShare.Companion.SHARE_PERMISSION_FLAG
import com.owncloud.android.lib.resources.shares.ShareType
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.fragment.util.SharingMenuHelper
import com.owncloud.android.utils.ScreenshotTest
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.anyOf
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.CoreMatchers.not
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@Suppress("TooManyFunctions")
class FileDetailSharingFragmentIT : AbstractIT() {
@get:Rule
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
@get:Rule
val retryRule = RetryTestRule()
lateinit var file: OCFile
lateinit var folder: OCFile
lateinit var activity: TestActivity
@Before
fun before() {
activity = testActivityRule.launchActivity(null)
file = OCFile("/test.md").apply {
remoteId = "00000001"
parentId = activity.storageManager.getFileByEncryptedRemotePath("/").fileId
permissions = OCFile.PERMISSION_CAN_RESHARE
fileDataStorageManager.saveFile(this)
}
folder = OCFile("/test").apply {
setFolder()
parentId = activity.storageManager.getFileByEncryptedRemotePath("/").fileId
permissions = OCFile.PERMISSION_CAN_RESHARE
}
}
@Test
@ScreenshotTest
fun listSharesFileNone() {
show(file)
}
@Test
@ScreenshotTest
fun listSharesFileResharingNotAllowed() {
file.permissions = ""
show(file)
}
/**
* Use same values as {@link OCFileListFragmentStaticServerIT showSharedFiles }
*/
@Test
@ScreenshotTest
@Suppress("MagicNumber")
fun listSharesFileAllShareTypes() {
OCShare(file.decryptedRemotePath).apply {
remoteId = 1
shareType = ShareType.USER
sharedWithDisplayName = "Admin"
permissions = MAXIMUM_PERMISSIONS_FOR_FILE
userId = getUserId(user)
activity.storageManager.saveShare(this)
}
OCShare(file.decryptedRemotePath).apply {
remoteId = 2
shareType = ShareType.GROUP
sharedWithDisplayName = "Group"
permissions = MAXIMUM_PERMISSIONS_FOR_FILE
userId = getUserId(user)
activity.storageManager.saveShare(this)
}
OCShare(file.decryptedRemotePath).apply {
remoteId = 3
shareType = ShareType.EMAIL
sharedWithDisplayName = "admin@nextcloud.localhost"
userId = getUserId(user)
activity.storageManager.saveShare(this)
}
OCShare(file.decryptedRemotePath).apply {
remoteId = 4
shareType = ShareType.PUBLIC_LINK
label = "Customer"
activity.storageManager.saveShare(this)
}
OCShare(file.decryptedRemotePath).apply {
remoteId = 5
shareType = ShareType.PUBLIC_LINK
label = "Colleagues"
activity.storageManager.saveShare(this)
}
OCShare(file.decryptedRemotePath).apply {
remoteId = 6
shareType = ShareType.FEDERATED
sharedWithDisplayName = "admin@nextcloud.localhost"
permissions = OCShare.FEDERATED_PERMISSIONS_FOR_FILE
userId = getUserId(user)
activity.storageManager.saveShare(this)
}
OCShare(file.decryptedRemotePath).apply {
remoteId = 7
shareType = ShareType.CIRCLE
sharedWithDisplayName = "Personal team"
permissions = SHARE_PERMISSION_FLAG
userId = getUserId(user)
activity.storageManager.saveShare(this)
}
OCShare(file.decryptedRemotePath).apply {
remoteId = 8
shareType = ShareType.CIRCLE
sharedWithDisplayName = "Public team"
permissions = SHARE_PERMISSION_FLAG
userId = getUserId(user)
activity.storageManager.saveShare(this)
}
OCShare(file.decryptedRemotePath).apply {
remoteId = 9
shareType = ShareType.CIRCLE
sharedWithDisplayName = "Closed team"
permissions = SHARE_PERMISSION_FLAG
userId = getUserId(user)
activity.storageManager.saveShare(this)
}
OCShare(file.decryptedRemotePath).apply {
remoteId = 10
shareType = ShareType.CIRCLE
sharedWithDisplayName = "Secret team"
permissions = SHARE_PERMISSION_FLAG
userId = getUserId(user)
activity.storageManager.saveShare(this)
}
OCShare(file.decryptedRemotePath).apply {
remoteId = 11
shareType = ShareType.ROOM
sharedWithDisplayName = "Admin"
permissions = SHARE_PERMISSION_FLAG
userId = getUserId(user)
activity.storageManager.saveShare(this)
}
OCShare(file.decryptedRemotePath).apply {
remoteId = 12
shareType = ShareType.ROOM
sharedWithDisplayName = "Meeting"
permissions = SHARE_PERMISSION_FLAG
userId = getUserId(user)
activity.storageManager.saveShare(this)
}
show(file)
}
private fun show(file: OCFile) {
val fragment = FileDetailSharingFragment.newInstance(file, user)
activity.addFragment(fragment)
waitForIdleSync()
screenshot(activity)
}
// public link and email are handled the same way
// for advanced permissions
@Test
@Suppress("MagicNumber")
fun publicLinkOptionMenuFolderAdvancePermission() {
val sut = FileDetailSharingFragment.newInstance(file, user)
activity.addFragment(sut)
setupSecondaryFragment()
shortSleep()
sut.refreshCapabilitiesFromDB()
val publicShare = OCShare().apply {
isFolder = true
shareType = ShareType.PUBLIC_LINK
permissions = 17
}
activity.runOnUiThread { sut.showSharingMenuActionSheet(publicShare) }
shortSleep()
waitForIdleSync()
// check if items are visible
onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.menu_share_add_another_link)).check(matches(isDisplayed()))
// click event
onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click())
// validate view shown on screen
onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.share_process_change_name_switch)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(not(isDisplayed())))
// read-only
onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isChecked()))
onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked()))
onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isNotChecked()))
goBack()
// upload and editing
publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER
openAdvancedPermissions(sut, publicShare)
onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked()))
onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isChecked()))
onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isNotChecked()))
goBack()
// file drop
publicShare.permissions = 4
openAdvancedPermissions(sut, publicShare)
onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked()))
onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked()))
onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isChecked()))
goBack()
// password protection
publicShare.shareWith = "someValue"
openAdvancedPermissions(sut, publicShare)
onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isChecked()))
goBack()
publicShare.shareWith = ""
openAdvancedPermissions(sut, publicShare)
onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isNotChecked()))
goBack()
// hide download
publicShare.isHideFileDownload = true
publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER
openAdvancedPermissions(sut, publicShare)
onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isChecked()))
goBack()
publicShare.isHideFileDownload = false
openAdvancedPermissions(sut, publicShare)
onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isNotChecked()))
goBack()
publicShare.expirationDate = 1582019340000
openAdvancedPermissions(sut, publicShare)
onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked()))
onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText(""))))
goBack()
publicShare.expirationDate = 0
openAdvancedPermissions(sut, publicShare)
onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked()))
onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText("")))
}
// public link and email are handled the same way
// for send new email
@Test
@Suppress("MagicNumber")
fun publicLinkOptionMenuFolderSendNewEmail() {
val sut = FileDetailSharingFragment.newInstance(file, user)
activity.addFragment(sut)
setupSecondaryFragment()
shortSleep()
sut.refreshCapabilitiesFromDB()
val publicShare = OCShare().apply {
isFolder = true
shareType = ShareType.PUBLIC_LINK
permissions = 17
}
verifySendNewEmail(sut, publicShare)
}
private fun setupSecondaryFragment() {
val parentFolder = OCFile("/")
val secondary = FileDetailFragment.newInstance(file, parentFolder, user)
activity.addSecondaryFragment(secondary, FileDisplayActivity.TAG_LIST_OF_FILES)
activity.addView(
FloatingActionButton(activity).apply {
// needed for some reason
visibility = View.GONE
id = R.id.fab_main
}
)
}
// public link and email are handled the same way
// for advanced permissions
@Test
@Suppress("MagicNumber")
fun publicLinkOptionMenuFileAdvancePermission() {
val sut = FileDetailSharingFragment.newInstance(file, user)
activity.addFragment(sut)
setupSecondaryFragment()
shortSleep()
sut.refreshCapabilitiesFromDB()
val publicShare = OCShare().apply {
isFolder = false
shareType = ShareType.PUBLIC_LINK
permissions = 17
}
activity.handler.post { sut.showSharingMenuActionSheet(publicShare) }
waitForIdleSync()
// check if items are visible
onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.menu_share_add_another_link)).check(matches(isDisplayed()))
// click event
onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click())
// validate view shown on screen
onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(not(isDisplayed())))
onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.share_process_change_name_switch)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(not(isDisplayed())))
// read-only
publicShare.permissions = 17 // from server
onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isChecked()))
onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked()))
goBack()
// editing
publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FILE // from server
openAdvancedPermissions(sut, publicShare)
onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked()))
onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isChecked()))
goBack()
// hide download
publicShare.isHideFileDownload = true
openAdvancedPermissions(sut, publicShare)
onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isChecked()))
goBack()
publicShare.isHideFileDownload = false
openAdvancedPermissions(sut, publicShare)
onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isNotChecked()))
goBack()
// password protection
publicShare.isPasswordProtected = true
publicShare.shareWith = "someValue"
openAdvancedPermissions(sut, publicShare)
onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isChecked()))
goBack()
publicShare.isPasswordProtected = false
publicShare.shareWith = ""
openAdvancedPermissions(sut, publicShare)
onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isNotChecked()))
goBack()
// expires
publicShare.expirationDate = 1582019340
openAdvancedPermissions(sut, publicShare)
onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked()))
onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText(""))))
goBack()
publicShare.expirationDate = 0
openAdvancedPermissions(sut, publicShare)
onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked()))
onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText("")))
}
// public link and email are handled the same way
// for send new email
@Test
@Suppress("MagicNumber")
fun publicLinkOptionMenuFileSendNewEmail() {
val sut = FileDetailSharingFragment.newInstance(file, user)
activity.addFragment(sut)
setupSecondaryFragment()
shortSleep()
sut.refreshCapabilitiesFromDB()
val publicShare = OCShare().apply {
isFolder = false
shareType = ShareType.PUBLIC_LINK
permissions = 17
}
verifySendNewEmail(sut, publicShare)
}
// also applies for
// group
// conversation
// circle
// federated share
// for advanced permissions
@Test
@Suppress("MagicNumber")
fun userOptionMenuFileAdvancePermission() {
val sut = FileDetailSharingFragment.newInstance(file, user)
suppressFDFAccessibilityChecks()
activity.addFragment(sut)
setupSecondaryFragment()
shortSleep()
sut.refreshCapabilitiesFromDB()
val userShare = OCShare().apply {
isFolder = false
shareType = ShareType.USER
permissions = 17
}
activity.runOnUiThread { sut.showSharingMenuActionSheet(userShare) }
shortSleep()
waitForIdleSync()
// check if items are visible
onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(not(isDisplayed())))
onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.menu_share_add_another_link)).check(matches(not(isDisplayed())))
// click event
onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click())
shortSleep()
waitForIdleSync()
// validate view shown on screen
onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(not(isDisplayed())))
onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(not(isDisplayed())))
onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(not(isDisplayed())))
onView(ViewMatchers.withId(R.id.share_process_change_name_switch)).check(matches(not(isDisplayed())))
onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isDisplayed()))
// read-only
userShare.permissions = 17 // from server
onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isChecked()))
onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked()))
goBack()
// editing
userShare.permissions = MAXIMUM_PERMISSIONS_FOR_FILE // from server
openAdvancedPermissions(sut, userShare)
onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked()))
onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isChecked()))
goBack()
// allow reshare
userShare.permissions = 1 // from server
openAdvancedPermissions(sut, userShare)
onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isNotChecked()))
goBack()
userShare.permissions = 17 // from server
openAdvancedPermissions(sut, userShare)
onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isChecked()))
goBack()
// set expiration date
userShare.expirationDate = 1582019340000
openAdvancedPermissions(sut, userShare)
onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked()))
onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText(""))))
goBack()
userShare.expirationDate = 0
openAdvancedPermissions(sut, userShare)
onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked()))
onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText("")))
}
private fun suppressFDFAccessibilityChecks() {
AccessibilityChecks.enable().apply {
setSuppressingResultMatcher(
allOf(
anyOf(
matchesCheckNames(`is`("TouchTargetSizeCheck")),
matchesCheckNames(`is`("SpeakableTextPresentCheck"))
),
anyOf(
matchesViews(ViewMatchers.withId(R.id.favorite)),
matchesViews(ViewMatchers.withId(R.id.last_modification_timestamp))
)
)
)
}
}
// also applies for
// group
// conversation
// circle
// federated share
// for send new email
@Test
@Suppress("MagicNumber")
fun userOptionMenuFileSendNewEmail() {
val sut = FileDetailSharingFragment.newInstance(file, user)
activity.addFragment(sut)
setupSecondaryFragment()
shortSleep()
sut.refreshCapabilitiesFromDB()
val userShare = OCShare().apply {
isFolder = false
shareType = ShareType.USER
permissions = 17
}
verifySendNewEmail(sut, userShare)
}
// also applies for
// group
// conversation
// circle
// federated share
// for advanced permissions
@Test
@Suppress("MagicNumber")
fun userOptionMenuFolderAdvancePermission() {
val sut = FileDetailSharingFragment.newInstance(file, user)
activity.addFragment(sut)
setupSecondaryFragment()
suppressFDFAccessibilityChecks()
shortSleep()
sut.refreshCapabilitiesFromDB()
val userShare = OCShare().apply {
isFolder = true
shareType = ShareType.USER
permissions = 17
}
activity.runOnUiThread { sut.showSharingMenuActionSheet(userShare) }
shortSleep()
waitForIdleSync()
// check if items are visible
onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(not(isDisplayed())))
onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.menu_share_add_another_link)).check(matches(not(isDisplayed())))
// click event
onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click())
// validate view shown on screen
onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isDisplayed()))
onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(not(isDisplayed())))
onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(not(isDisplayed())))
onView(ViewMatchers.withId(R.id.share_process_change_name_switch)).check(matches(not(isDisplayed())))
onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isDisplayed()))
// read-only
userShare.permissions = 17 // from server
onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isChecked()))
onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked()))
onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isNotChecked()))
goBack()
// allow upload & editing
userShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER // from server
openAdvancedPermissions(sut, userShare)
onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked()))
onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isChecked()))
onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isNotChecked()))
goBack()
// file drop
userShare.permissions = 4
openAdvancedPermissions(sut, userShare)
onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked()))
onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked()))
onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isChecked()))
goBack()
// allow reshare
userShare.permissions = 1 // from server
openAdvancedPermissions(sut, userShare)
onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isNotChecked()))
goBack()
userShare.permissions = 17 // from server
openAdvancedPermissions(sut, userShare)
onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isChecked()))
goBack()
// set expiration date
userShare.expirationDate = 1582019340000
openAdvancedPermissions(sut, userShare)
onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked()))
onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText(""))))
goBack()
userShare.expirationDate = 0
openAdvancedPermissions(sut, userShare)
onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked()))
onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText("")))
}
// open bottom sheet with actions
private fun openAdvancedPermissions(
sut: FileDetailSharingFragment,
userShare: OCShare
) {
activity.handler.post {
sut.showSharingMenuActionSheet(userShare)
}
shortSleep()
waitForIdleSync()
onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click())
}
// remove the fragment shown
private fun goBack() {
activity.handler.post {
val processFragment =
activity.supportFragmentManager.findFragmentByTag(FileDetailsSharingProcessFragment.TAG) as
FileDetailsSharingProcessFragment
processFragment.onBackPressed()
}
shortSleep()
waitForIdleSync()
}
// also applies for
// group
// conversation
// circle
// federated share
// for send new email
@Test
@Suppress("MagicNumber")
fun userOptionMenuFolderSendNewEmail() {
val sut = FileDetailSharingFragment.newInstance(file, user)
activity.addFragment(sut)
setupSecondaryFragment()
shortSleep()
sut.refreshCapabilitiesFromDB()
val userShare = OCShare().apply {
isFolder = true
shareType = ShareType.USER
permissions = 17
}
verifySendNewEmail(sut, userShare)
}
/**
* verify send new email note text
*/
private fun verifySendNewEmail(
sut: FileDetailSharingFragment,
userShare: OCShare
) {
activity.runOnUiThread { sut.showSharingMenuActionSheet(userShare) }
waitForIdleSync()
// click event
onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).perform(ViewActions.click())
// validate view shown on screen
onView(ViewMatchers.withId(R.id.note_text)).check(matches(isDisplayed()))
}
@Test
fun testUploadAndEditingSharePermissions() {
val share = OCShare().apply {
permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER
}
assertTrue(SharingMenuHelper.isUploadAndEditingAllowed(share))
share.permissions = NO_PERMISSION
assertFalse(SharingMenuHelper.isUploadAndEditingAllowed(share))
share.permissions = READ_PERMISSION_FLAG
assertFalse(SharingMenuHelper.isUploadAndEditingAllowed(share))
share.permissions = CREATE_PERMISSION_FLAG
assertFalse(SharingMenuHelper.isUploadAndEditingAllowed(share))
share.permissions = DELETE_PERMISSION_FLAG
assertFalse(SharingMenuHelper.isUploadAndEditingAllowed(share))
share.permissions = SHARE_PERMISSION_FLAG
assertFalse(SharingMenuHelper.isUploadAndEditingAllowed(share))
}
@Test
@Suppress("MagicNumber")
fun testReadOnlySharePermissions() {
val share = OCShare().apply {
permissions = 17
}
assertTrue(SharingMenuHelper.isReadOnly(share))
share.permissions = NO_PERMISSION
assertFalse(SharingMenuHelper.isReadOnly(share))
share.permissions = READ_PERMISSION_FLAG
assertTrue(SharingMenuHelper.isReadOnly(share))
share.permissions = CREATE_PERMISSION_FLAG
assertFalse(SharingMenuHelper.isReadOnly(share))
share.permissions = DELETE_PERMISSION_FLAG
assertFalse(SharingMenuHelper.isReadOnly(share))
share.permissions = SHARE_PERMISSION_FLAG
assertFalse(SharingMenuHelper.isReadOnly(share))
share.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER
assertFalse(SharingMenuHelper.isReadOnly(share))
share.permissions = MAXIMUM_PERMISSIONS_FOR_FILE
assertFalse(SharingMenuHelper.isReadOnly(share))
}
@Test
@Suppress("MagicNumber")
fun testFileDropSharePermissions() {
val share = OCShare().apply {
permissions = 4
}
assertTrue(SharingMenuHelper.isFileDrop(share))
share.permissions = NO_PERMISSION
assertFalse(SharingMenuHelper.isFileDrop(share))
share.permissions = READ_PERMISSION_FLAG
assertFalse(SharingMenuHelper.isFileDrop(share))
share.permissions = CREATE_PERMISSION_FLAG
assertTrue(SharingMenuHelper.isFileDrop(share))
share.permissions = DELETE_PERMISSION_FLAG
assertFalse(SharingMenuHelper.isFileDrop(share))
share.permissions = SHARE_PERMISSION_FLAG
assertFalse(SharingMenuHelper.isFileDrop(share))
share.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER
assertFalse(SharingMenuHelper.isFileDrop(share))
share.permissions = MAXIMUM_PERMISSIONS_FOR_FILE
assertFalse(SharingMenuHelper.isFileDrop(share))
}
@After
override fun after() {
activity.storageManager.cleanShares()
activity.finish()
super.after()
}
}

View file

@ -0,0 +1,120 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.fragment
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import androidx.test.espresso.intent.rule.IntentsTestRule
import com.nextcloud.test.TestActivity
import com.owncloud.android.AbstractIT
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.ThumbnailsCacheManager
import com.owncloud.android.datamodel.ThumbnailsCacheManager.InitDiskCacheTask
import com.owncloud.android.datamodel.ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.lib.resources.files.model.ImageDimension
import com.owncloud.android.utils.ScreenshotTest
import org.junit.After
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.util.Random
class GalleryFragmentIT : AbstractIT() {
@get:Rule
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
lateinit var activity: TestActivity
val random = Random(1)
@Before
fun before() {
activity = testActivityRule.launchActivity(null)
// initialise thumbnails cache on background thread
InitDiskCacheTask().execute()
}
@After
override fun after() {
ThumbnailsCacheManager.clearCache()
super.after()
}
@ScreenshotTest
@Test
fun showEmpty() {
val sut = GalleryFragment()
activity.addFragment(sut)
waitForIdleSync()
screenshot(activity)
}
@Test
@ScreenshotTest
fun showGallery() {
createImage(10000001, 700, 300)
createImage(10000002, 500, 300)
createImage(10000007, 300, 400)
val sut = GalleryFragment()
activity.addFragment(sut)
waitForIdleSync()
shortSleep()
screenshot(activity)
}
private fun createImage(id: Int, width: Int? = null, height: Int? = null) {
val defaultSize = ThumbnailsCacheManager.getThumbnailDimension().toFloat()
val file = OCFile("/$id.png").apply {
fileId = id.toLong()
remoteId = "$id"
mimeType = "image/png"
isPreviewAvailable = true
modificationTimestamp = (1658475504 + id.toLong()) * 1000
imageDimension = ImageDimension(width?.toFloat() ?: defaultSize, height?.toFloat() ?: defaultSize)
storageManager.saveFile(this)
}
// create dummy thumbnail
val w: Int
val h: Int
if (width == null || height == null) {
if (random.nextBoolean()) {
// portrait
w = (random.nextInt(3) + 2) * 100 // 200-400
h = (random.nextInt(5) + 4) * 100 // 400-800
} else {
// landscape
w = (random.nextInt(5) + 4) * 100 // 400-800
h = (random.nextInt(3) + 2) * 100 // 200-400
}
} else {
w = width
h = height
}
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
Canvas(bitmap).apply {
drawRGB(random.nextInt(256), random.nextInt(256), random.nextInt(256))
drawCircle(w / 2f, h / 2f, w.coerceAtMost(h) / 2f, Paint().apply { color = Color.BLACK })
}
ThumbnailsCacheManager.addBitmapToCache(PREFIX_RESIZED_IMAGE + file.remoteId, bitmap)
assertNotNull(ThumbnailsCacheManager.getBitmapFromDiskCache(PREFIX_RESIZED_IMAGE + file.remoteId))
Log_OC.d("Gallery_thumbnail", "created $id with ${bitmap.width} x ${bitmap.height}")
}
}

View file

@ -0,0 +1,74 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.fragment
import androidx.test.espresso.intent.rule.IntentsTestRule
import com.nextcloud.android.lib.resources.groupfolders.Groupfolder
import com.nextcloud.test.TestActivity
import com.owncloud.android.AbstractIT
import com.owncloud.android.utils.ScreenshotTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
class GroupfolderListFragmentIT : AbstractIT() {
@get:Rule
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
lateinit var activity: TestActivity
@Before
fun before() {
activity = testActivityRule.launchActivity(null)
}
@Test
@ScreenshotTest
fun showGroupfolder() {
val sut = GroupfolderListFragment()
activity.addFragment(sut)
shortSleep() // to let async task finish
activity.runOnUiThread {
sut.setAdapter(null)
sut.setData(
mapOf(
Pair("2", Groupfolder(2, "/subfolder/group"))
)
)
}
waitForIdleSync()
shortSleep()
screenshot(activity)
}
@Test
@ScreenshotTest
fun showGroupfolders() {
val sut = GroupfolderListFragment()
activity.addFragment(sut)
shortSleep() // to let async task finish
activity.runOnUiThread {
sut.setAdapter(null)
sut.setData(
mapOf(
Pair("1", Groupfolder(1, "/test/")),
Pair("2", Groupfolder(2, "/subfolder/group"))
)
)
}
waitForIdleSync()
shortSleep()
screenshot(activity)
}
}

View file

@ -0,0 +1,364 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.fragment
import androidx.test.espresso.intent.rule.IntentsTestRule
import com.nextcloud.test.GrantStoragePermissionRule
import com.nextcloud.test.TestActivity
import com.owncloud.android.AbstractIT
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.resources.shares.ShareType
import com.owncloud.android.lib.resources.shares.ShareeUser
import com.owncloud.android.utils.MimeType
import com.owncloud.android.utils.ScreenshotTest
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
class OCFileListFragmentStaticServerIT : AbstractIT() {
@get:Rule
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
@get:Rule
val permissionRule = GrantStoragePermissionRule.grant()
@Test
@ScreenshotTest
@Suppress("MagicNumber")
fun showFiles() {
val sut = testActivityRule.launchActivity(null)
OCFile("/1.png").apply {
mimeType = "image/png"
fileLength = 1024000
modificationTimestamp = 1188206955000
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
sut.storageManager.saveFile(this)
}
OCFile("/image.png").apply {
mimeType = "image/png"
isPreviewAvailable = false
fileLength = 3072000
modificationTimestamp = 746443755000
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
tags = listOf("Top secret")
sut.storageManager.saveFile(this)
}
OCFile("/live photo.png").apply {
mimeType = "image/png"
isPreviewAvailable = false
fileLength = 3072000
modificationTimestamp = 746443755000
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
setLivePhoto("/video.mov")
sut.storageManager.saveFile(this)
}
OCFile("/video.mp4").apply {
mimeType = "video/mp4"
isPreviewAvailable = false
fileLength = 12092000
modificationTimestamp = 746143952000
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
tags = listOf("Confidential", "+5")
sut.storageManager.saveFile(this)
}
sut.addFragment(OCFileListFragment())
val fragment = (sut.fragment as OCFileListFragment)
val root = sut.storageManager.getFileByEncryptedRemotePath("/")
shortSleep()
sut.runOnUiThread { fragment.listDirectory(root, false, false) }
waitForIdleSync()
screenshot(sut)
}
/**
* Use same values as {@link FileDetailSharingFragmentIT listSharesFileAllShareTypes }
*/
@Test
@ScreenshotTest
fun showSharedFiles() {
val sut = testActivityRule.launchActivity(null)
val fragment = OCFileListFragment()
OCFile("/sharedToUser.jpg").apply {
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
isSharedWithSharee = true
sharees = listOf(ShareeUser("Admin", "Server Admin", ShareType.USER))
modificationTimestamp = 1000
sut.storageManager.saveFile(this)
}
OCFile("/sharedToGroup.jpg").apply {
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
isSharedWithSharee = true
sharees = listOf(ShareeUser("group", "Group", ShareType.GROUP))
modificationTimestamp = 1000
sut.storageManager.saveFile(this)
}
OCFile("/sharedToEmail.jpg").apply {
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
isSharedWithSharee = true
sharees = listOf(ShareeUser("admin@nextcloud.localhost", "admin@nextcloud.localhost", ShareType.EMAIL))
modificationTimestamp = 1000
sut.storageManager.saveFile(this)
}
OCFile("/publicLink.jpg").apply {
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
isSharedViaLink = true
modificationTimestamp = 1000
sut.storageManager.saveFile(this)
}
OCFile("/sharedToFederatedUser.jpg").apply {
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
isSharedWithSharee = true
sharees = listOf(
ShareeUser("admin@remote.nextcloud.com", "admin@remote.nextcloud.com (remote)", ShareType.FEDERATED)
)
modificationTimestamp = 1000
sut.storageManager.saveFile(this)
}
OCFile("/sharedToPersonalCircle.jpg").apply {
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
isSharedWithSharee = true
sharees = listOf(ShareeUser("circle", "Circle (Personal circle)", ShareType.CIRCLE))
modificationTimestamp = 1000
sut.storageManager.saveFile(this)
}
// as we cannot distinguish circle types, we do not need them right now
// OCFile("/sharedToPublicCircle.jpg").apply {
// parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
// isSharedWithSharee = true
// sharees = listOf(ShareeUser("circle", "Circle (Public circle)", ShareType.CIRCLE))
// modificationTimestamp = 1000
// sut.storageManager.saveFile(this)
// }
//
// OCFile("/sharedToClosedCircle.jpg").apply {
// parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
// isSharedWithSharee = true
// sharees = listOf(ShareeUser("circle", "Circle (Closed circle)", ShareType.CIRCLE))
// modificationTimestamp = 1000
// sut.storageManager.saveFile(this)
// }
//
// OCFile("/sharedToSecretCircle.jpg").apply {
// parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
// isSharedWithSharee = true
// sharees = listOf(ShareeUser("circle", "Circle (Secret circle)", ShareType.CIRCLE))
// modificationTimestamp = 1000
// sut.storageManager.saveFile(this)
// }
OCFile("/sharedToUserRoom.jpg").apply {
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
isSharedWithSharee = true
sharees = listOf(ShareeUser("Conversation", "Admin", ShareType.ROOM))
modificationTimestamp = 1000
sut.storageManager.saveFile(this)
}
OCFile("/sharedToGroupRoom.jpg").apply {
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
isSharedWithSharee = true
sharees = listOf(ShareeUser("Conversation", "Meeting", ShareType.ROOM))
modificationTimestamp = 1000
sut.storageManager.saveFile(this)
}
OCFile("/sharedToUsers.jpg").apply {
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
isSharedWithSharee = true
sharees = listOf(
ShareeUser("Admin", "Server Admin", ShareType.USER),
ShareeUser("User", "User", ShareType.USER),
ShareeUser("Christine", "Christine Scott", ShareType.USER)
)
modificationTimestamp = 1000
sut.storageManager.saveFile(this)
}
OCFile("/notShared.jpg").apply {
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
modificationTimestamp = 1000
sut.storageManager.saveFile(this)
}
sut.addFragment(fragment)
shortSleep()
val root = sut.storageManager.getFileByEncryptedRemotePath("/")
sut.runOnUiThread {
fragment.listDirectory(root, false, false)
fragment.adapter.setShowShareAvatar(true)
}
waitForIdleSync()
shortSleep()
shortSleep()
shortSleep()
screenshot(sut)
}
/**
* Use same values as {@link FileDetailSharingFragmentIT listSharesFileAllShareTypes }
*/
@Test
@ScreenshotTest
fun showFolderTypes() {
val sut = testActivityRule.launchActivity(null)
val fragment = OCFileListFragment()
OCFile("/normal/").apply {
mimeType = MimeType.DIRECTORY
modificationTimestamp = 1624003571000
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
sut.storageManager.saveFile(this)
}
OCFile("/sharedViaLink/").apply {
mimeType = MimeType.DIRECTORY
isSharedViaLink = true
modificationTimestamp = 1619003571000
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
sut.storageManager.saveFile(this)
}
OCFile("/share/").apply {
mimeType = MimeType.DIRECTORY
isSharedWithSharee = true
modificationTimestamp = 1619303571000
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
sut.storageManager.saveFile(this)
}
OCFile("/groupFolder/").apply {
mimeType = MimeType.DIRECTORY
modificationTimestamp = 1615003571000
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
permissions += "M"
sut.storageManager.saveFile(this)
}
OCFile("/encrypted/").apply {
mimeType = MimeType.DIRECTORY
isEncrypted = true
decryptedRemotePath = "/encrypted/"
modificationTimestamp = 1614003571000
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
sut.storageManager.saveFile(this)
}
OCFile("/locked/").apply {
mimeType = MimeType.DIRECTORY
isLocked = true
decryptedRemotePath = "/locked/"
modificationTimestamp = 1613003571000
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
sut.storageManager.saveFile(this)
}
sut.addFragment(fragment)
shortSleep()
val root = sut.storageManager.getFileByEncryptedRemotePath("/")
sut.runOnUiThread {
fragment.listDirectory(root, false, false)
fragment.adapter.setShowShareAvatar(true)
}
waitForIdleSync()
shortSleep()
shortSleep()
shortSleep()
screenshot(sut)
}
@Test
@ScreenshotTest
@Suppress("MagicNumber")
fun showRichWorkspace() {
val sut = testActivityRule.launchActivity(null)
val fragment = OCFileListFragment()
val folder = OCFile("/test/")
folder.setFolder()
sut.storageManager.saveFile(folder)
val imageFile = OCFile("/test/image.png")
imageFile.mimeType = "image/png"
imageFile.fileLength = 1024000
imageFile.modificationTimestamp = 1188206955000
imageFile.parentId = sut.storageManager.getFileByEncryptedRemotePath("/test/").fileId
imageFile.storagePath = getFile("java.md").absolutePath
sut.storageManager.saveFile(imageFile)
sut.addFragment(fragment)
val testFolder: OCFile = sut.storageManager.getFileByEncryptedRemotePath("/test/")
testFolder.richWorkspace = getFile("java.md").readText()
sut.runOnUiThread { fragment.listDirectory(testFolder, false, false) }
shortSleep()
screenshot(sut)
}
@Test
fun shouldShowHeader() {
val activity = testActivityRule.launchActivity(null)
val sut = OCFileListFragment()
val folder = OCFile("/test/")
folder.setFolder()
activity.storageManager.saveFile(folder)
activity.addFragment(sut)
val testFolder: OCFile = activity.storageManager.getFileByEncryptedRemotePath("/test/")
activity.runOnUiThread {
// richWorkspace is not set
Assert.assertFalse(sut.adapter.shouldShowHeader())
testFolder.richWorkspace = " "
activity.storageManager.saveFile(testFolder)
sut.adapter.swapDirectory(user, testFolder, activity.storageManager, false, "")
Assert.assertFalse(sut.adapter.shouldShowHeader())
testFolder.richWorkspace = null
activity.storageManager.saveFile(testFolder)
sut.adapter.swapDirectory(user, testFolder, activity.storageManager, false, "")
Assert.assertFalse(sut.adapter.shouldShowHeader())
testFolder.richWorkspace = "1"
activity.storageManager.saveFile(testFolder)
sut.adapter.setCurrentDirectory(testFolder)
Assert.assertTrue(sut.adapter.shouldShowHeader())
}
}
}

Some files were not shown because too many files have changed in this diff Show more