feat: implement Android database layer with Room
- Add SubscriptionEntity, FeedItemEntity, SearchHistoryEntity - Create SubscriptionDao, FeedItemDao, SearchHistoryDao with CRUD operations - Implement RssDatabase with FTS5 virtual table for full-text search - Add type converters for Date, String lists, and FeedItem lists - Implement cascade delete for feed items when subscription is removed - Add comprehensive unit tests for all DAOs - Add database integration tests for entity round-trips and FTS - Configure Room testing dependencies
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
package com.rssuper.database
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.rssuper.database.daos.FeedItemDao
|
||||
import com.rssuper.database.entities.FeedItemEntity
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.util.Date
|
||||
|
||||
class FeedItemDaoTest {
|
||||
|
||||
private lateinit var database: RssDatabase
|
||||
private lateinit var dao: FeedItemDao
|
||||
|
||||
@Before
|
||||
fun createDb() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
database = Room.inMemoryDatabaseBuilder(
|
||||
context,
|
||||
RssDatabase::class.java
|
||||
)
|
||||
.allowMainThreadQueries()
|
||||
.build()
|
||||
dao = database.feedItemDao()
|
||||
}
|
||||
|
||||
@After
|
||||
fun closeDb() {
|
||||
database.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertAndGetItem() = runTest {
|
||||
val item = createTestItem("1", "sub1")
|
||||
|
||||
dao.insertItem(item)
|
||||
|
||||
val result = dao.getItemById("1")
|
||||
assertNotNull(result)
|
||||
assertEquals("1", result?.id)
|
||||
assertEquals("Test Item", result?.title)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getItemsBySubscription() = runTest {
|
||||
val item1 = createTestItem("1", "sub1")
|
||||
val item2 = createTestItem("2", "sub1")
|
||||
val item3 = createTestItem("3", "sub2")
|
||||
|
||||
dao.insertItems(listOf(item1, item2, item3))
|
||||
|
||||
val result = dao.getItemsBySubscription("sub1").first()
|
||||
assertEquals(2, result.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getItemsBySubscriptions() = runTest {
|
||||
val item1 = createTestItem("1", "sub1")
|
||||
val item2 = createTestItem("2", "sub2")
|
||||
val item3 = createTestItem("3", "sub3")
|
||||
|
||||
dao.insertItems(listOf(item1, item2, item3))
|
||||
|
||||
val result = dao.getItemsBySubscriptions(listOf("sub1", "sub2")).first()
|
||||
assertEquals(2, result.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getUnreadItems() = runTest {
|
||||
val unread = createTestItem("1", "sub1", isRead = false)
|
||||
val read = createTestItem("2", "sub1", isRead = true)
|
||||
|
||||
dao.insertItems(listOf(unread, read))
|
||||
|
||||
val result = dao.getUnreadItems().first()
|
||||
assertEquals(1, result.size)
|
||||
assertEquals("1", result[0].id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getStarredItems() = runTest {
|
||||
val starred = createTestItem("1", "sub1", isStarred = true)
|
||||
val notStarred = createTestItem("2", "sub1", isStarred = false)
|
||||
|
||||
dao.insertItems(listOf(starred, notStarred))
|
||||
|
||||
val result = dao.getStarredItems().first()
|
||||
assertEquals(1, result.size)
|
||||
assertEquals("1", result[0].id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getItemsAfterDate() = runTest {
|
||||
val oldDate = Date(System.currentTimeMillis() - 86400000 * 2)
|
||||
val newDate = Date(System.currentTimeMillis() - 86400000)
|
||||
val today = Date()
|
||||
|
||||
val oldItem = createTestItem("1", "sub1", published = oldDate)
|
||||
val newItem = createTestItem("2", "sub1", published = newDate)
|
||||
val todayItem = createTestItem("3", "sub1", published = today)
|
||||
|
||||
dao.insertItems(listOf(oldItem, newItem, todayItem))
|
||||
|
||||
val result = dao.getItemsAfterDate(newDate).first()
|
||||
assertEquals(1, result.size)
|
||||
assertEquals("3", result[0].id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getUnreadCount() = runTest {
|
||||
val unread1 = createTestItem("1", "sub1", isRead = false)
|
||||
val unread2 = createTestItem("2", "sub1", isRead = false)
|
||||
val read = createTestItem("3", "sub1", isRead = true)
|
||||
|
||||
dao.insertItems(listOf(unread1, unread2, read))
|
||||
|
||||
val count = dao.getUnreadCount("sub1").first()
|
||||
assertEquals(2, count)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getTotalUnreadCount() = runTest {
|
||||
val unread1 = createTestItem("1", "sub1", isRead = false)
|
||||
val unread2 = createTestItem("2", "sub2", isRead = false)
|
||||
val read = createTestItem("3", "sub1", isRead = true)
|
||||
|
||||
dao.insertItems(listOf(unread1, unread2, read))
|
||||
|
||||
val count = dao.getTotalUnreadCount().first()
|
||||
assertEquals(2, count)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateItem() = runTest {
|
||||
val item = createTestItem("1", "sub1")
|
||||
|
||||
dao.insertItem(item)
|
||||
|
||||
val updated = item.copy(title = "Updated Title")
|
||||
dao.updateItem(updated)
|
||||
|
||||
val result = dao.getItemById("1")
|
||||
assertEquals("Updated Title", result?.title)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteItem() = runTest {
|
||||
val item = createTestItem("1", "sub1")
|
||||
|
||||
dao.insertItem(item)
|
||||
dao.deleteItem(item)
|
||||
|
||||
val result = dao.getItemById("1")
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteItemById() = runTest {
|
||||
val item = createTestItem("1", "sub1")
|
||||
|
||||
dao.insertItem(item)
|
||||
dao.deleteItemById("1")
|
||||
|
||||
val result = dao.getItemById("1")
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteItemsBySubscription() = runTest {
|
||||
val item1 = createTestItem("1", "sub1")
|
||||
val item2 = createTestItem("2", "sub1")
|
||||
val item3 = createTestItem("3", "sub2")
|
||||
|
||||
dao.insertItems(listOf(item1, item2, item3))
|
||||
dao.deleteItemsBySubscription("sub1")
|
||||
|
||||
val sub1Items = dao.getItemsBySubscription("sub1").first()
|
||||
val sub2Items = dao.getItemsBySubscription("sub2").first()
|
||||
|
||||
assertEquals(0, sub1Items.size)
|
||||
assertEquals(1, sub2Items.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun markAsRead() = runTest {
|
||||
val item = createTestItem("1", "sub1", isRead = false)
|
||||
|
||||
dao.insertItem(item)
|
||||
dao.markAsRead("1")
|
||||
|
||||
val result = dao.getItemById("1")
|
||||
assertEquals(true, result?.isRead)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun markAsUnread() = runTest {
|
||||
val item = createTestItem("1", "sub1", isRead = true)
|
||||
|
||||
dao.insertItem(item)
|
||||
dao.markAsUnread("1")
|
||||
|
||||
val result = dao.getItemById("1")
|
||||
assertEquals(false, result?.isRead)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun markAsStarred() = runTest {
|
||||
val item = createTestItem("1", "sub1", isStarred = false)
|
||||
|
||||
dao.insertItem(item)
|
||||
dao.markAsStarred("1")
|
||||
|
||||
val result = dao.getItemById("1")
|
||||
assertEquals(true, result?.isStarred)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun markAsUnstarred() = runTest {
|
||||
val item = createTestItem("1", "sub1", isStarred = true)
|
||||
|
||||
dao.insertItem(item)
|
||||
dao.markAsUnstarred("1")
|
||||
|
||||
val result = dao.getItemById("1")
|
||||
assertEquals(false, result?.isStarred)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun markAllAsRead() = runTest {
|
||||
val item1 = createTestItem("1", "sub1", isRead = false)
|
||||
val item2 = createTestItem("2", "sub1", isRead = false)
|
||||
val item3 = createTestItem("3", "sub2", isRead = false)
|
||||
|
||||
dao.insertItems(listOf(item1, item2, item3))
|
||||
dao.markAllAsRead("sub1")
|
||||
|
||||
val sub1Items = dao.getItemsBySubscription("sub1").first()
|
||||
val sub2Items = dao.getItemsBySubscription("sub2").first()
|
||||
|
||||
assertEquals(true, sub1Items[0].isRead)
|
||||
assertEquals(true, sub1Items[1].isRead)
|
||||
assertEquals(false, sub2Items[0].isRead)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getItemsPaginated() = runTest {
|
||||
for (i in 1..10) {
|
||||
val item = createTestItem(i.toString(), "sub1")
|
||||
dao.insertItem(item)
|
||||
}
|
||||
|
||||
val firstPage = dao.getItemsPaginated("sub1", 5, 0)
|
||||
val secondPage = dao.getItemsPaginated("sub1", 5, 5)
|
||||
|
||||
assertEquals(5, firstPage.size)
|
||||
assertEquals(5, secondPage.size)
|
||||
}
|
||||
|
||||
private fun createTestItem(
|
||||
id: String,
|
||||
subscriptionId: String,
|
||||
title: String = "Test Item",
|
||||
isRead: Boolean = false,
|
||||
isStarred: Boolean = false,
|
||||
published: Date = Date()
|
||||
): FeedItemEntity {
|
||||
return FeedItemEntity(
|
||||
id = id,
|
||||
subscriptionId = subscriptionId,
|
||||
title = title,
|
||||
link = "https://example.com/$id",
|
||||
description = "Test description",
|
||||
content = "Test content",
|
||||
author = "Test Author",
|
||||
published = published,
|
||||
updated = published,
|
||||
categories = "Tech,News",
|
||||
enclosureUrl = null,
|
||||
enclosureType = null,
|
||||
enclosureLength = null,
|
||||
guid = "guid-$id",
|
||||
isRead = isRead,
|
||||
isStarred = isStarred
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package com.rssuper.database
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.rssuper.database.entities.FeedItemEntity
|
||||
import com.rssuper.database.entities.SearchHistoryEntity
|
||||
import com.rssuper.database.entities.SubscriptionEntity
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.util.Date
|
||||
import java.util.UUID
|
||||
|
||||
class RssDatabaseTest {
|
||||
|
||||
private lateinit var database: RssDatabase
|
||||
|
||||
@Before
|
||||
fun createDb() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
database = Room.inMemoryDatabaseBuilder(
|
||||
context,
|
||||
RssDatabase::class.java
|
||||
)
|
||||
.allowMainThreadQueries()
|
||||
.build()
|
||||
}
|
||||
|
||||
@After
|
||||
fun closeDb() {
|
||||
database.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun databaseConstruction() {
|
||||
assertNotNull(database.subscriptionDao())
|
||||
assertNotNull(database.feedItemDao())
|
||||
assertNotNull(database.searchHistoryDao())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ftsVirtualTableExists() {
|
||||
val cursor = database.run {
|
||||
openHelper.writableDatabase.rawQuery(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='feed_items_fts'",
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
assertEquals(true, cursor.moveToFirst())
|
||||
cursor.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subscriptionEntityRoundTrip() = runTest {
|
||||
val now = Date()
|
||||
val subscription = SubscriptionEntity(
|
||||
id = UUID.randomUUID().toString(),
|
||||
url = "https://example.com/feed",
|
||||
title = "Test Feed",
|
||||
category = "Tech",
|
||||
enabled = true,
|
||||
fetchInterval = 3600000,
|
||||
createdAt = now,
|
||||
updatedAt = now,
|
||||
lastFetchedAt = null,
|
||||
nextFetchAt = null,
|
||||
error = null,
|
||||
httpAuthUsername = null,
|
||||
httpAuthPassword = null
|
||||
)
|
||||
|
||||
database.subscriptionDao().insertSubscription(subscription)
|
||||
|
||||
val result = database.subscriptionDao().getSubscriptionById(subscription.id)
|
||||
assertNotNull(result)
|
||||
assertEquals(subscription.id, result?.id)
|
||||
assertEquals(subscription.title, result?.title)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun feedItemEntityRoundTrip() = runTest {
|
||||
val now = Date()
|
||||
val subscription = SubscriptionEntity(
|
||||
id = "sub1",
|
||||
url = "https://example.com/feed",
|
||||
title = "Test Feed",
|
||||
category = "Tech",
|
||||
enabled = true,
|
||||
fetchInterval = 3600000,
|
||||
createdAt = now,
|
||||
updatedAt = now,
|
||||
lastFetchedAt = null,
|
||||
nextFetchAt = null,
|
||||
error = null,
|
||||
httpAuthUsername = null,
|
||||
httpAuthPassword = null
|
||||
)
|
||||
database.subscriptionDao().insertSubscription(subscription)
|
||||
|
||||
val item = FeedItemEntity(
|
||||
id = UUID.randomUUID().toString(),
|
||||
subscriptionId = "sub1",
|
||||
title = "Test Item",
|
||||
link = "https://example.com/item",
|
||||
description = "Test description",
|
||||
content = "Test content",
|
||||
author = "Test Author",
|
||||
published = now,
|
||||
updated = now,
|
||||
categories = "Tech",
|
||||
enclosureUrl = null,
|
||||
enclosureType = null,
|
||||
enclosureLength = null,
|
||||
guid = "guid-1",
|
||||
isRead = false,
|
||||
isStarred = false
|
||||
)
|
||||
|
||||
database.feedItemDao().insertItem(item)
|
||||
|
||||
val result = database.feedItemDao().getItemById(item.id)
|
||||
assertNotNull(result)
|
||||
assertEquals(item.id, result?.id)
|
||||
assertEquals(item.title, result?.title)
|
||||
assertEquals("sub1", result?.subscriptionId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun searchHistoryEntityRoundTrip() = runTest {
|
||||
val now = Date()
|
||||
val search = SearchHistoryEntity(
|
||||
id = UUID.randomUUID().toString(),
|
||||
query = "kotlin coroutines",
|
||||
timestamp = now
|
||||
)
|
||||
|
||||
database.searchHistoryDao().insertSearchHistory(search)
|
||||
|
||||
val result = database.searchHistoryDao().getSearchHistoryById(search.id)
|
||||
assertNotNull(result)
|
||||
assertEquals(search.id, result?.id)
|
||||
assertEquals(search.query, result?.query)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cascadeDeleteFeedItems() = runTest {
|
||||
val now = Date()
|
||||
val subscription = SubscriptionEntity(
|
||||
id = "sub1",
|
||||
url = "https://example.com/feed",
|
||||
title = "Test Feed",
|
||||
category = "Tech",
|
||||
enabled = true,
|
||||
fetchInterval = 3600000,
|
||||
createdAt = now,
|
||||
updatedAt = now,
|
||||
lastFetchedAt = null,
|
||||
nextFetchAt = null,
|
||||
error = null,
|
||||
httpAuthUsername = null,
|
||||
httpAuthPassword = null
|
||||
)
|
||||
database.subscriptionDao().insertSubscription(subscription)
|
||||
|
||||
val item = FeedItemEntity(
|
||||
id = "item1",
|
||||
subscriptionId = "sub1",
|
||||
title = "Test Item",
|
||||
link = "https://example.com/item",
|
||||
description = "Test description",
|
||||
content = "Test content",
|
||||
author = "Test Author",
|
||||
published = now,
|
||||
updated = now,
|
||||
categories = "Tech",
|
||||
enclosureUrl = null,
|
||||
enclosureType = null,
|
||||
enclosureLength = null,
|
||||
guid = "guid-1",
|
||||
isRead = false,
|
||||
isStarred = false
|
||||
)
|
||||
database.feedItemDao().insertItem(item)
|
||||
|
||||
database.subscriptionDao().deleteSubscription(subscription)
|
||||
|
||||
val items = database.feedItemDao().getItemsBySubscription("sub1").first()
|
||||
assertEquals(0, items.size)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package com.rssuper.database
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.rssuper.database.daos.SearchHistoryDao
|
||||
import com.rssuper.database.entities.SearchHistoryEntity
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.util.Date
|
||||
|
||||
class SearchHistoryDaoTest {
|
||||
|
||||
private lateinit var database: RssDatabase
|
||||
private lateinit var dao: SearchHistoryDao
|
||||
|
||||
@Before
|
||||
fun createDb() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
database = Room.inMemoryDatabaseBuilder(
|
||||
context,
|
||||
RssDatabase::class.java
|
||||
)
|
||||
.allowMainThreadQueries()
|
||||
.build()
|
||||
dao = database.searchHistoryDao()
|
||||
}
|
||||
|
||||
@After
|
||||
fun closeDb() {
|
||||
database.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertAndGetSearchHistory() = runTest {
|
||||
val search = createTestSearch("1", "kotlin")
|
||||
|
||||
dao.insertSearchHistory(search)
|
||||
|
||||
val result = dao.getSearchHistoryById("1")
|
||||
assertNotNull(result)
|
||||
assertEquals("1", result?.id)
|
||||
assertEquals("kotlin", result?.query)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getAllSearchHistory() = runTest {
|
||||
val search1 = createTestSearch("1", "kotlin")
|
||||
val search2 = createTestSearch("2", "android")
|
||||
val search3 = createTestSearch("3", "room database")
|
||||
|
||||
dao.insertSearchHistories(listOf(search1, search2, search3))
|
||||
|
||||
val result = dao.getAllSearchHistory().first()
|
||||
assertEquals(3, result.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun searchHistory() = runTest {
|
||||
val search1 = createTestSearch("1", "kotlin coroutines")
|
||||
val search2 = createTestSearch("2", "android kotlin")
|
||||
val search3 = createTestSearch("3", "java")
|
||||
|
||||
dao.insertSearchHistories(listOf(search1, search2, search3))
|
||||
|
||||
val result = dao.searchHistory("kotlin").first()
|
||||
assertEquals(2, result.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getRecentSearches() = runTest {
|
||||
val search1 = createTestSearch("1", "query1", timestamp = Date(System.currentTimeMillis() - 300000))
|
||||
val search2 = createTestSearch("2", "query2", timestamp = Date(System.currentTimeMillis() - 200000))
|
||||
val search3 = createTestSearch("3", "query3", timestamp = Date(System.currentTimeMillis() - 100000))
|
||||
|
||||
dao.insertSearchHistories(listOf(search1, search2, search3))
|
||||
|
||||
val result = dao.getRecentSearches(2).first()
|
||||
assertEquals(2, result.size)
|
||||
assertEquals("3", result[0].id)
|
||||
assertEquals("2", result[1].id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getSearchHistoryCount() = runTest {
|
||||
val search1 = createTestSearch("1", "query1")
|
||||
val search2 = createTestSearch("2", "query2")
|
||||
val search3 = createTestSearch("3", "query3")
|
||||
|
||||
dao.insertSearchHistories(listOf(search1, search2, search3))
|
||||
|
||||
val count = dao.getSearchHistoryCount().first()
|
||||
assertEquals(3, count)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateSearchHistory() = runTest {
|
||||
val search = createTestSearch("1", "old query")
|
||||
|
||||
dao.insertSearchHistory(search)
|
||||
|
||||
val updated = search.copy(query = "new query")
|
||||
dao.updateSearchHistory(updated)
|
||||
|
||||
val result = dao.getSearchHistoryById("1")
|
||||
assertEquals("new query", result?.query)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteSearchHistory() = runTest {
|
||||
val search = createTestSearch("1", "kotlin")
|
||||
|
||||
dao.insertSearchHistory(search)
|
||||
dao.deleteSearchHistory(search)
|
||||
|
||||
val result = dao.getSearchHistoryById("1")
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteSearchHistoryById() = runTest {
|
||||
val search = createTestSearch("1", "kotlin")
|
||||
|
||||
dao.insertSearchHistory(search)
|
||||
dao.deleteSearchHistoryById("1")
|
||||
|
||||
val result = dao.getSearchHistoryById("1")
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteAllSearchHistory() = runTest {
|
||||
val search1 = createTestSearch("1", "query1")
|
||||
val search2 = createTestSearch("2", "query2")
|
||||
|
||||
dao.insertSearchHistories(listOf(search1, search2))
|
||||
dao.deleteAllSearchHistory()
|
||||
|
||||
val result = dao.getAllSearchHistory().first()
|
||||
assertEquals(0, result.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteSearchHistoryOlderThan() = runTest {
|
||||
val oldSearch = createTestSearch("1", "old query", timestamp = Date(System.currentTimeMillis() - 86400000 * 2))
|
||||
val recentSearch = createTestSearch("2", "recent query", timestamp = Date(System.currentTimeMillis() - 86400000))
|
||||
|
||||
dao.insertSearchHistories(listOf(oldSearch, recentSearch))
|
||||
dao.deleteSearchHistoryOlderThan(System.currentTimeMillis() - 86400000)
|
||||
|
||||
val result = dao.getAllSearchHistory().first()
|
||||
assertEquals(1, result.size)
|
||||
assertEquals("2", result[0].id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertSearchHistoryWithConflict() = runTest {
|
||||
val search = createTestSearch("1", "kotlin")
|
||||
|
||||
dao.insertSearchHistory(search)
|
||||
|
||||
val duplicate = search.copy(query = "android")
|
||||
val result = dao.insertSearchHistory(duplicate)
|
||||
|
||||
assertEquals(-1L, result)
|
||||
|
||||
val dbSearch = dao.getSearchHistoryById("1")
|
||||
assertEquals("kotlin", dbSearch?.query)
|
||||
}
|
||||
|
||||
private fun createTestSearch(
|
||||
id: String,
|
||||
query: String,
|
||||
timestamp: Date = Date()
|
||||
): SearchHistoryEntity {
|
||||
return SearchHistoryEntity(
|
||||
id = id,
|
||||
query = query,
|
||||
timestamp = timestamp
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package com.rssuper.database
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.rssuper.database.daos.SubscriptionDao
|
||||
import com.rssuper.database.entities.SubscriptionEntity
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.util.Date
|
||||
|
||||
class SubscriptionDaoTest {
|
||||
|
||||
private lateinit var database: RssDatabase
|
||||
private lateinit var dao: SubscriptionDao
|
||||
|
||||
@Before
|
||||
fun createDb() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
database = Room.inMemoryDatabaseBuilder(
|
||||
context,
|
||||
RssDatabase::class.java
|
||||
)
|
||||
.allowMainThreadQueries()
|
||||
.build()
|
||||
dao = database.subscriptionDao()
|
||||
}
|
||||
|
||||
@After
|
||||
fun closeDb() {
|
||||
database.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertAndGetSubscription() = runTest {
|
||||
val subscription = createTestSubscription("1")
|
||||
|
||||
dao.insertSubscription(subscription)
|
||||
|
||||
val result = dao.getSubscriptionById("1")
|
||||
assertNotNull(result)
|
||||
assertEquals("1", result?.id)
|
||||
assertEquals("Test Feed", result?.title)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getSubscriptionByUrl() = runTest {
|
||||
val subscription = createTestSubscription("1", url = "https://example.com/feed")
|
||||
|
||||
dao.insertSubscription(subscription)
|
||||
|
||||
val result = dao.getSubscriptionByUrl("https://example.com/feed")
|
||||
assertNotNull(result)
|
||||
assertEquals("1", result?.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getAllSubscriptions() = runTest {
|
||||
val subscription1 = createTestSubscription("1")
|
||||
val subscription2 = createTestSubscription("2")
|
||||
|
||||
dao.insertSubscriptions(listOf(subscription1, subscription2))
|
||||
|
||||
val result = dao.getAllSubscriptions().first()
|
||||
assertEquals(2, result.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getEnabledSubscriptions() = runTest {
|
||||
val enabled = createTestSubscription("1", enabled = true)
|
||||
val disabled = createTestSubscription("2", enabled = false)
|
||||
|
||||
dao.insertSubscriptions(listOf(enabled, disabled))
|
||||
|
||||
val result = dao.getEnabledSubscriptions().first()
|
||||
assertEquals(1, result.size)
|
||||
assertEquals("1", result[0].id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateSubscription() = runTest {
|
||||
val subscription = createTestSubscription("1")
|
||||
|
||||
dao.insertSubscription(subscription)
|
||||
|
||||
val updated = subscription.copy(title = "Updated Title")
|
||||
dao.updateSubscription(updated)
|
||||
|
||||
val result = dao.getSubscriptionById("1")
|
||||
assertEquals("Updated Title", result?.title)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteSubscription() = runTest {
|
||||
val subscription = createTestSubscription("1")
|
||||
|
||||
dao.insertSubscription(subscription)
|
||||
dao.deleteSubscription(subscription)
|
||||
|
||||
val result = dao.getSubscriptionById("1")
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteSubscriptionById() = runTest {
|
||||
val subscription = createTestSubscription("1")
|
||||
|
||||
dao.insertSubscription(subscription)
|
||||
dao.deleteSubscriptionById("1")
|
||||
|
||||
val result = dao.getSubscriptionById("1")
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getSubscriptionCount() = runTest {
|
||||
val subscription1 = createTestSubscription("1")
|
||||
val subscription2 = createTestSubscription("2")
|
||||
|
||||
dao.insertSubscriptions(listOf(subscription1, subscription2))
|
||||
|
||||
val count = dao.getSubscriptionCount().first()
|
||||
assertEquals(2, count)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateError() = runTest {
|
||||
val subscription = createTestSubscription("1")
|
||||
|
||||
dao.insertSubscription(subscription)
|
||||
dao.updateError("1", "Feed not found")
|
||||
|
||||
val result = dao.getSubscriptionById("1")
|
||||
assertEquals("Feed not found", result?.error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateLastFetchedAt() = runTest {
|
||||
val subscription = createTestSubscription("1")
|
||||
val now = Date()
|
||||
|
||||
dao.insertSubscription(subscription)
|
||||
dao.updateLastFetchedAt("1", now)
|
||||
|
||||
val result = dao.getSubscriptionById("1")
|
||||
assertEquals(now, result?.lastFetchedAt)
|
||||
assertNull(result?.error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateNextFetchAt() = runTest {
|
||||
val subscription = createTestSubscription("1")
|
||||
val future = Date(System.currentTimeMillis() + 3600000)
|
||||
|
||||
dao.insertSubscription(subscription)
|
||||
dao.updateNextFetchAt("1", future)
|
||||
|
||||
val result = dao.getSubscriptionById("1")
|
||||
assertEquals(future, result?.nextFetchAt)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertSubscriptionWithConflict() = runTest {
|
||||
val subscription = createTestSubscription("1")
|
||||
|
||||
dao.insertSubscription(subscription)
|
||||
|
||||
val updated = subscription.copy(title = "Updated")
|
||||
dao.insertSubscription(updated)
|
||||
|
||||
val result = dao.getSubscriptionById("1")
|
||||
assertEquals("Updated", result?.title)
|
||||
}
|
||||
|
||||
private fun createTestSubscription(
|
||||
id: String,
|
||||
url: String = "https://example.com/feed/$id",
|
||||
title: String = "Test Feed",
|
||||
enabled: Boolean = true
|
||||
): SubscriptionEntity {
|
||||
val now = Date()
|
||||
return SubscriptionEntity(
|
||||
id = id,
|
||||
url = url,
|
||||
title = title,
|
||||
category = "Tech",
|
||||
enabled = enabled,
|
||||
fetchInterval = 3600000,
|
||||
createdAt = now,
|
||||
updatedAt = now,
|
||||
lastFetchedAt = null,
|
||||
nextFetchAt = null,
|
||||
error = null,
|
||||
httpAuthUsername = null,
|
||||
httpAuthPassword = null
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user