repo created
This commit is contained in:
commit
1ef725ef20
2483 changed files with 278273 additions and 0 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
31
app/src/androidTest/java/com/nextcloud/client/TestRunner.kt
Normal file
31
app/src/androidTest/java/com/nextcloud/client/TestRunner.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()) }
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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?
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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("")
|
||||
}
|
||||
}
|
||||
58
app/src/androidTest/java/com/nextcloud/test/RetryTestRule.kt
Normal file
58
app/src/androidTest/java/com/nextcloud/test/RetryTestRule.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
app/src/androidTest/java/com/nextcloud/test/TestMainApp.kt
Normal file
59
app/src/androidTest/java/com/nextcloud/test/TestMainApp.kt
Normal 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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
124
app/src/androidTest/java/com/nextcloud/ui/BitmapIT.kt
Normal file
124
app/src/androidTest/java/com/nextcloud/ui/BitmapIT.kt
Normal 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
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
544
app/src/androidTest/java/com/owncloud/android/AbstractIT.java
Normal file
544
app/src/androidTest/java/com/owncloud/android/AbstractIT.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
108
app/src/androidTest/java/com/owncloud/android/DownloadIT.java
Normal file
108
app/src/androidTest/java/com/owncloud/android/DownloadIT.java
Normal 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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
150
app/src/androidTest/java/com/owncloud/android/FileIT.java
Normal file
150
app/src/androidTest/java/com/owncloud/android/FileIT.java
Normal 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());
|
||||
}
|
||||
}
|
||||
137
app/src/androidTest/java/com/owncloud/android/ScreenshotsIT.java
Normal file
137
app/src/androidTest/java/com/owncloud/android/ScreenshotsIT.java
Normal 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
|
||||
}
|
||||
}
|
||||
517
app/src/androidTest/java/com/owncloud/android/UploadIT.java
Normal file
517
app/src/androidTest/java/com/owncloud/android/UploadIT.java
Normal 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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
136
app/src/androidTest/java/com/owncloud/android/ui/LoginIT.kt
Normal file
136
app/src/androidTest/java/com/owncloud/android/ui/LoginIT.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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}")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue