Skip to content

Commit e3eda3f

Browse files
committed
cover edge cases for file name extension comparison
Signed-off-by: alperozturk96 <alper_ozturk@proton.me>
1 parent d43b0bd commit e3eda3f

3 files changed

Lines changed: 130 additions & 8 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.utils
9+
10+
import com.nextcloud.utils.fileNameValidator.FileNameValidator.isExtensionChanged
11+
import junit.framework.TestCase.assertFalse
12+
import junit.framework.TestCase.assertTrue
13+
import org.junit.Test
14+
15+
class FileExtensionTest {
16+
17+
@Test
18+
fun sameExtensionReturnsFalse() {
19+
assertFalse(isExtensionChanged("file.txt", "other.txt"))
20+
}
21+
22+
@Test
23+
fun differentExtensionReturnsTrue() {
24+
assertTrue(isExtensionChanged("file.txt", "file.pdf"))
25+
}
26+
27+
@Test
28+
fun caseDifferenceDoesNotTriggerChange() {
29+
assertFalse(isExtensionChanged("file.JPG", "file.jpg"))
30+
}
31+
32+
@Test
33+
fun bothWithoutExtensionReturnsFalse() {
34+
assertFalse(isExtensionChanged("README", "LICENSE"))
35+
}
36+
37+
@Test
38+
fun noExtensionToExtensionReturnsTrue() {
39+
assertTrue(isExtensionChanged("README", "file.txt"))
40+
}
41+
42+
@Test
43+
fun extensionToNoExtensionReturnsTrue() {
44+
assertTrue(isExtensionChanged("file.txt", "README"))
45+
}
46+
47+
@Test
48+
fun hiddenFilesWithoutExtensionReturnFalse() {
49+
assertFalse(isExtensionChanged(".gitignore", ".env"))
50+
}
51+
52+
@Test
53+
fun hiddenFileToNormalExtensionReturnsTrue() {
54+
assertTrue(isExtensionChanged(".gitignore", "file.txt"))
55+
}
56+
57+
@Test
58+
fun multipleDotsSameLastExtensionReturnsFalse() {
59+
assertFalse(isExtensionChanged("archive.tar.gz", "backup.gz"))
60+
}
61+
62+
@Test
63+
fun multipleDotsDifferentLastExtensionReturnsTrue() {
64+
assertTrue(isExtensionChanged("archive.tar.gz", "archive.tar.zip"))
65+
}
66+
67+
@Test
68+
fun trailingDotTreatedAsNoExtensionReturnsTrue() {
69+
assertTrue(isExtensionChanged("file.", "file.txt"))
70+
}
71+
72+
@Test
73+
fun bothTrailingDotReturnFalse() {
74+
assertFalse(isExtensionChanged("file.", "another."))
75+
}
76+
77+
@Test
78+
fun emptyStringsReturnFalse() {
79+
assertFalse(isExtensionChanged("", ""))
80+
}
81+
82+
@Test
83+
fun emptyStringToExtensionReturnsTrue() {
84+
assertTrue(isExtensionChanged("", "file.txt"))
85+
}
86+
87+
@Test
88+
fun bothNullReturnFalse() {
89+
assertFalse(isExtensionChanged(null, null))
90+
}
91+
92+
@Test
93+
fun previousNullNewNotNullReturnsTrue() {
94+
assertTrue(isExtensionChanged(null, "file.txt"))
95+
}
96+
97+
@Test
98+
fun previousNotNullNewNullReturnsTrue() {
99+
assertTrue(isExtensionChanged("file.txt", null))
100+
}
101+
102+
@Test
103+
fun singleDotFilenameReturnsFalse() {
104+
assertFalse(isExtensionChanged(".", "."))
105+
}
106+
107+
@Test
108+
fun dotToExtensionReturnsTrue() {
109+
assertTrue(isExtensionChanged(".", "file.txt"))
110+
}
111+
112+
@Test
113+
fun filenamesEndingWithDotReturnFalse() {
114+
assertFalse(isExtensionChanged("test.", "another."))
115+
}
116+
}

app/src/main/java/com/nextcloud/utils/extensions/StringExtensions.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ fun String?.eTagChanged(eTagOnServer: String?): Boolean {
3535
return !this.equals(eTagOnServer, ignoreCase = true)
3636
}
3737

38+
fun String.extension(): String {
39+
val lastDot = lastIndexOf('.')
40+
41+
// return empty string for filenames like ".gitignore"
42+
if (lastDot <= 0 || lastDot == length - 1) {
43+
return ""
44+
}
45+
46+
return substring(lastDot + 1).lowercase()
47+
}
48+
3849
fun String.truncateWithEllipsis(limit: Int) = take(limit) + if (length > limit) StringConstants.THREE_DOT else ""
3950

4051
object StringConstants {

app/src/main/java/com/nextcloud/utils/fileNameValidator/FileNameValidator.kt

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import android.content.Context
1111
import android.text.TextUtils
1212
import com.nextcloud.utils.extensions.StringConstants
1313
import com.nextcloud.utils.extensions.checkWCFRestrictions
14+
import com.nextcloud.utils.extensions.extension
1415
import com.nextcloud.utils.extensions.forbiddenFilenameBaseNames
1516
import com.nextcloud.utils.extensions.forbiddenFilenameCharacters
1617
import com.nextcloud.utils.extensions.forbiddenFilenameExtensions
@@ -19,7 +20,6 @@ import com.nextcloud.utils.extensions.removeFileExtension
1920
import com.owncloud.android.R
2021
import com.owncloud.android.datamodel.OCFile
2122
import com.owncloud.android.lib.resources.status.OCCapability
22-
import java.io.File
2323

2424
object FileNameValidator {
2525

@@ -148,17 +148,12 @@ object FileNameValidator {
148148
return null
149149
}
150150

151-
/**
152-
* @return True, if the extension of both filenames is different. If either filename is null, function returns false
153-
*/
154151
fun isExtensionChanged(previousFileName: String?, newFileName: String?): Boolean {
155152
if (previousFileName == null || newFileName == null) {
156-
return false
153+
return previousFileName != newFileName
157154
}
158-
val previousExtension = File(previousFileName).extension
159-
val newExtension = File(newFileName).extension
160155

161-
return previousExtension != newExtension
156+
return previousFileName.extension() != newFileName.extension()
162157
}
163158

164159
fun isFileHidden(name: String): Boolean = !TextUtils.isEmpty(name) && name[0] == '.'

0 commit comments

Comments
 (0)