Skip to content

Commit 40e9f3d

Browse files
committed
Enhance test coverage by adding new test cases for StorageItem and User response mappings in ApiResponseTest, and improve error handling in ActivityLoggerTest. Extend AdminUseCaseTest with error propagation tests for user quota, role, and status updates. Update collaboration-related tests to include new use cases for document comments and user presence. Revise TEST_COVERAGE_ACTION_PLAN.md to reflect recent improvements and new test additions.
1 parent c6ee529 commit 40e9f3d

16 files changed

Lines changed: 2705 additions & 17 deletions

File tree

backend/api/src/test/kotlin/com/vaultstadio/api/dto/ApiResponseTest.kt

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import com.vaultstadio.domain.auth.model.UserRole
1111
import com.vaultstadio.domain.auth.model.UserStatus
1212
import com.vaultstadio.domain.common.pagination.PagedResult
1313
import com.vaultstadio.domain.share.model.ShareLink
14+
import com.vaultstadio.domain.storage.model.ItemType
15+
import com.vaultstadio.domain.storage.model.StorageItem
1416
import com.vaultstadio.domain.storage.model.StorageQuota
17+
import com.vaultstadio.domain.storage.model.Visibility
1518
import kotlinx.datetime.Instant
1619
import org.junit.jupiter.api.Test
1720
import kotlin.test.assertEquals
@@ -160,6 +163,63 @@ class ShareLinkToResponseTest {
160163
}
161164
}
162165

166+
class StorageItemToResponseTest {
167+
168+
private val testInstant = Instant.fromEpochMilliseconds(0L)
169+
170+
@Test
171+
fun `toResponse maps item to StorageItemResponse`() {
172+
val item = StorageItem(
173+
id = "item-1",
174+
name = "doc.pdf",
175+
path = "/doc.pdf",
176+
type = ItemType.FILE,
177+
parentId = "parent-1",
178+
ownerId = "user-1",
179+
size = 1024L,
180+
mimeType = "application/pdf",
181+
visibility = Visibility.PRIVATE,
182+
isStarred = true,
183+
isTrashed = false,
184+
createdAt = testInstant,
185+
updatedAt = testInstant,
186+
)
187+
val response = item.toResponse()
188+
assertEquals("item-1", response.id)
189+
assertEquals("doc.pdf", response.name)
190+
assertEquals("/doc.pdf", response.path)
191+
assertEquals(ItemType.FILE, response.type)
192+
assertEquals("parent-1", response.parentId)
193+
assertEquals(1024L, response.size)
194+
assertEquals("application/pdf", response.mimeType)
195+
assertEquals(Visibility.PRIVATE, response.visibility)
196+
assertTrue(response.isStarred)
197+
assertFalse(response.isTrashed)
198+
assertEquals(testInstant, response.createdAt)
199+
assertEquals(testInstant, response.updatedAt)
200+
assertNull(response.metadata)
201+
}
202+
203+
@Test
204+
fun `toResponse with metadata passes metadata to response`() {
205+
val item = StorageItem(
206+
id = "f1",
207+
name = "photo.jpg",
208+
path = "/photo.jpg",
209+
type = ItemType.FILE,
210+
parentId = null,
211+
ownerId = "user-1",
212+
size = 0,
213+
createdAt = testInstant,
214+
updatedAt = testInstant,
215+
)
216+
val metadata = mapOf("camera" to "Canon", "width" to "1920")
217+
val response = item.toResponse(metadata = metadata)
218+
assertEquals(metadata, response.metadata)
219+
assertEquals("f1", response.id)
220+
}
221+
}
222+
163223
class StorageQuotaToResponseTest {
164224

165225
@Test
@@ -248,3 +308,55 @@ class UserToResponseTest {
248308
assertEquals(testInstant, response.createdAt)
249309
}
250310
}
311+
312+
class UserToAdminResponseTest {
313+
314+
private val testInstant = Instant.fromEpochMilliseconds(0L)
315+
316+
@Test
317+
fun `toAdminResponse maps user to AdminUserResponse with usedBytes`() {
318+
val user = User(
319+
id = "admin-user-1",
320+
email = "admin@example.com",
321+
username = "adminuser",
322+
passwordHash = "hash",
323+
role = UserRole.ADMIN,
324+
status = UserStatus.ACTIVE,
325+
quotaBytes = 1_000_000L,
326+
avatarUrl = "https://example.com/avatar.png",
327+
lastLoginAt = testInstant,
328+
createdAt = testInstant,
329+
updatedAt = testInstant,
330+
)
331+
val response = user.toAdminResponse(usedBytes = 500_000L)
332+
assertEquals("admin-user-1", response.id)
333+
assertEquals("admin@example.com", response.email)
334+
assertEquals("adminuser", response.username)
335+
assertEquals(UserRole.ADMIN, response.role)
336+
assertEquals(UserStatus.ACTIVE, response.status)
337+
assertEquals("https://example.com/avatar.png", response.avatarUrl)
338+
assertEquals(1_000_000L, response.quotaBytes)
339+
assertEquals(500_000L, response.usedBytes)
340+
assertEquals(testInstant, response.createdAt)
341+
assertEquals(testInstant, response.lastLoginAt)
342+
}
343+
344+
@Test
345+
fun `toAdminResponse with default usedBytes yields zero`() {
346+
val user = User(
347+
id = "u2",
348+
email = "u2@test.com",
349+
username = "u2",
350+
passwordHash = "h",
351+
role = UserRole.USER,
352+
status = UserStatus.ACTIVE,
353+
quotaBytes = null,
354+
createdAt = testInstant,
355+
updatedAt = testInstant,
356+
)
357+
val response = user.toAdminResponse()
358+
assertEquals(0L, response.usedBytes)
359+
assertNull(response.quotaBytes)
360+
assertNull(response.lastLoginAt)
361+
}
362+
}

