Skip to content

Commit f2803b8

Browse files
alperozturk96backportbot[bot]
authored andcommitted
add tests for upsert file
Signed-off-by: alperozturk <[email protected]>
1 parent e49509e commit f2803b8

File tree

2 files changed

+158
-1
lines changed

2 files changed

+158
-1
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2025 Alper Ozturk <[email protected]>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.owncloud.android.providers
9+
10+
import android.content.ContentValues
11+
import android.content.ContentUris
12+
import android.net.Uri
13+
import androidx.sqlite.db.SimpleSQLiteQuery
14+
import androidx.sqlite.db.SupportSQLiteDatabase
15+
import com.owncloud.android.db.ProviderMeta
16+
import io.mockk.Runs
17+
import io.mockk.every
18+
import io.mockk.just
19+
import io.mockk.mockk
20+
import kotlinx.coroutines.async
21+
import kotlinx.coroutines.awaitAll
22+
import kotlinx.coroutines.coroutineScope
23+
import kotlinx.coroutines.runBlocking
24+
import org.junit.Assert.assertEquals
25+
import org.junit.Assert.assertTrue
26+
import org.junit.Before
27+
import org.junit.Test
28+
29+
class FileContentProviderTests {
30+
31+
private lateinit var provider: FileContentProvider
32+
private lateinit var db: SupportSQLiteDatabase
33+
34+
@Before
35+
fun setup() {
36+
provider = FileContentProvider()
37+
db = mockk()
38+
}
39+
40+
@Test
41+
fun insertNewFileShouldReturnNewId() {
42+
val values = ContentValues().apply {
43+
put(ProviderMeta.ProviderTableMeta.FILE_PATH, "/path/to/file.txt")
44+
put(ProviderMeta.ProviderTableMeta.FILE_ACCOUNT_OWNER, "[email protected]")
45+
}
46+
47+
// Mock insert to return new ID
48+
every { db.insert(any(), any(), any()) } returns 42L
49+
50+
val result: Uri = provider.upsertSingleFile(
51+
db,
52+
ProviderMeta.ProviderTableMeta.CONTENT_URI_FILE,
53+
values
54+
)
55+
56+
assertEquals(
57+
ContentUris.withAppendedId(ProviderMeta.ProviderTableMeta.CONTENT_URI_FILE, 42),
58+
result
59+
)
60+
}
61+
62+
@Test
63+
fun updateExistingFileShouldReturnSameId() {
64+
val values = ContentValues().apply {
65+
put(ProviderMeta.ProviderTableMeta.FILE_PATH, "/path/to/file.txt")
66+
put(ProviderMeta.ProviderTableMeta.FILE_ACCOUNT_OWNER, "[email protected]")
67+
}
68+
69+
// Simulate insert conflict
70+
every { db.insert(ProviderMeta.ProviderTableMeta.FILE_TABLE_NAME, any(), values) } returns -1L
71+
72+
// Simulate update returning 1 row affected
73+
every {
74+
db.update(
75+
ProviderMeta.ProviderTableMeta.FILE_TABLE_NAME,
76+
any(),
77+
values,
78+
any(),
79+
any()
80+
)
81+
} returns 1
82+
83+
// Mock cursor to return ID 99
84+
val cursor = mockk<android.database.Cursor>()
85+
every { cursor.moveToFirst() } returns true
86+
every { cursor.getLong(0) } returns 99L
87+
every { cursor.close() } just Runs
88+
89+
every { db.query(any<SimpleSQLiteQuery>()) } returns cursor
90+
91+
val result: Uri = provider.upsertSingleFile(
92+
db,
93+
ProviderMeta.ProviderTableMeta.CONTENT_URI_FILE,
94+
values
95+
)
96+
97+
assertEquals(ContentUris.withAppendedId(ProviderMeta.ProviderTableMeta.CONTENT_URI_FILE, 99), result)
98+
99+
cursor.close()
100+
}
101+
102+
@Test
103+
fun testConcurrentUpserts() = runBlocking {
104+
val values = ContentValues().apply {
105+
put(ProviderMeta.ProviderTableMeta.FILE_PATH, "/path/to/file.txt")
106+
put(ProviderMeta.ProviderTableMeta.FILE_ACCOUNT_OWNER, "[email protected]")
107+
}
108+
109+
// shared state to simulate race
110+
val inserted = mutableListOf<Long>()
111+
112+
// mock insert: fail first call, succeed second
113+
every { db.insert(any(), any(), any()) } answers {
114+
synchronized(inserted) {
115+
if (inserted.isEmpty()) {
116+
// first thread "fails" insert which means already existing file id will be returned
117+
inserted.add(-1L)
118+
-1L
119+
} else {
120+
// second thread "succeeds" it will update existing one
121+
inserted.add(42L)
122+
42L
123+
}
124+
}
125+
}
126+
127+
// mock update only one row should be affected
128+
every { db.update(any(), any(), any(), any(), any()) } returns 1
129+
130+
// mock query existing file id will return 99
131+
val cursor = mockk<android.database.Cursor>()
132+
every { cursor.moveToFirst() } returns true
133+
every { cursor.getLong(0) } returns 99L
134+
every { cursor.close() } just Runs
135+
every { db.query(any<SimpleSQLiteQuery>()) } returns cursor
136+
137+
// launch two coroutines simulating concurrent threads
138+
val results = mutableListOf<Uri>()
139+
coroutineScope {
140+
val job1 =
141+
async {
142+
results.add(provider.upsertSingleFile(db, ProviderMeta.ProviderTableMeta.CONTENT_URI_FILE, values))
143+
}
144+
val job2 =
145+
async {
146+
results.add(provider.upsertSingleFile(db, ProviderMeta.ProviderTableMeta.CONTENT_URI_FILE, values))
147+
}
148+
awaitAll(job1, job2)
149+
}
150+
151+
// both URIs should be correct (one updated, one inserted)
152+
assertTrue(results.contains(ContentUris.withAppendedId(ProviderMeta.ProviderTableMeta.CONTENT_URI_FILE, 99)))
153+
assertTrue(results.contains(ContentUris.withAppendedId(ProviderMeta.ProviderTableMeta.CONTENT_URI_FILE, 42)))
154+
155+
cursor.close()
156+
}
157+
}

app/src/main/java/com/owncloud/android/providers/FileContentProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ private Uri insert(SupportSQLiteDatabase db, Uri uri, ContentValues values) {
317317
}
318318
}
319319

320-
private Uri upsertSingleFile(SupportSQLiteDatabase db, Uri uri, ContentValues values) {
320+
public Uri upsertSingleFile(SupportSQLiteDatabase db, Uri uri, ContentValues values) {
321321
String filePath = values.getAsString(ProviderTableMeta.FILE_PATH);
322322
String accountOwner = values.getAsString(ProviderTableMeta.FILE_ACCOUNT_OWNER);
323323

0 commit comments

Comments
 (0)