Pārlūkot izejas kodu

Deep link handler tests

Signed-off-by: Chris Narkiewicz <hello@ezaquarii.com>
Chris Narkiewicz 4 gadi atpakaļ
vecāks
revīzija
74e68d6b8e

+ 164 - 3
src/androidTest/java/com/nextcloud/client/files/DeepLinkHandlerTest.kt

@@ -1,16 +1,48 @@
+/**
+ * Nextcloud Android client application
+ *
+ * @author Chris Narkiewicz
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
 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.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.whenever
+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 java.net.URI
 
 @RunWith(Suite::class)
 @Suite.SuiteClasses(
-    DeepLinkHandlerTest.DeepLinkPattern::class
+    DeepLinkHandlerTest.DeepLinkPattern::class,
+    DeepLinkHandlerTest.FileDeepLink::class
 )
 class DeepLinkHandlerTest {
 
@@ -39,8 +71,7 @@ class DeepLinkHandlerTest {
             fun urls(): Array<Array<Any>> {
                 val testInput = mutableListOf<Array<Any>>()
                 SERVER_BASE_URLS.forEach { baseUrl ->
-                    INDEX_PHP_PATH.forEach {
-                        indexPath ->
+                    INDEX_PHP_PATH.forEach { indexPath ->
                         val url = "$baseUrl$indexPath/f/$FILE_ID"
                         testInput.add(arrayOf(baseUrl, indexPath, "$FILE_ID", url))
                     }
@@ -70,5 +101,135 @@ class DeepLinkHandlerTest {
             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)
+        }
     }
 }

+ 41 - 7
src/main/java/com/nextcloud/client/files/DeepLinkHandler.kt

@@ -1,17 +1,43 @@
+/**
+ * Nextcloud Android client application
+ *
+ * @author Chris Narkiewicz
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
 package com.nextcloud.client.files
 
-import android.content.Context
 import android.net.Uri
 import com.nextcloud.client.account.User
 import com.nextcloud.client.account.UserAccountManager
-import java.util.regex.Pattern
-
 
+/**
+ * This component parses and matches deep links.
+ * Result is returned to the UI for further processing.
+ *
+ * TODO: This is intermediate refactring step; this component should be moved into
+ *       [com.nextcloud.client.mixins.ActivityMixin] and handle UI callbacks as well
+ */
 class DeepLinkHandler(
-    private val context: Context,
     private val userAccountManager: UserAccountManager
 ) {
 
+    /**
+     * Provide parsed link arguments and context information required
+     * to launch it.
+     */
     data class Match(val users: List<User>, val fileId: String)
 
     companion object {
@@ -21,17 +47,25 @@ class DeepLinkHandler(
         val FILE_ID_GROUP_INDEX = 3
     }
 
+    /**
+     * Parse deep link and return a match result.
+     * Matching result may depend on environmental factors, such
+     * as app version or registered users.
+     *
+     * @param uri Deep link as arrived in incoming [android.content.Intent]
+     * @return deep link match result with all context data required for further processing; null if link does not match
+     */
     fun parseDeepLink(uri: Uri): Match? {
         val match = DEEP_LINK_PATTERN.matchEntire(uri.toString())
         if (match != null) {
             val baseServerUrl = match.groupValues[BASE_URL_GROUP_INDEX]
-            val fielId = match.groupValues[FILE_ID_GROUP_INDEX]
-            return Match(users = getUsers(baseServerUrl), fileId = fielId)
+            val fileId = match.groupValues[FILE_ID_GROUP_INDEX]
+            return Match(users = getMatchingUsers(baseServerUrl), fileId = fileId)
         } else {
             return null
         }
     }
 
-    private fun getUsers(serverBaseUrl: String): List<User> =
+    private fun getMatchingUsers(serverBaseUrl: String): List<User> =
         userAccountManager.allUsers.filter { it.server.uri.toString() == serverBaseUrl }
 }

+ 2 - 3
src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -2414,12 +2414,12 @@ public class FileDisplayActivity extends FileActivity
     }
 
     private void openDeepLink(Uri uri) {
-        DeepLinkHandler linkHandler = new DeepLinkHandler(getApplicationContext(), getUserAccountManager());
+        DeepLinkHandler linkHandler = new DeepLinkHandler(getUserAccountManager());
         DeepLinkHandler.Match match = linkHandler.parseDeepLink(uri);
         if (match == null) {
             dismissLoadingDialog();
             DisplayUtils.showSnackMessage(this, getString(R.string.invalid_url));
-        } else if (match.getUsers().size() == 0) {
+        } else if (match.getUsers().isEmpty()) {
             dismissLoadingDialog();
             DisplayUtils.showSnackMessage(this, getString(R.string.associated_account_not_found));
         } else if (match.getUsers().size() == 1) {
@@ -2449,7 +2449,6 @@ public class FileDisplayActivity extends FileActivity
 
     private void openFile(User user, String fileId) {
         setUser(user);
-        updateAccountList();
 
         if (fileId == null) {
             dismissLoadingDialog();