backend/core/activity/src/test/kotlin/com/vaultstadio/core/domain/service/ActivityLoggerTest.kt

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.vaultstadio.core.domain.event.FileEvent
1212
import com.vaultstadio.core.domain.event.FolderEvent
1313
import com.vaultstadio.domain.activity.model.ActivityType
1414
import com.vaultstadio.domain.activity.repository.ActivityRepository
15+
import com.vaultstadio.domain.common.exception.StorageBackendException
1516
import com.vaultstadio.domain.storage.model.ItemType
1617
import com.vaultstadio.domain.storage.model.StorageItem
1718
import io.mockk.coEvery
@@ -217,4 +218,68 @@ class ActivityLoggerTest {
217218
)
218219
}
219220
}
221+
222+
@Test
223+
fun `start then publish FileEvent Downloaded with accessedViaShare includes shareId in details`() =
224+
runTest {
225+
activityLogger.start()
226+
val item = StorageItem(
227+
id = "item-share",
228+
name = "shared.pdf",
229+
path = "/shared.pdf",
230+
type = ItemType.FILE,
231+
ownerId = "user-1",
232+
size = 100,
233+
mimeType = "application/pdf",
234+
createdAt = Clock.System.now(),
235+
updatedAt = Clock.System.now(),
236+
)
237+
val event = FileEvent.Downloaded(
238+
id = "ev-share",
239+
timestamp = Clock.System.now(),
240+
userId = "user-1",
241+
item = item,
242+
accessedViaShare = true,
243+
shareId = "share-123",
244+
)
245+
eventBus.publish(event, async = false)
246+
coVerify(exactly = 1) {
247+
activityRepository.create(
248+
match {
249+
it.type == ActivityType.FILE_DOWNLOADED &&
250+
it.itemId == "item-share" &&
251+
it.details != null &&
252+
it.details!!.contains("share-123")
253+
},
254+
)
255+
}
256+
}
257+
258+
@Test
259+
fun `when activityRepository create returns Left logger still invokes create and does not throw`() =
260+
runTest {
261+
coEvery { activityRepository.create(any()) } returns Either.Left(
262+
StorageBackendException("test", "create failed"),
263+
)
264+
activityLogger.start()
265+
val item = StorageItem(
266+
id = "item-err",
267+
name = "x.pdf",
268+
path = "/x.pdf",
269+
type = ItemType.FILE,
270+
ownerId = "user-1",
271+
size = 0,
272+
mimeType = "application/pdf",
273+
createdAt = Clock.System.now(),
274+
updatedAt = Clock.System.now(),
275+
)
276+
val event = FileEvent.Uploaded(
277+
id = "ev-err",
278+
timestamp = Clock.System.now(),
279+
userId = "user-1",
280+
item = item,
281+
)
282+
eventBus.publish(event, async = false)
283+
coVerify(exactly = 1) { activityRepository.create(any()) }
284+
}
220285
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/**
2+
* VaultStadio CollaborationContentApply Tests
3+
*
4+
* Unit tests for applyOperationToContent (insert, delete, retain; position clamping).
5+
*/
6+
7+
package com.vaultstadio.infrastructure.persistence
8+
9+
import com.vaultstadio.core.domain.model.CollaborationOperation
10+
import kotlinx.datetime.Instant
11+
import org.junit.jupiter.api.Test
12+
import kotlin.test.assertEquals
13+
14+
class CollaborationContentApplyTest {
15+
16+
private fun instant(epochMillis: Long): Instant = Instant.fromEpochMilliseconds(epochMillis)
17+
18+
@Test
19+
fun `applyOperationToContent Insert at start prepends text`() {
20+
val op = CollaborationOperation.Insert(
21+
userId = "u1",
22+
timestamp = instant(0),
23+
baseVersion = 1,
24+
position = 0,
25+
text = "Hello",
26+
)
27+
val result = applyOperationToContent("World", op)
28+
assertEquals("HelloWorld", result)
29+
}
30+
31+
@Test
32+
fun `applyOperationToContent Insert in middle inserts text`() {
33+
val op = CollaborationOperation.Insert(
34+
userId = "u1",
35+
timestamp = instant(0),
36+
baseVersion = 1,
37+
position = 2,
38+
text = "XX",
39+
)
40+
val result = applyOperationToContent("abc", op)
41+
assertEquals("abXXc", result)
42+
}
43+
44+
@Test
45+
fun `applyOperationToContent Insert at end appends text`() {
46+
val op = CollaborationOperation.Insert(
47+
userId = "u1",
48+
timestamp = instant(0),
49+
baseVersion = 1,
50+
position = 5,
51+
text = "!",
52+
)
53+
val result = applyOperationToContent("Hello", op)
54+
assertEquals("Hello!", result)
55+
}
56+
57+
@Test
58+
fun `applyOperationToContent Insert position clamped when out of range`() {
59+
val op = CollaborationOperation.Insert(
60+
userId = "u1",
61+
timestamp = instant(0),
62+
baseVersion = 1,
63+
position = 100,
64+
text = "X",
65+
)
66+
val result = applyOperationToContent("ab", op)
67+
assertEquals("abX", result)
68+
}
69+
70+
@Test
71+
fun `applyOperationToContent Delete removes range`() {
72+
val op = CollaborationOperation.Delete(
73+
userId = "u1",
74+
timestamp = instant(0),
75+
baseVersion = 1,
76+
position = 1,
77+
length = 3,
78+
)
79+
val result = applyOperationToContent("Hello", op)
80+
assertEquals("Ho", result)
81+
}
82+
83+
@Test
84+
fun `applyOperationToContent Delete full content yields empty string`() {
85+
val op = CollaborationOperation.Delete(
86+
userId = "u1",
87+
timestamp = instant(0),
88+
baseVersion = 1,
89+
position = 0,
90+
length = 5,
91+
)
92+
val result = applyOperationToContent("Hello", op)
93+
assertEquals("", result)
94+
}
95+
96+
@Test
97+
fun `applyOperationToContent Delete position and length clamped when out of range`() {
98+
val op = CollaborationOperation.Delete(
99+
userId = "u1",
100+
timestamp = instant(0),
101+
baseVersion = 1,
102+
position = 1,
103+
length = 100,
104+
)
105+
val result = applyOperationToContent("abc", op)
106+
assertEquals("a", result)
107+
}
108+
109+
@Test
110+
fun `applyOperationToContent Retain leaves content unchanged`() {
111+
val op = CollaborationOperation.Retain(
112+
userId = "u1",
113+
timestamp = instant(0),
114+
baseVersion = 1,
115+
count = 5,
116+
)
117+
val content = "Hello"
118+
val result = applyOperationToContent(content, op)
119+
assertEquals(content, result)
120+
}
121+
122+
@Test
123+
fun `applyOperationToContent Insert into empty string`() {
124+
val op = CollaborationOperation.Insert(
125+
userId = "u1",
126+
timestamp = instant(0),
127+
baseVersion = 1,
128+
position = 0,
129+
text = "x",
130+
)
131+
val result = applyOperationToContent("", op)
132+
assertEquals("x", result)
133+
}
134+
}

0 commit comments

Comments
 (0)