Skip to content

Commit 6f7f3a2

Browse files
committed
Enhance test coverage by adding new test cases for UserInfo and CreateShareRequest mappings in ApiResponseTest, and improve error handling in ThumbnailCacheTest. Extend VersionApiModelsTest with additional tests for FileVersion and VersionDiff. Update TEST_COVERAGE_ACTION_PLAN.md to reflect recent improvements and new test additions.
1 parent 40e9f3d commit 6f7f3a2

12 files changed

Lines changed: 1135 additions & 11 deletions

File tree

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

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package com.vaultstadio.api.dto
77
import com.vaultstadio.domain.activity.model.Activity
88
import com.vaultstadio.domain.activity.model.ActivityType
99
import com.vaultstadio.domain.auth.model.User
10+
import com.vaultstadio.domain.auth.model.UserInfo
1011
import com.vaultstadio.domain.auth.model.UserRole
1112
import com.vaultstadio.domain.auth.model.UserStatus
1213
import com.vaultstadio.domain.common.pagination.PagedResult
@@ -282,6 +283,50 @@ class ActivityToResponseTest {
282283
}
283284
}
284285

286+
class UserInfoToResponseTest {
287+
288+
private val testInstant = Instant.fromEpochMilliseconds(0L)
289+
290+
@Test
291+
fun `toResponse maps UserInfo to UserResponse`() {
292+
val info = UserInfo(
293+
id = "info-1",
294+
email = "info@example.com",
295+
username = "infouser",
296+
role = UserRole.USER,
297+
status = UserStatus.ACTIVE,
298+
quotaBytes = 5_000L,
299+
avatarUrl = "https://example.com/avatar.jpg",
300+
createdAt = testInstant,
301+
)
302+
val response = info.toResponse()
303+
assertEquals("info-1", response.id)
304+
assertEquals("info@example.com", response.email)
305+
assertEquals("infouser", response.username)
306+
assertEquals(UserRole.USER, response.role)
307+
assertEquals(UserStatus.ACTIVE, response.status)
308+
assertEquals("https://example.com/avatar.jpg", response.avatarUrl)
309+
assertEquals(testInstant, response.createdAt)
310+
}
311+
312+
@Test
313+
fun `toResponse with null avatarUrl`() {
314+
val info = UserInfo(
315+
id = "u2",
316+
email = "u2@test.com",
317+
username = "u2",
318+
role = UserRole.ADMIN,
319+
status = UserStatus.ACTIVE,
320+
quotaBytes = null,
321+
avatarUrl = null,
322+
createdAt = testInstant,
323+
)
324+
val response = info.toResponse()
325+
assertNull(response.avatarUrl)
326+
assertEquals("u2", response.id)
327+
}
328+
}
329+
285330
class UserToResponseTest {
286331

287332
private val testInstant = Instant.fromEpochMilliseconds(0L)
@@ -360,3 +405,154 @@ class UserToAdminResponseTest {
360405
assertNull(response.lastLoginAt)
361406
}
362407
}
408+
409+
class CreateShareRequestTest {
410+
411+
@Test
412+
fun `holds itemId and optional fields`() {
413+
val req = CreateShareRequest(
414+
itemId = "item-1",
415+
expirationDays = 7,
416+
password = "secret",
417+
maxDownloads = 10,
418+
)
419+
assertEquals("item-1", req.itemId)
420+
assertEquals(7, req.expirationDays)
421+
assertEquals("secret", req.password)
422+
assertEquals(10, req.maxDownloads)
423+
}
424+
425+
@Test
426+
fun `defaults optional fields to null`() {
427+
val req = CreateShareRequest(itemId = "item-2")
428+
assertEquals("item-2", req.itemId)
429+
assertNull(req.expirationDays)
430+
assertNull(req.password)
431+
assertNull(req.maxDownloads)
432+
}
433+
}
434+
435+
class AccessShareRequestTest {
436+
437+
@Test
438+
fun `holds optional password`() {
439+
val req = AccessShareRequest(password = "pwd")
440+
assertEquals("pwd", req.password)
441+
}
442+
443+
@Test
444+
fun `defaults password to null`() {
445+
val req = AccessShareRequest()
446+
assertNull(req.password)
447+
}
448+
}
449+
450+
class SearchRequestTest {
451+
452+
@Test
453+
fun `holds query and optional params`() {
454+
val req = SearchRequest(
455+
query = "test",
456+
type = ItemType.FILE,
457+
mimeType = "image/*",
458+
limit = 20,
459+
offset = 10,
460+
)
461+
assertEquals("test", req.query)
462+
assertEquals(ItemType.FILE, req.type)
463+
assertEquals("image/*", req.mimeType)
464+
assertEquals(20, req.limit)
465+
assertEquals(10, req.offset)
466+
}
467+
468+
@Test
469+
fun `defaults type mimeType offset and limit`() {
470+
val req = SearchRequest(query = "q")
471+
assertEquals("q", req.query)
472+
assertNull(req.type)
473+
assertNull(req.mimeType)
474+
assertEquals(50, req.limit)
475+
assertEquals(0, req.offset)
476+
}
477+
}
478+
479+
class PluginInfoResponseTest {
480+
481+
@Test
482+
fun `holds all plugin info fields`() {
483+
val res = PluginInfoResponse(
484+
id = "plugin-1",
485+
name = "Image Metadata",
486+
version = "1.0.0",
487+
description = "Extracts EXIF",
488+
author = "VaultStadio",
489+
isEnabled = true,
490+
state = "loaded",
491+
)
492+
assertEquals("plugin-1", res.id)
493+
assertEquals("Image Metadata", res.name)
494+
assertEquals("1.0.0", res.version)
495+
assertTrue(res.isEnabled)
496+
assertEquals("loaded", res.state)
497+
}
498+
}
499+
500+
class CreateFolderRequestTest {
501+
502+
@Test
503+
fun `holds name and optional parentId`() {
504+
val req = CreateFolderRequest(name = "Documents", parentId = "parent-1")
505+
assertEquals("Documents", req.name)
506+
assertEquals("parent-1", req.parentId)
507+
}
508+
509+
@Test
510+
fun `defaults parentId to null`() {
511+
val req = CreateFolderRequest(name = "New Folder")
512+
assertEquals("New Folder", req.name)
513+
assertNull(req.parentId)
514+
}
515+
}
516+
517+
class RenameRequestTest {
518+
519+
@Test
520+
fun `holds name`() {
521+
val req = RenameRequest(name = "renamed.txt")
522+
assertEquals("renamed.txt", req.name)
523+
}
524+
}
525+
526+
class MoveRequestTest {
527+
528+
@Test
529+
fun `holds destinationId and optional newName`() {
530+
val req = MoveRequest(destinationId = "folder-1", newName = "moved.pdf")
531+
assertEquals("folder-1", req.destinationId)
532+
assertEquals("moved.pdf", req.newName)
533+
}
534+
535+
@Test
536+
fun `defaults newName to null`() {
537+
val req = MoveRequest(destinationId = null)
538+
assertNull(req.destinationId)
539+
assertNull(req.newName)
540+
}
541+
}
542+
543+
class CopyRequestTest {
544+
545+
@Test
546+
fun `holds destinationId and optional newName`() {
547+
val req = CopyRequest(destinationId = "target-folder", newName = "copy.pdf")
548+
assertEquals("target-folder", req.destinationId)
549+
assertEquals("copy.pdf", req.newName)
550+
}
551+
552+
@Test
553+
fun `defaults newName to null`() {
554+
val req = CopyRequest(destinationId = "dest")
555+
assertEquals("dest", req.destinationId)
556+
assertNull(req.newName)
557+
}
558+
}

