test: add unit tests for Room DAOs, LocalDateConverter, and JSON roundtrip
LocalDateConverterTest: added negative test for invalid string input (DateTimeParseException). CategoryDaoTest, LocationDaoTest: added getAll tests with multiple entities to verify complete retrieval. ItemDaoTest: fixed getExpiringSoon test (was calling non-existent getExpiringSoon(Int) instead of getExpiringSoonByCutoff(LocalDate)); added getAll, getById positive, and getById negative tests. JsonRoundtripTest (new): verifies lossless export-import roundtrip with multiple items covering all fields, nullable fields (null kcalPer100g, null expiryDate), and empty database edge case. TestFakes (new): extracted shared Fake DAO implementations from ImportExportRepositoryImplTest to avoid private class redeclaration errors across test files in the same package. Closes #22
This commit is contained in:
parent
5825b0351c
commit
0c1e06afca
7 changed files with 408 additions and 119 deletions
|
|
@ -84,4 +84,18 @@ internal class CategoryDaoTest {
|
||||||
// Then
|
// Then
|
||||||
assertNull(result)
|
assertNull(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_getAll_multipleCategories_returnsAll() = runBlocking {
|
||||||
|
// Given
|
||||||
|
dao.insert(CategoryEntity(name = "Lebensmittel"))
|
||||||
|
dao.insert(CategoryEntity(name = "Hygiene"))
|
||||||
|
dao.insert(CategoryEntity(name = "Medizin"))
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = dao.getAll().first()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(3, result.size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
@ -133,16 +134,53 @@ internal class ItemDaoTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_getExpiringSoon_itemExpiresInOneDayWithDaysUntil365_returnsItem() = runBlocking {
|
fun test_getExpiringSoon_itemExpiresInOneDayWithCutoff365Days_returnsItem() = runBlocking {
|
||||||
// Given
|
// Given
|
||||||
val tomorrow = LocalDate.now().plusDays(1)
|
val tomorrow = LocalDate.now().plusDays(1)
|
||||||
dao.insert(buildItem(expiryDate = tomorrow))
|
dao.insert(buildItem(expiryDate = tomorrow))
|
||||||
|
|
||||||
// When
|
// When
|
||||||
val result = dao.getExpiringSoon(365).first()
|
val cutoff = LocalDate.now().plusDays(365)
|
||||||
|
val result = dao.getExpiringSoonByCutoff(cutoff).first()
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
assertEquals(1, result.size)
|
assertEquals(1, result.size)
|
||||||
assertEquals("item-1", result.first().id)
|
assertEquals("item-1", result.first().id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_getAll_multipleItems_returnsAll() = runBlocking {
|
||||||
|
// Given
|
||||||
|
dao.insert(buildItem(id = "item-1"))
|
||||||
|
dao.insert(buildItem(id = "item-2"))
|
||||||
|
dao.insert(buildItem(id = "item-3"))
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = dao.getAll().first()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(3, result.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_getById_existingItem_returnsItem() = runBlocking {
|
||||||
|
// Given
|
||||||
|
dao.insert(buildItem(id = "item-1"))
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = dao.getById("item-1")
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals("item-1", result?.id)
|
||||||
|
assertEquals("Wasser", result?.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_getById_nonExistentId_returnsNull() = runBlocking {
|
||||||
|
// Given / When
|
||||||
|
val result = dao.getById("does-not-exist")
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertNull(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,4 +84,18 @@ internal class LocationDaoTest {
|
||||||
// Then
|
// Then
|
||||||
assertNull(result)
|
assertNull(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_getAll_multipleLocations_returnsAll() = runBlocking {
|
||||||
|
// Given
|
||||||
|
dao.insert(LocationEntity(name = "Keller"))
|
||||||
|
dao.insert(LocationEntity(name = "Garage"))
|
||||||
|
dao.insert(LocationEntity(name = "Dachboden"))
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = dao.getAll().first()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(3, result.size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ package de.krisenvorrat.app.data.db
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertThrows
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
import java.time.format.DateTimeParseException
|
||||||
|
|
||||||
class LocalDateConverterTest {
|
class LocalDateConverterTest {
|
||||||
|
|
||||||
|
|
@ -56,4 +58,15 @@ class LocalDateConverterTest {
|
||||||
// Then
|
// Then
|
||||||
assertNull(result)
|
assertNull(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_toLocalDate_withInvalidString_throwsDateTimeParseException() {
|
||||||
|
// Given
|
||||||
|
val invalidString = "not-a-date"
|
||||||
|
|
||||||
|
// When / Then
|
||||||
|
assertThrows(DateTimeParseException::class.java) {
|
||||||
|
converter.toLocalDate(invalidString)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,131 +1,14 @@
|
||||||
package de.krisenvorrat.app.data.export
|
package de.krisenvorrat.app.data.export
|
||||||
|
|
||||||
import de.krisenvorrat.app.data.db.dao.CategoryDao
|
|
||||||
import de.krisenvorrat.app.data.db.dao.ItemDao
|
|
||||||
import de.krisenvorrat.app.data.db.dao.LocationDao
|
|
||||||
import de.krisenvorrat.app.data.db.dao.SettingsDao
|
|
||||||
import de.krisenvorrat.app.data.db.entity.CategoryEntity
|
import de.krisenvorrat.app.data.db.entity.CategoryEntity
|
||||||
import de.krisenvorrat.app.data.db.entity.ItemEntity
|
|
||||||
import de.krisenvorrat.app.data.db.entity.LocationEntity
|
import de.krisenvorrat.app.data.db.entity.LocationEntity
|
||||||
import de.krisenvorrat.app.data.db.entity.SettingsEntity
|
import de.krisenvorrat.app.data.db.entity.SettingsEntity
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
private class FakeCategoryDao : CategoryDao {
|
|
||||||
private val items = mutableListOf<CategoryEntity>()
|
|
||||||
private val flow = MutableStateFlow<List<CategoryEntity>>(emptyList())
|
|
||||||
|
|
||||||
private fun emit() { flow.value = items.toList() }
|
|
||||||
|
|
||||||
override suspend fun insert(category: CategoryEntity) = throw UnsupportedOperationException()
|
|
||||||
override suspend fun update(category: CategoryEntity) = throw UnsupportedOperationException()
|
|
||||||
override suspend fun delete(category: CategoryEntity) = throw UnsupportedOperationException()
|
|
||||||
override fun getAll(): Flow<List<CategoryEntity>> = flow
|
|
||||||
override suspend fun getById(id: Int): CategoryEntity? = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override suspend fun upsertAll(categories: List<CategoryEntity>) {
|
|
||||||
categories.forEach { cat ->
|
|
||||||
val idx = items.indexOfFirst { it.id == cat.id }
|
|
||||||
if (idx >= 0) items[idx] = cat else items.add(cat)
|
|
||||||
}
|
|
||||||
emit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getItems(): List<CategoryEntity> = items.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FakeLocationDao : LocationDao {
|
|
||||||
private val items = mutableListOf<LocationEntity>()
|
|
||||||
private val flow = MutableStateFlow<List<LocationEntity>>(emptyList())
|
|
||||||
|
|
||||||
private fun emit() { flow.value = items.toList() }
|
|
||||||
|
|
||||||
override suspend fun insert(location: LocationEntity) = throw UnsupportedOperationException()
|
|
||||||
override suspend fun update(location: LocationEntity) = throw UnsupportedOperationException()
|
|
||||||
override suspend fun delete(location: LocationEntity) = throw UnsupportedOperationException()
|
|
||||||
override fun getAll(): Flow<List<LocationEntity>> = flow
|
|
||||||
override suspend fun getById(id: Int): LocationEntity? = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override suspend fun upsertAll(locations: List<LocationEntity>) {
|
|
||||||
locations.forEach { loc ->
|
|
||||||
val idx = items.indexOfFirst { it.id == loc.id }
|
|
||||||
if (idx >= 0) items[idx] = loc else items.add(loc)
|
|
||||||
}
|
|
||||||
emit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getItems(): List<LocationEntity> = items.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FakeItemDao : ItemDao {
|
|
||||||
private val items = mutableListOf<ItemEntity>()
|
|
||||||
private val flow = MutableStateFlow<List<ItemEntity>>(emptyList())
|
|
||||||
|
|
||||||
private fun emit() { flow.value = items.toList() }
|
|
||||||
|
|
||||||
override suspend fun insert(item: ItemEntity) = throw UnsupportedOperationException()
|
|
||||||
override suspend fun update(item: ItemEntity) = throw UnsupportedOperationException()
|
|
||||||
override suspend fun delete(item: ItemEntity) = throw UnsupportedOperationException()
|
|
||||||
override fun getAll(): Flow<List<ItemEntity>> = flow
|
|
||||||
override suspend fun getById(id: String): ItemEntity? = throw UnsupportedOperationException()
|
|
||||||
override fun getByCategory(categoryId: Int): Flow<List<ItemEntity>> = throw UnsupportedOperationException()
|
|
||||||
override fun getByLocation(locationId: Int): Flow<List<ItemEntity>> = throw UnsupportedOperationException()
|
|
||||||
override fun getExpiringSoonByCutoff(cutoff: LocalDate): Flow<List<ItemEntity>> = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override suspend fun upsertAll(items: List<ItemEntity>) {
|
|
||||||
items.forEach { item ->
|
|
||||||
val idx = this.items.indexOfFirst { it.id == item.id }
|
|
||||||
if (idx >= 0) this.items[idx] = item else this.items.add(item)
|
|
||||||
}
|
|
||||||
emit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getItems(): List<ItemEntity> = items.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FakeSettingsDao : SettingsDao {
|
|
||||||
private val items = mutableListOf<SettingsEntity>()
|
|
||||||
private val flow = MutableStateFlow<List<SettingsEntity>>(emptyList())
|
|
||||||
|
|
||||||
private fun emit() { flow.value = items.toList() }
|
|
||||||
|
|
||||||
override suspend fun upsert(setting: SettingsEntity) = throw UnsupportedOperationException()
|
|
||||||
override suspend fun getValue(key: String): String? = throw UnsupportedOperationException()
|
|
||||||
override fun getAll(): Flow<List<SettingsEntity>> = flow
|
|
||||||
|
|
||||||
override suspend fun upsertAll(settings: List<SettingsEntity>) {
|
|
||||||
settings.forEach { setting ->
|
|
||||||
val idx = items.indexOfFirst { it.key == setting.key }
|
|
||||||
if (idx >= 0) items[idx] = setting else items.add(setting)
|
|
||||||
}
|
|
||||||
emit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getItems(): List<SettingsEntity> = items.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val passThroughTransaction = DatabaseTransaction { block -> block() }
|
|
||||||
|
|
||||||
private fun buildItemEntity(id: String = "item1") = ItemEntity(
|
|
||||||
id = id,
|
|
||||||
name = "Konserve",
|
|
||||||
categoryId = 1,
|
|
||||||
quantity = 2.0,
|
|
||||||
unit = "Stk",
|
|
||||||
unitPrice = 1.5,
|
|
||||||
kcalPer100g = null,
|
|
||||||
expiryDate = null,
|
|
||||||
locationId = 1,
|
|
||||||
minStock = 1.0,
|
|
||||||
notes = "",
|
|
||||||
lastUpdated = 0L
|
|
||||||
)
|
|
||||||
|
|
||||||
class ImportExportRepositoryImplTest {
|
class ImportExportRepositoryImplTest {
|
||||||
|
|
||||||
private fun buildRepository(
|
private fun buildRepository(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,204 @@
|
||||||
|
package de.krisenvorrat.app.data.export
|
||||||
|
|
||||||
|
import de.krisenvorrat.app.data.db.entity.CategoryEntity
|
||||||
|
import de.krisenvorrat.app.data.db.entity.ItemEntity
|
||||||
|
import de.krisenvorrat.app.data.db.entity.LocationEntity
|
||||||
|
import de.krisenvorrat.app.data.db.entity.SettingsEntity
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
class JsonRoundtripTest {
|
||||||
|
|
||||||
|
private fun buildRepository(
|
||||||
|
categoryDao: FakeCategoryDao = FakeCategoryDao(),
|
||||||
|
locationDao: FakeLocationDao = FakeLocationDao(),
|
||||||
|
itemDao: FakeItemDao = FakeItemDao(),
|
||||||
|
settingsDao: FakeSettingsDao = FakeSettingsDao()
|
||||||
|
) = ImportExportRepositoryImpl(
|
||||||
|
categoryDao = categoryDao,
|
||||||
|
locationDao = locationDao,
|
||||||
|
itemDao = itemDao,
|
||||||
|
settingsDao = settingsDao,
|
||||||
|
transaction = passThroughTransaction
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_roundtrip_multipleItemsWithAllFields_dataIsPreservedLosslessly() = runBlocking {
|
||||||
|
// Given
|
||||||
|
val categories = listOf(
|
||||||
|
CategoryEntity(id = 1, name = "Lebensmittel"),
|
||||||
|
CategoryEntity(id = 2, name = "Hygiene")
|
||||||
|
)
|
||||||
|
val locations = listOf(
|
||||||
|
LocationEntity(id = 1, name = "Keller"),
|
||||||
|
LocationEntity(id = 2, name = "Garage")
|
||||||
|
)
|
||||||
|
val items = listOf(
|
||||||
|
ItemEntity(
|
||||||
|
id = "item-1",
|
||||||
|
name = "Konserve",
|
||||||
|
categoryId = 1,
|
||||||
|
quantity = 10.0,
|
||||||
|
unit = "Stk",
|
||||||
|
unitPrice = 2.49,
|
||||||
|
kcalPer100g = 180,
|
||||||
|
expiryDate = LocalDate.of(2027, 6, 15),
|
||||||
|
locationId = 1,
|
||||||
|
minStock = 5.0,
|
||||||
|
notes = "Ravioli",
|
||||||
|
lastUpdated = 1700000000L
|
||||||
|
),
|
||||||
|
ItemEntity(
|
||||||
|
id = "item-2",
|
||||||
|
name = "Seife",
|
||||||
|
categoryId = 2,
|
||||||
|
quantity = 3.0,
|
||||||
|
unit = "Stk",
|
||||||
|
unitPrice = 0.99,
|
||||||
|
kcalPer100g = null,
|
||||||
|
expiryDate = null,
|
||||||
|
locationId = 2,
|
||||||
|
minStock = 1.0,
|
||||||
|
notes = "",
|
||||||
|
lastUpdated = 1700000001L
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val settings = listOf(
|
||||||
|
SettingsEntity(key = "theme", value = "dark"),
|
||||||
|
SettingsEntity(key = "language", value = "de")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Export-Repository mit vorhandenen Daten
|
||||||
|
val exportCategoryDao = FakeCategoryDao()
|
||||||
|
val exportLocationDao = FakeLocationDao()
|
||||||
|
val exportItemDao = FakeItemDao()
|
||||||
|
val exportSettingsDao = FakeSettingsDao()
|
||||||
|
exportCategoryDao.upsertAll(categories)
|
||||||
|
exportLocationDao.upsertAll(locations)
|
||||||
|
exportItemDao.upsertAll(items)
|
||||||
|
exportSettingsDao.upsertAll(settings)
|
||||||
|
|
||||||
|
val exportRepo = buildRepository(exportCategoryDao, exportLocationDao, exportItemDao, exportSettingsDao)
|
||||||
|
|
||||||
|
// When – Export
|
||||||
|
val json = exportRepo.exportToJson()
|
||||||
|
|
||||||
|
// Then – Import in leeres Repository
|
||||||
|
val importCategoryDao = FakeCategoryDao()
|
||||||
|
val importLocationDao = FakeLocationDao()
|
||||||
|
val importItemDao = FakeItemDao()
|
||||||
|
val importSettingsDao = FakeSettingsDao()
|
||||||
|
val importRepo = buildRepository(importCategoryDao, importLocationDao, importItemDao, importSettingsDao)
|
||||||
|
|
||||||
|
val result = importRepo.importFromJson(json)
|
||||||
|
|
||||||
|
// Then – Roundtrip ist verlustfrei
|
||||||
|
assertTrue(result.isSuccess)
|
||||||
|
|
||||||
|
// Categories
|
||||||
|
assertEquals(categories.size, importCategoryDao.getItems().size)
|
||||||
|
categories.forEach { original ->
|
||||||
|
val imported = importCategoryDao.getItems().find { it.id == original.id }
|
||||||
|
assertEquals(original.name, imported?.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locations
|
||||||
|
assertEquals(locations.size, importLocationDao.getItems().size)
|
||||||
|
locations.forEach { original ->
|
||||||
|
val imported = importLocationDao.getItems().find { it.id == original.id }
|
||||||
|
assertEquals(original.name, imported?.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Items – alle Felder prüfen
|
||||||
|
assertEquals(items.size, importItemDao.getItems().size)
|
||||||
|
items.forEach { original ->
|
||||||
|
val imported = importItemDao.getItems().find { it.id == original.id }
|
||||||
|
assertEquals(original.name, imported?.name)
|
||||||
|
assertEquals(original.categoryId, imported?.categoryId)
|
||||||
|
assertEquals(original.quantity, imported?.quantity)
|
||||||
|
assertEquals(original.unit, imported?.unit)
|
||||||
|
assertEquals(original.unitPrice, imported?.unitPrice)
|
||||||
|
assertEquals(original.kcalPer100g, imported?.kcalPer100g)
|
||||||
|
assertEquals(original.expiryDate, imported?.expiryDate)
|
||||||
|
assertEquals(original.locationId, imported?.locationId)
|
||||||
|
assertEquals(original.minStock, imported?.minStock)
|
||||||
|
assertEquals(original.notes, imported?.notes)
|
||||||
|
assertEquals(original.lastUpdated, imported?.lastUpdated)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
assertEquals(settings.size, importSettingsDao.getItems().size)
|
||||||
|
settings.forEach { original ->
|
||||||
|
val imported = importSettingsDao.getItems().find { it.key == original.key }
|
||||||
|
assertEquals(original.value, imported?.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_roundtrip_itemWithNullableFields_nullsArePreserved() = runBlocking {
|
||||||
|
// Given
|
||||||
|
val categoryDao = FakeCategoryDao()
|
||||||
|
val locationDao = FakeLocationDao()
|
||||||
|
val itemDao = FakeItemDao()
|
||||||
|
val settingsDao = FakeSettingsDao()
|
||||||
|
categoryDao.upsertAll(listOf(CategoryEntity(id = 1, name = "Test")))
|
||||||
|
locationDao.upsertAll(listOf(LocationEntity(id = 1, name = "Ort")))
|
||||||
|
itemDao.upsertAll(listOf(
|
||||||
|
ItemEntity(
|
||||||
|
id = "null-item",
|
||||||
|
name = "Ohne Extras",
|
||||||
|
categoryId = 1,
|
||||||
|
quantity = 1.0,
|
||||||
|
unit = "Stk",
|
||||||
|
unitPrice = 0.0,
|
||||||
|
kcalPer100g = null,
|
||||||
|
expiryDate = null,
|
||||||
|
locationId = 1,
|
||||||
|
minStock = 0.0,
|
||||||
|
notes = "",
|
||||||
|
lastUpdated = 0L
|
||||||
|
)
|
||||||
|
))
|
||||||
|
val exportRepo = buildRepository(categoryDao, locationDao, itemDao, settingsDao)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val json = exportRepo.exportToJson()
|
||||||
|
val importItemDao = FakeItemDao()
|
||||||
|
val importCatDao = FakeCategoryDao()
|
||||||
|
val importLocDao = FakeLocationDao()
|
||||||
|
val importSetDao = FakeSettingsDao()
|
||||||
|
val importRepo = buildRepository(importCatDao, importLocDao, importItemDao, importSetDao)
|
||||||
|
val result = importRepo.importFromJson(json)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertTrue(result.isSuccess)
|
||||||
|
val imported = importItemDao.getItems().first()
|
||||||
|
assertEquals(null, imported.kcalPer100g)
|
||||||
|
assertEquals(null, imported.expiryDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_roundtrip_emptyDatabase_producesEmptyCollections() = runBlocking {
|
||||||
|
// Given
|
||||||
|
val exportRepo = buildRepository()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val json = exportRepo.exportToJson()
|
||||||
|
val importCategoryDao = FakeCategoryDao()
|
||||||
|
val importLocationDao = FakeLocationDao()
|
||||||
|
val importItemDao = FakeItemDao()
|
||||||
|
val importSettingsDao = FakeSettingsDao()
|
||||||
|
val importRepo = buildRepository(importCategoryDao, importLocationDao, importItemDao, importSettingsDao)
|
||||||
|
val result = importRepo.importFromJson(json)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertTrue(result.isSuccess)
|
||||||
|
assertEquals(0, importCategoryDao.getItems().size)
|
||||||
|
assertEquals(0, importLocationDao.getItems().size)
|
||||||
|
assertEquals(0, importItemDao.getItems().size)
|
||||||
|
assertEquals(0, importSettingsDao.getItems().size)
|
||||||
|
}
|
||||||
|
}
|
||||||
123
app/src/test/java/de/krisenvorrat/app/data/export/TestFakes.kt
Normal file
123
app/src/test/java/de/krisenvorrat/app/data/export/TestFakes.kt
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
package de.krisenvorrat.app.data.export
|
||||||
|
|
||||||
|
import de.krisenvorrat.app.data.db.dao.CategoryDao
|
||||||
|
import de.krisenvorrat.app.data.db.dao.ItemDao
|
||||||
|
import de.krisenvorrat.app.data.db.dao.LocationDao
|
||||||
|
import de.krisenvorrat.app.data.db.dao.SettingsDao
|
||||||
|
import de.krisenvorrat.app.data.db.entity.CategoryEntity
|
||||||
|
import de.krisenvorrat.app.data.db.entity.ItemEntity
|
||||||
|
import de.krisenvorrat.app.data.db.entity.LocationEntity
|
||||||
|
import de.krisenvorrat.app.data.db.entity.SettingsEntity
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
internal class FakeCategoryDao : CategoryDao {
|
||||||
|
private val items = mutableListOf<CategoryEntity>()
|
||||||
|
private val flow = MutableStateFlow<List<CategoryEntity>>(emptyList())
|
||||||
|
|
||||||
|
private fun emit() { flow.value = items.toList() }
|
||||||
|
|
||||||
|
override suspend fun insert(category: CategoryEntity) = throw UnsupportedOperationException()
|
||||||
|
override suspend fun update(category: CategoryEntity) = throw UnsupportedOperationException()
|
||||||
|
override suspend fun delete(category: CategoryEntity) = throw UnsupportedOperationException()
|
||||||
|
override fun getAll(): Flow<List<CategoryEntity>> = flow
|
||||||
|
override suspend fun getById(id: Int): CategoryEntity? = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override suspend fun upsertAll(categories: List<CategoryEntity>) {
|
||||||
|
categories.forEach { cat ->
|
||||||
|
val idx = items.indexOfFirst { it.id == cat.id }
|
||||||
|
if (idx >= 0) items[idx] = cat else items.add(cat)
|
||||||
|
}
|
||||||
|
emit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getItems(): List<CategoryEntity> = items.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class FakeLocationDao : LocationDao {
|
||||||
|
private val items = mutableListOf<LocationEntity>()
|
||||||
|
private val flow = MutableStateFlow<List<LocationEntity>>(emptyList())
|
||||||
|
|
||||||
|
private fun emit() { flow.value = items.toList() }
|
||||||
|
|
||||||
|
override suspend fun insert(location: LocationEntity) = throw UnsupportedOperationException()
|
||||||
|
override suspend fun update(location: LocationEntity) = throw UnsupportedOperationException()
|
||||||
|
override suspend fun delete(location: LocationEntity) = throw UnsupportedOperationException()
|
||||||
|
override fun getAll(): Flow<List<LocationEntity>> = flow
|
||||||
|
override suspend fun getById(id: Int): LocationEntity? = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override suspend fun upsertAll(locations: List<LocationEntity>) {
|
||||||
|
locations.forEach { loc ->
|
||||||
|
val idx = items.indexOfFirst { it.id == loc.id }
|
||||||
|
if (idx >= 0) items[idx] = loc else items.add(loc)
|
||||||
|
}
|
||||||
|
emit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getItems(): List<LocationEntity> = items.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class FakeItemDao : ItemDao {
|
||||||
|
private val items = mutableListOf<ItemEntity>()
|
||||||
|
private val flow = MutableStateFlow<List<ItemEntity>>(emptyList())
|
||||||
|
|
||||||
|
private fun emit() { flow.value = items.toList() }
|
||||||
|
|
||||||
|
override suspend fun insert(item: ItemEntity) = throw UnsupportedOperationException()
|
||||||
|
override suspend fun update(item: ItemEntity) = throw UnsupportedOperationException()
|
||||||
|
override suspend fun delete(item: ItemEntity) = throw UnsupportedOperationException()
|
||||||
|
override fun getAll(): Flow<List<ItemEntity>> = flow
|
||||||
|
override suspend fun getById(id: String): ItemEntity? = throw UnsupportedOperationException()
|
||||||
|
override fun getByCategory(categoryId: Int): Flow<List<ItemEntity>> = throw UnsupportedOperationException()
|
||||||
|
override fun getByLocation(locationId: Int): Flow<List<ItemEntity>> = throw UnsupportedOperationException()
|
||||||
|
override fun getExpiringSoonByCutoff(cutoff: LocalDate): Flow<List<ItemEntity>> = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override suspend fun upsertAll(items: List<ItemEntity>) {
|
||||||
|
items.forEach { item ->
|
||||||
|
val idx = this.items.indexOfFirst { it.id == item.id }
|
||||||
|
if (idx >= 0) this.items[idx] = item else this.items.add(item)
|
||||||
|
}
|
||||||
|
emit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getItems(): List<ItemEntity> = items.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class FakeSettingsDao : SettingsDao {
|
||||||
|
private val items = mutableListOf<SettingsEntity>()
|
||||||
|
private val flow = MutableStateFlow<List<SettingsEntity>>(emptyList())
|
||||||
|
|
||||||
|
private fun emit() { flow.value = items.toList() }
|
||||||
|
|
||||||
|
override suspend fun upsert(setting: SettingsEntity) = throw UnsupportedOperationException()
|
||||||
|
override suspend fun getValue(key: String): String? = throw UnsupportedOperationException()
|
||||||
|
override fun getAll(): Flow<List<SettingsEntity>> = flow
|
||||||
|
|
||||||
|
override suspend fun upsertAll(settings: List<SettingsEntity>) {
|
||||||
|
settings.forEach { setting ->
|
||||||
|
val idx = items.indexOfFirst { it.key == setting.key }
|
||||||
|
if (idx >= 0) items[idx] = setting else items.add(setting)
|
||||||
|
}
|
||||||
|
emit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getItems(): List<SettingsEntity> = items.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val passThroughTransaction = DatabaseTransaction { block -> block() }
|
||||||
|
|
||||||
|
internal fun buildItemEntity(id: String = "item1") = ItemEntity(
|
||||||
|
id = id,
|
||||||
|
name = "Konserve",
|
||||||
|
categoryId = 1,
|
||||||
|
quantity = 2.0,
|
||||||
|
unit = "Stk",
|
||||||
|
unitPrice = 1.5,
|
||||||
|
kcalPer100g = null,
|
||||||
|
expiryDate = null,
|
||||||
|
locationId = 1,
|
||||||
|
minStock = 1.0,
|
||||||
|
notes = "",
|
||||||
|
lastUpdated = 0L
|
||||||
|
)
|
||||||
Loading…
Reference in a new issue