backend/api/src/test/kotlin/com/vaultstadio/api/service/ThumbnailCacheTest.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,14 @@ class ThumbnailCacheTest {
155155
assertNull(cache.get(key(itemId = "i1", size = "128")))
156156
assertNotNull(cache.get(key(itemId = "i2")))
157157
}
158+
159+
@Test
160+
fun `invalidate for non-existent itemId does not throw`() {
161+
cache.put(key(itemId = "i1"), thumbnail())
162+
cache.invalidate("other-item")
163+
assertNotNull(cache.get(key(itemId = "i1")))
164+
assertEquals(1, cache.getStats().size)
165+
}
158166
}
159167

160168
@Nested

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

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,4 +282,155 @@ class ActivityLoggerTest {
282282
eventBus.publish(event, async = false)
283283
coVerify(exactly = 1) { activityRepository.create(any()) }
284284
}
285+
286+
@Test
287+
fun `start then publish FileEvent Deleted calls activityRepository create with FILE_DELETED`() =
288+
runTest {
289+
activityLogger.start()
290+
val item = StorageItem(
291+
id = "item-del",
292+
name = "old.pdf",
293+
path = "/old.pdf",
294+
type = ItemType.FILE,
295+
ownerId = "user-1",
296+
size = 100,
297+
mimeType = "application/pdf",
298+
createdAt = Clock.System.now(),
299+
updatedAt = Clock.System.now(),
300+
)
301+
val event = FileEvent.Deleted(
302+
id = "ev-del",
303+
timestamp = Clock.System.now(),
304+
userId = "user-1",
305+
item = item,
306+
permanent = true,
307+
)
308+
eventBus.publish(event, async = false)
309+
coVerify(exactly = 1) {
310+
activityRepository.create(
311+
match {
312+
it.type == ActivityType.FILE_DELETED &&
313+
it.itemId == "item-del" &&
314+
it.details != null &&
315+
it.details!!.contains("true")
316+
},
317+
)
318+
}
319+
}
320+
321+
@Test
322+
fun `start then publish FileEvent Renamed calls activityRepository create with FILE_RENAMED`() =
323+
runTest {
324+
activityLogger.start()
325+
val item = StorageItem(
326+
id = "item-renamed",
327+
name = "new-name.txt",
328+
path = "/new-name.txt",
329+
type = ItemType.FILE,
330+
ownerId = "user-1",
331+
size = 50,
332+
mimeType = "text/plain",
333+
createdAt = Clock.System.now(),
334+
updatedAt = Clock.System.now(),
335+
)
336+
val event = FileEvent.Renamed(
337+
id = "ev-renamed",
338+
timestamp = Clock.System.now(),
339+
userId = "user-1",
340+
item = item,
341+
previousName = "old-name.txt",
342+
)
343+
eventBus.publish(event, async = false)
344+
coVerify(exactly = 1) {
345+
activityRepository.create(
346+
match {
347+
it.type == ActivityType.FILE_RENAMED &&
348+
it.itemId == "item-renamed" &&
349+
it.details != null &&
350+
it.details!!.contains("old-name.txt") &&
351+
it.details!!.contains("new-name.txt")
352+
},
353+
)
354+
}
355+
}
356+
357+
@Test
358+
fun `start then publish FileEvent Restored calls activityRepository create with FILE_RESTORED`() =
359+
runTest {
360+
activityLogger.start()
361+
val item = StorageItem(
362+
id = "item-restored",
363+
name = "doc.pdf",
364+
path = "/doc.pdf",
365+
type = ItemType.FILE,
366+
ownerId = "user-1",
367+
size = 100,
368+
mimeType = "application/pdf",
369+
createdAt = Clock.System.now(),
370+
updatedAt = Clock.System.now(),
371+
)
372+
val event = FileEvent.Restored(
373+
id = "ev-restored",
374+
timestamp = Clock.System.now(),
375+
userId = "user-1",
376+
item = item,
377+
)
378+
eventBus.publish(event, async = false)
379+
coVerify(exactly = 1) {
380+
activityRepository.create(
381+
match {
382+
it.type == ActivityType.FILE_RESTORED &&
383+
it.itemId == "item-restored" &&
384+
it.itemPath == "/doc.pdf"
385+
},
386+
)
387+
}
388+
}
389+
390+
@Test
391+
fun `start then publish FileEvent Copied calls activityRepository create with FILE_COPIED`() =
392+
runTest {
393+
activityLogger.start()
394+
val sourceItem = StorageItem(
395+
id = "source-1",
396+
name = "original.pdf",
397+
path = "/original.pdf",
398+
type = ItemType.FILE,
399+
ownerId = "user-1",
400+
size = 200,
401+
mimeType = "application/pdf",
402+
createdAt = Clock.System.now(),
403+
updatedAt = Clock.System.now(),
404+
)
405+
val item = StorageItem(
406+
id = "copy-1",
407+
name = "copy.pdf",
408+
path = "/copy.pdf",
409+
type = ItemType.FILE,
410+
ownerId = "user-1",
411+
size = 200,
412+
mimeType = "application/pdf",
413+
createdAt = Clock.System.now(),
414+
updatedAt = Clock.System.now(),
415+
)
416+
val event = FileEvent.Copied(
417+
id = "ev-copied",
418+
timestamp = Clock.System.now(),
419+
userId = "user-1",
420+
item = item,
421+
sourceItem = sourceItem,
422+
)
423+
eventBus.publish(event, async = false)
424+
coVerify(exactly = 1) {
425+
activityRepository.create(
426+
match {
427+
it.type == ActivityType.FILE_COPIED &&
428+
it.itemId == "copy-1" &&
429+
it.details != null &&
430+
it.details!!.contains("source-1") &&
431+
it.details!!.contains("original.pdf")
432+
},
433+
)
434+
}
435+
}
285436
}

0 commit comments

Comments
 (0)