feat: implement Android data models in Kotlin

- Add FeedItem, Feed, FeedSubscription models with Moshi JSON support
- Add SearchResult, SearchFilters models with sealed classes for enums
- Add NotificationPreferences, ReadingPreferences models
- Add Room Entity annotations for database readiness
- Add TypeConverters for Date and List<String> serialization
- Add Parcelize for passing models between Activities/Fragments
- Write comprehensive unit tests for serialization/deserialization
- Write tests for copy(), equals/hashCode, and toString functionality
This commit is contained in:
2026-03-29 15:40:38 -04:00
parent fade9fd5b1
commit fdd4fd8a46
18 changed files with 1540 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("kotlin-parcelize")
id("kotlin-kapt")
}
android {
namespace = "com.rssuper"
compileSdk = 34
defaultConfig {
minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}
dependencies {
// AndroidX
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
kapt("androidx.room:room-compiler:2.6.1")
// Moshi for JSON serialization
implementation("com.squareup.moshi:moshi-kotlin:1.15.1")
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.15.1")
implementation("com.squareup.moshi:moshi-kotlin-reflect:1.15.1")
// Testing
testImplementation("junit:junit:4.13.2")
testImplementation("com.squareup.moshi:moshi-kotlin:1.15.1")
testImplementation("com.squareup.moshi:moshi-kotlin-reflect:1.15.1")
testImplementation("org.mockito:mockito-core:5.7.0")
testImplementation("org.mockito:mockito-inline:5.2.0")
}

View File

@@ -0,0 +1,16 @@
package com.rssuper.converters
import androidx.room.TypeConverter
import java.util.Date
class DateConverter {
@TypeConverter
fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) }
}
@TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time
}
}

View File

@@ -0,0 +1,23 @@
package com.rssuper.converters
import androidx.room.TypeConverter
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.rssuper.models.FeedItem
class FeedItemListConverter {
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val adapter = moshi.adapter(List::class.java)
@TypeConverter
fun fromFeedItemList(value: List<FeedItem>?): String? {
return value?.let { adapter.toJson(it) }
}
@TypeConverter
fun toFeedItemList(value: String?): List<FeedItem>? {
return value?.let { adapter.fromJson(it) }
}
}

View File

@@ -0,0 +1,15 @@
package com.rssuper.converters
import androidx.room.TypeConverter
class StringListConverter {
@TypeConverter
fun fromStringList(value: List<String>?): String? {
return value?.joinToString(",")
}
@TypeConverter
fun toStringList(value: String?): List<String>? {
return value?.split(",")?.filter { it.isNotEmpty() }
}
}

View File

@@ -0,0 +1,60 @@
package com.rssuper.models
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import com.rssuper.converters.DateConverter
import com.rssuper.converters.FeedItemListConverter
import kotlinx.parcelize.Parcelize
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import java.util.Date
@JsonClass(generateAdapter = true)
@Parcelize
@TypeConverters(DateConverter::class, FeedItemListConverter::class)
@Entity(tableName = "feeds")
data class Feed(
@PrimaryKey
val id: String,
@Json(name = "title")
val title: String,
@Json(name = "link")
val link: String? = null,
@Json(name = "description")
val description: String? = null,
@Json(name = "subtitle")
val subtitle: String? = null,
@Json(name = "language")
val language: String? = null,
@Json(name = "lastBuildDate")
val lastBuildDate: Date? = null,
@Json(name = "updated")
val updated: Date? = null,
@Json(name = "generator")
val generator: String? = null,
@Json(name = "ttl")
val ttl: Int? = null,
@Json(name = "items")
val items: List<FeedItem> = emptyList(),
@Json(name = "rawUrl")
val rawUrl: String,
@Json(name = "lastFetchedAt")
val lastFetchedAt: Date? = null,
@Json(name = "nextFetchAt")
val nextFetchAt: Date? = null
) : Parcelable

View File

@@ -0,0 +1,67 @@
package com.rssuper.models
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import com.rssuper.converters.DateConverter
import com.rssuper.converters.StringListConverter
import kotlinx.parcelize.Parcelize
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import java.util.Date
@JsonClass(generateAdapter = true)
@Parcelize
@TypeConverters(DateConverter::class, StringListConverter::class)
@Entity(tableName = "feed_items")
data class FeedItem(
@PrimaryKey
val id: String,
@Json(name = "title")
val title: String,
@Json(name = "link")
val link: String? = null,
@Json(name = "description")
val description: String? = null,
@Json(name = "content")
val content: String? = null,
@Json(name = "author")
val author: String? = null,
@Json(name = "published")
val published: Date? = null,
@Json(name = "updated")
val updated: Date? = null,
@Json(name = "categories")
val categories: List<String>? = null,
@Json(name = "enclosure")
val enclosure: Enclosure? = null,
@Json(name = "guid")
val guid: String? = null,
@Json(name = "subscriptionTitle")
val subscriptionTitle: String? = null
) : Parcelable
@JsonClass(generateAdapter = true)
@Parcelize
data class Enclosure(
@Json(name = "url")
val url: String,
@Json(name = "type")
val type: String,
@Json(name = "length")
val length: Long? = null
) : Parcelable

View File

@@ -0,0 +1,63 @@
package com.rssuper.models
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import com.rssuper.converters.DateConverter
import kotlinx.parcelize.Parcelize
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import java.util.Date
@JsonClass(generateAdapter = true)
@Parcelize
@TypeConverters(DateConverter::class)
@Entity(tableName = "feed_subscriptions")
data class FeedSubscription(
@PrimaryKey
val id: String,
@Json(name = "url")
val url: String,
@Json(name = "title")
val title: String,
@Json(name = "category")
val category: String? = null,
@Json(name = "enabled")
val enabled: Boolean = true,
@Json(name = "fetchInterval")
val fetchInterval: Long,
@Json(name = "createdAt")
val createdAt: Date,
@Json(name = "updatedAt")
val updatedAt: Date,
@Json(name = "lastFetchedAt")
val lastFetchedAt: Date? = null,
@Json(name = "nextFetchAt")
val nextFetchAt: Date? = null,
@Json(name = "error")
val error: String? = null,
@Json(name = "httpAuth")
val httpAuth: HttpAuth? = null
) : Parcelable
@JsonClass(generateAdapter = true)
@Parcelize
data class HttpAuth(
@Json(name = "username")
val username: String,
@Json(name = "password")
val password: String
) : Parcelable

View File

@@ -0,0 +1,34 @@
package com.rssuper.models
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
@Parcelize
@Entity(tableName = "notification_preferences")
data class NotificationPreferences(
@PrimaryKey
val id: String = "default",
@Json(name = "newArticles")
val newArticles: Boolean = true,
@Json(name = "episodeReleases")
val episodeReleases: Boolean = true,
@Json(name = "customAlerts")
val customAlerts: Boolean = false,
@Json(name = "badgeCount")
val badgeCount: Boolean = true,
@Json(name = "sound")
val sound: Boolean = true,
@Json(name = "vibration")
val vibration: Boolean = true
) : Parcelable

View File

@@ -0,0 +1,59 @@
package com.rssuper.models
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
@Parcelize
@Entity(tableName = "reading_preferences")
data class ReadingPreferences(
@PrimaryKey
val id: String = "default",
@Json(name = "fontSize")
val fontSize: FontSize = FontSize.MEDIUM,
@Json(name = "lineHeight")
val lineHeight: LineHeight = LineHeight.NORMAL,
@Json(name = "showTableOfContents")
val showTableOfContents: Boolean = false,
@Json(name = "showReadingTime")
val showReadingTime: Boolean = true,
@Json(name = "showAuthor")
val showAuthor: Boolean = true,
@Json(name = "showDate")
val showDate: Boolean = true
) : Parcelable
sealed class FontSize(val value: String) {
@Json(name = "small")
data object SMALL : FontSize("small")
@Json(name = "medium")
data object MEDIUM : FontSize("medium")
@Json(name = "large")
data object LARGE : FontSize("large")
@Json(name = "xlarge")
data object XLARGE : FontSize("xlarge")
}
sealed class LineHeight(val value: String) {
@Json(name = "normal")
data object NORMAL : LineHeight("normal")
@Json(name = "relaxed")
data object RELAXED : LineHeight("relaxed")
@Json(name = "loose")
data object LOOSE : LineHeight("loose")
}

View File

@@ -0,0 +1,73 @@
package com.rssuper.models
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import com.rssuper.converters.DateConverter
import com.rssuper.converters.StringListConverter
import kotlinx.parcelize.Parcelize
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import java.util.Date
@JsonClass(generateAdapter = true)
@Parcelize
@TypeConverters(DateConverter::class, StringListConverter::class)
@Entity(tableName = "search_filters")
data class SearchFilters(
@PrimaryKey
val id: String = "default",
@Json(name = "dateFrom")
val dateFrom: Date? = null,
@Json(name = "dateTo")
val dateTo: Date? = null,
@Json(name = "feedIds")
val feedIds: List<String>? = null,
@Json(name = "authors")
val authors: List<String>? = null,
@Json(name = "contentType")
val contentType: ContentType? = null,
@Json(name = "sortOption")
val sortOption: SearchSortOption = SearchSortOption.RELEVANCE
) : Parcelable
sealed class ContentType(val value: String) {
@Json(name = "article")
data object ARTICLE : ContentType("article")
@Json(name = "audio")
data object AUDIO : ContentType("audio")
@Json(name = "video")
data object VIDEO : ContentType("video")
}
sealed class SearchSortOption(val value: String) {
@Json(name = "relevance")
data object RELEVANCE : SearchSortOption("relevance")
@Json(name = "date_desc")
data object DATE_DESC : SearchSortOption("date_desc")
@Json(name = "date_asc")
data object DATE_ASC : SearchSortOption("date_asc")
@Json(name = "title_asc")
data object TITLE_ASC : SearchSortOption("title_asc")
@Json(name = "title_desc")
data object TITLE_DESC : SearchSortOption("title_desc")
@Json(name = "feed_asc")
data object FEED_ASC : SearchSortOption("feed_asc")
@Json(name = "feed_desc")
data object FEED_DESC : SearchSortOption("feed_desc")
}

View File

@@ -0,0 +1,49 @@
package com.rssuper.models
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import com.rssuper.converters.DateConverter
import kotlinx.parcelize.Parcelize
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import java.util.Date
@JsonClass(generateAdapter = true)
@Parcelize
@TypeConverters(DateConverter::class)
@Entity(tableName = "search_results")
data class SearchResult(
@PrimaryKey
val id: String,
@Json(name = "type")
val type: SearchResultType,
@Json(name = "title")
val title: String,
@Json(name = "snippet")
val snippet: String? = null,
@Json(name = "link")
val link: String? = null,
@Json(name = "feedTitle")
val feedTitle: String? = null,
@Json(name = "published")
val published: Date? = null,
@Json(name = "score")
val score: Double? = null
) : Parcelable
enum class SearchResultType {
@Json(name = "article")
ARTICLE,
@Json(name = "feed")
FEED
}

View File

@@ -0,0 +1,134 @@
package com.rssuper.models
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
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 FeedItemTest {
private lateinit var moshi: Moshi
private lateinit var adapter: com.squareup.moshi.JsonAdapter<FeedItem>
@Before
fun setup() {
moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
adapter = moshi.adapter(FeedItem::class.java)
}
@Test
fun testSerialization() {
val feedItem = FeedItem(
id = "item-1",
title = "Test Article",
link = "https://example.com/article",
description = "Short description",
content = "Full content here",
author = "John Doe",
published = Date(1672531200000),
categories = listOf("Tech", "News"),
guid = "guid-123",
subscriptionTitle = "Tech News"
)
val json = adapter.toJson(feedItem)
assertNotNull(json)
}
@Test
fun testDeserialization() {
val json = """{
"id": "item-1",
"title": "Test Article",
"link": "https://example.com/article",
"description": "Short description",
"author": "John Doe",
"published": 1672531200000,
"categories": ["Tech", "News"],
"guid": "guid-123",
"subscriptionTitle": "Tech News"
}"""
val feedItem = adapter.fromJson(json)
assertNotNull(feedItem)
assertEquals("item-1", feedItem?.id)
assertEquals("Test Article", feedItem?.title)
assertEquals("John Doe", feedItem?.author)
}
@Test
fun testOptionalFieldsNull() {
val json = """{
"id": "item-1",
"title": "Test Article"
}"""
val feedItem = adapter.fromJson(json)
assertNotNull(feedItem)
assertNull(feedItem?.link)
assertNull(feedItem?.description)
assertNull(feedItem?.author)
}
@Test
fun testEnclosureSerialization() {
val feedItem = FeedItem(
id = "item-1",
title = "Podcast Episode",
enclosure = Enclosure(
url = "https://example.com/episode.mp3",
type = "audio/mpeg",
length = 12345678
)
)
val json = adapter.toJson(feedItem)
assertNotNull(json)
}
@Test
fun testCopy() {
val original = FeedItem(
id = "item-1",
title = "Original Title",
author = "Original Author"
)
val modified = original.copy(title = "Modified Title")
assertEquals("item-1", modified.id)
assertEquals("Modified Title", modified.title)
assertEquals("Original Author", modified.author)
}
@Test
fun testEqualsAndHashCode() {
val item1 = FeedItem(id = "item-1", title = "Test")
val item2 = FeedItem(id = "item-1", title = "Test")
val item3 = FeedItem(id = "item-2", title = "Test")
assertEquals(item1, item2)
assertEquals(item1.hashCode(), item2.hashCode())
assert(item1 != item3)
}
@Test
fun testToString() {
val feedItem = FeedItem(
id = "item-1",
title = "Test Article",
author = "John Doe"
)
val toString = feedItem.toString()
assertNotNull(toString)
assert(toString.contains("id=item-1"))
assert(toString.contains("title=Test Article"))
}
}

View File

@@ -0,0 +1,199 @@
package com.rssuper.models
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
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 FeedSubscriptionTest {
private lateinit var moshi: Moshi
private lateinit var adapter: com.squareup.moshi.JsonAdapter<FeedSubscription>
@Before
fun setup() {
moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
adapter = moshi.adapter(FeedSubscription::class.java)
}
@Test
fun testSerialization() {
val subscription = FeedSubscription(
id = "sub-1",
url = "https://example.com/feed.xml",
title = "Tech News",
category = "Technology",
enabled = true,
fetchInterval = 60,
createdAt = Date(1672531200000),
updatedAt = Date(1672617600000)
)
val json = adapter.toJson(subscription)
assertNotNull(json)
}
@Test
fun testDeserialization() {
val json = """{
"id": "sub-1",
"url": "https://example.com/feed.xml",
"title": "Tech News",
"category": "Technology",
"enabled": true,
"fetchInterval": 60,
"createdAt": 1672531200000,
"updatedAt": 1672617600000
}"""
val subscription = adapter.fromJson(json)
assertNotNull(subscription)
assertEquals("sub-1", subscription?.id)
assertEquals("https://example.com/feed.xml", subscription?.url)
assertEquals("Tech News", subscription?.title)
assertEquals("Technology", subscription?.category)
assertEquals(true, subscription?.enabled)
assertEquals(60, subscription?.fetchInterval)
}
@Test
fun testOptionalFieldsNull() {
val json = """{
"id": "sub-1",
"url": "https://example.com/feed.xml",
"title": "Tech News",
"enabled": true,
"fetchInterval": 60,
"createdAt": 1672531200000,
"updatedAt": 1672617600000
}"""
val subscription = adapter.fromJson(json)
assertNotNull(subscription)
assertNull(subscription?.category)
assertNull(subscription?.error)
assertNull(subscription?.httpAuth)
}
@Test
fun testHttpAuthSerialization() {
val subscription = FeedSubscription(
id = "sub-1",
url = "https://example.com/feed.xml",
title = "Private Feed",
enabled = true,
fetchInterval = 60,
createdAt = Date(1672531200000),
updatedAt = Date(1672617600000),
httpAuth = HttpAuth(
username = "user123",
password = "pass456"
)
)
val json = adapter.toJson(subscription)
assertNotNull(json)
}
@Test
fun testHttpAuthDeserialization() {
val json = """{
"id": "sub-1",
"url": "https://example.com/feed.xml",
"title": "Private Feed",
"enabled": true,
"fetchInterval": 60,
"createdAt": 1672531200000,
"updatedAt": 1672617600000,
"httpAuth": {
"username": "user123",
"password": "pass456"
}
}"""
val subscription = adapter.fromJson(json)
assertNotNull(subscription)
assertNotNull(subscription?.httpAuth)
assertEquals("user123", subscription?.httpAuth?.username)
assertEquals("pass456", subscription?.httpAuth?.password)
}
@Test
fun testCopy() {
val original = FeedSubscription(
id = "sub-1",
url = "https://example.com/feed.xml",
title = "Original Title",
enabled = true,
fetchInterval = 60,
createdAt = Date(1672531200000),
updatedAt = Date(1672617600000)
)
val modified = original.copy(title = "Modified Title", enabled = false)
assertEquals("sub-1", modified.id)
assertEquals("Modified Title", modified.title)
assertEquals(false, modified.enabled)
assertEquals(60, modified.fetchInterval)
}
@Test
fun testEqualsAndHashCode() {
val sub1 = FeedSubscription(
id = "sub-1",
url = "https://example.com",
title = "Test",
enabled = true,
fetchInterval = 60,
createdAt = Date(1672531200000),
updatedAt = Date(1672617600000)
)
val sub2 = FeedSubscription(
id = "sub-1",
url = "https://example.com",
title = "Test",
enabled = true,
fetchInterval = 60,
createdAt = Date(1672531200000),
updatedAt = Date(1672617600000)
)
val sub3 = FeedSubscription(
id = "sub-2",
url = "https://example.com",
title = "Test",
enabled = true,
fetchInterval = 60,
createdAt = Date(1672531200000),
updatedAt = Date(1672617600000)
)
assertEquals(sub1, sub2)
assertEquals(sub1.hashCode(), sub2.hashCode())
assert(sub1 != sub3)
}
@Test
fun testToString() {
val subscription = FeedSubscription(
id = "sub-1",
url = "https://example.com/feed.xml",
title = "Tech News",
enabled = true,
fetchInterval = 60,
createdAt = Date(1672531200000),
updatedAt = Date(1672617600000)
)
val toString = subscription.toString()
assertNotNull(toString)
assert(toString.contains("id=sub-1"))
assert(toString.contains("title=Tech News"))
}
}

View File

@@ -0,0 +1,139 @@
package com.rssuper.models
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import java.util.Date
class FeedTest {
private lateinit var moshi: Moshi
private lateinit var adapter: com.squareup.moshi.JsonAdapter<Feed>
@Before
fun setup() {
moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
adapter = moshi.adapter(Feed::class.java)
}
@Test
fun testSerialization() {
val feed = Feed(
id = "feed-1",
title = "Tech News",
link = "https://example.com",
description = "Technology news feed",
subtitle = "Daily tech updates",
language = "en",
rawUrl = "https://example.com/feed.xml",
ttl = 60,
items = listOf(
FeedItem(id = "item-1", title = "Article 1"),
FeedItem(id = "item-2", title = "Article 2")
)
)
val json = adapter.toJson(feed)
assertNotNull(json)
}
@Test
fun testDeserialization() {
val json = """{
"id": "feed-1",
"title": "Tech News",
"link": "https://example.com",
"description": "Technology news feed",
"subtitle": "Daily tech updates",
"language": "en",
"rawUrl": "https://example.com/feed.xml",
"ttl": 60,
"items": [
{"id": "item-1", "title": "Article 1"},
{"id": "item-2", "title": "Article 2"}
]
}"""
val feed = adapter.fromJson(json)
assertNotNull(feed)
assertEquals("feed-1", feed?.id)
assertEquals("Tech News", feed?.title)
assertEquals(2, feed?.items?.size)
}
@Test
fun testOptionalFieldsNull() {
val json = """{
"id": "feed-1",
"title": "Tech News",
"rawUrl": "https://example.com/feed.xml"
}"""
val feed = adapter.fromJson(json)
assertNotNull(feed)
assertNull(feed?.link)
assertNull(feed?.description)
assertNull(feed?.language)
}
@Test
fun testEmptyItemsList() {
val json = """{
"id": "feed-1",
"title": "Tech News",
"rawUrl": "https://example.com/feed.xml",
"items": []
}"""
val feed = adapter.fromJson(json)
assertNotNull(feed)
assertTrue(feed?.items?.isEmpty() == true)
}
@Test
fun testCopy() {
val original = Feed(
id = "feed-1",
title = "Original Title",
rawUrl = "https://example.com/feed.xml"
)
val modified = original.copy(title = "Modified Title")
assertEquals("feed-1", modified.id)
assertEquals("Modified Title", modified.title)
assertEquals("https://example.com/feed.xml", modified.rawUrl)
}
@Test
fun testEqualsAndHashCode() {
val feed1 = Feed(id = "feed-1", title = "Test", rawUrl = "https://example.com")
val feed2 = Feed(id = "feed-1", title = "Test", rawUrl = "https://example.com")
val feed3 = Feed(id = "feed-2", title = "Test", rawUrl = "https://example.com")
assertEquals(feed1, feed2)
assertEquals(feed1.hashCode(), feed2.hashCode())
assert(feed1 != feed3)
}
@Test
fun testToString() {
val feed = Feed(
id = "feed-1",
title = "Tech News",
rawUrl = "https://example.com/feed.xml"
)
val toString = feed.toString()
assertNotNull(toString)
assert(toString.contains("id=feed-1"))
assert(toString.contains("title=Tech News"))
}
}

View File

@@ -0,0 +1,108 @@
package com.rssuper.models
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Test
class NotificationPreferencesTest {
private lateinit var moshi: Moshi
private lateinit var adapter: com.squareup.moshi.JsonAdapter<NotificationPreferences>
@Before
fun setup() {
moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
adapter = moshi.adapter(NotificationPreferences::class.java)
}
@Test
fun testSerialization() {
val preferences = NotificationPreferences(
newArticles = true,
episodeReleases = true,
customAlerts = false,
badgeCount = true,
sound = true,
vibration = false
)
val json = adapter.toJson(preferences)
assertNotNull(json)
}
@Test
fun testDeserialization() {
val json = """{
"newArticles": true,
"episodeReleases": true,
"customAlerts": false,
"badgeCount": true,
"sound": true,
"vibration": false
}"""
val preferences = adapter.fromJson(json)
assertNotNull(preferences)
assertEquals(true, preferences?.newArticles)
assertEquals(true, preferences?.episodeReleases)
assertEquals(false, preferences?.customAlerts)
assertEquals(true, preferences?.badgeCount)
assertEquals(true, preferences?.sound)
assertEquals(false, preferences?.vibration)
}
@Test
fun testDefaultValues() {
val preferences = NotificationPreferences()
assertEquals(true, preferences.newArticles)
assertEquals(true, preferences.episodeReleases)
assertEquals(false, preferences.customAlerts)
assertEquals(true, preferences.badgeCount)
assertEquals(true, preferences.sound)
assertEquals(true, preferences.vibration)
}
@Test
fun testCopy() {
val original = NotificationPreferences(
newArticles = true,
sound = true
)
val modified = original.copy(newArticles = false, sound = false)
assertEquals(false, modified.newArticles)
assertEquals(false, modified.sound)
assertEquals(true, modified.episodeReleases)
}
@Test
fun testEqualsAndHashCode() {
val pref1 = NotificationPreferences(newArticles = true, sound = true)
val pref2 = NotificationPreferences(newArticles = true, sound = true)
val pref3 = NotificationPreferences(newArticles = false, sound = true)
assertEquals(pref1, pref2)
assertEquals(pref1.hashCode(), pref2.hashCode())
assert(pref1 != pref3)
}
@Test
fun testToString() {
val preferences = NotificationPreferences(
newArticles = true,
sound = true
)
val toString = preferences.toString()
assertNotNull(toString)
assert(toString.contains("newArticles"))
assert(toString.contains("sound"))
}
}

View File

@@ -0,0 +1,141 @@
package com.rssuper.models
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Test
class ReadingPreferencesTest {
private lateinit var moshi: Moshi
private lateinit var adapter: com.squareup.moshi.JsonAdapter<ReadingPreferences>
@Before
fun setup() {
moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
adapter = moshi.adapter(ReadingPreferences::class.java)
}
@Test
fun testSerialization() {
val preferences = ReadingPreferences(
fontSize = FontSize.LARGE,
lineHeight = LineHeight.RELAXED,
showTableOfContents = true,
showReadingTime = true,
showAuthor = false,
showDate = true
)
val json = adapter.toJson(preferences)
assertNotNull(json)
}
@Test
fun testDeserialization() {
val json = """{
"fontSize": "large",
"lineHeight": "relaxed",
"showTableOfContents": true,
"showReadingTime": true,
"showAuthor": false,
"showDate": true
}"""
val preferences = adapter.fromJson(json)
assertNotNull(preferences)
assertEquals(FontSize.LARGE, preferences?.fontSize)
assertEquals(LineHeight.RELAXED, preferences?.lineHeight)
assertEquals(true, preferences?.showTableOfContents)
assertEquals(true, preferences?.showReadingTime)
assertEquals(false, preferences?.showAuthor)
assertEquals(true, preferences?.showDate)
}
@Test
fun testFontSizeOptions() {
val fontSizes = listOf(
"small" to FontSize.SMALL,
"medium" to FontSize.MEDIUM,
"large" to FontSize.LARGE,
"xlarge" to FontSize.XLARGE
)
for ((jsonValue, expectedEnum) in fontSizes) {
val json = """{"fontSize": "$jsonValue"}"""
val preferences = adapter.fromJson(json)
assertNotNull("Failed for fontSize: $jsonValue", preferences)
assertEquals("Failed for fontSize: $jsonValue", expectedEnum, preferences?.fontSize)
}
}
@Test
fun testLineHeightOptions() {
val lineHeights = listOf(
"normal" to LineHeight.NORMAL,
"relaxed" to LineHeight.RELAXED,
"loose" to LineHeight.LOOSE
)
for ((jsonValue, expectedEnum) in lineHeights) {
val json = """{"lineHeight": "$jsonValue"}"""
val preferences = adapter.fromJson(json)
assertNotNull("Failed for lineHeight: $jsonValue", preferences)
assertEquals("Failed for lineHeight: $jsonValue", expectedEnum, preferences?.lineHeight)
}
}
@Test
fun testDefaultValues() {
val preferences = ReadingPreferences()
assertEquals(FontSize.MEDIUM, preferences.fontSize)
assertEquals(LineHeight.NORMAL, preferences.lineHeight)
assertEquals(false, preferences.showTableOfContents)
assertEquals(true, preferences.showReadingTime)
assertEquals(true, preferences.showAuthor)
assertEquals(true, preferences.showDate)
}
@Test
fun testCopy() {
val original = ReadingPreferences(
fontSize = FontSize.MEDIUM,
showReadingTime = true
)
val modified = original.copy(fontSize = FontSize.XLARGE, showReadingTime = false)
assertEquals(FontSize.XLARGE, modified.fontSize)
assertEquals(false, modified.showReadingTime)
assertEquals(LineHeight.NORMAL, modified.lineHeight)
}
@Test
fun testEqualsAndHashCode() {
val pref1 = ReadingPreferences(fontSize = FontSize.MEDIUM, showReadingTime = true)
val pref2 = ReadingPreferences(fontSize = FontSize.MEDIUM, showReadingTime = true)
val pref3 = ReadingPreferences(fontSize = FontSize.LARGE, showReadingTime = true)
assertEquals(pref1, pref2)
assertEquals(pref1.hashCode(), pref2.hashCode())
assert(pref1 != pref3)
}
@Test
fun testToString() {
val preferences = ReadingPreferences(
fontSize = FontSize.LARGE,
showReadingTime = true
)
val toString = preferences.toString()
assertNotNull(toString)
assert(toString.contains("fontSize"))
assert(toString.contains("showReadingTime"))
}
}

View File

@@ -0,0 +1,156 @@
package com.rssuper.models
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
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 SearchFiltersTest {
private lateinit var moshi: Moshi
private lateinit var adapter: com.squareup.moshi.JsonAdapter<SearchFilters>
@Before
fun setup() {
moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
adapter = moshi.adapter(SearchFilters::class.java)
}
@Test
fun testSerialization() {
val filters = SearchFilters(
dateFrom = Date(1672531200000),
dateTo = Date(1672617600000),
feedIds = listOf("feed-1", "feed-2"),
authors = listOf("John Doe", "Jane Smith"),
contentType = ContentType.ARTICLE,
sortOption = SearchSortOption.DATE_DESC
)
val json = adapter.toJson(filters)
assertNotNull(json)
}
@Test
fun testDeserialization() {
val json = """{
"dateFrom": 1672531200000,
"dateTo": 1672617600000,
"feedIds": ["feed-1", "feed-2"],
"authors": ["John Doe", "Jane Smith"],
"contentType": "article",
"sortOption": "date_desc"
}"""
val filters = adapter.fromJson(json)
assertNotNull(filters)
assertNotNull(filters?.dateFrom)
assertNotNull(filters?.dateTo)
assertEquals(2, filters?.feedIds?.size)
assertEquals(2, filters?.authors?.size)
assertEquals(ContentType.ARTICLE, filters?.contentType)
assertEquals(SearchSortOption.DATE_DESC, filters?.sortOption)
}
@Test
fun testContentTypeAudio() {
val json = """{
"contentType": "audio"
}"""
val filters = adapter.fromJson(json)
assertNotNull(filters)
assertEquals(ContentType.AUDIO, filters?.contentType)
}
@Test
fun testContentTypeVideo() {
val json = """{
"contentType": "video"
}"""
val filters = adapter.fromJson(json)
assertNotNull(filters)
assertEquals(ContentType.VIDEO, filters?.contentType)
}
@Test
fun testSortOptions() {
val sortOptions = listOf(
"relevance" to SearchSortOption.RELEVANCE,
"date_desc" to SearchSortOption.DATE_DESC,
"date_asc" to SearchSortOption.DATE_ASC,
"title_asc" to SearchSortOption.TITLE_ASC,
"title_desc" to SearchSortOption.TITLE_DESC,
"feed_asc" to SearchSortOption.FEED_ASC,
"feed_desc" to SearchSortOption.FEED_DESC
)
for ((jsonValue, expectedEnum) in sortOptions) {
val json = """{"sortOption": "$jsonValue"}"""
val filters = adapter.fromJson(json)
assertNotNull("Failed for sortOption: $jsonValue", filters)
assertEquals("Failed for sortOption: $jsonValue", expectedEnum, filters?.sortOption)
}
}
@Test
fun testOptionalFieldsNull() {
val json = "{}"
val filters = adapter.fromJson(json)
assertNotNull(filters)
assertNull(filters?.dateFrom)
assertNull(filters?.dateTo)
assertNull(filters?.feedIds)
assertNull(filters?.authors)
assertNull(filters?.contentType)
assertEquals(SearchSortOption.RELEVANCE, filters?.sortOption)
}
@Test
fun testCopy() {
val original = SearchFilters(
feedIds = listOf("feed-1"),
sortOption = SearchSortOption.RELEVANCE
)
val modified = original.copy(
feedIds = listOf("feed-1", "feed-2"),
sortOption = SearchSortOption.DATE_DESC
)
assertEquals(2, modified.feedIds?.size)
assertEquals(SearchSortOption.DATE_DESC, modified.sortOption)
}
@Test
fun testEqualsAndHashCode() {
val filters1 = SearchFilters(feedIds = listOf("feed-1"), sortOption = SearchSortOption.RELEVANCE)
val filters2 = SearchFilters(feedIds = listOf("feed-1"), sortOption = SearchSortOption.RELEVANCE)
val filters3 = SearchFilters(feedIds = listOf("feed-2"), sortOption = SearchSortOption.RELEVANCE)
assertEquals(filters1, filters2)
assertEquals(filters1.hashCode(), filters2.hashCode())
assert(filters1 != filters3)
}
@Test
fun testToString() {
val filters = SearchFilters(
feedIds = listOf("feed-1"),
sortOption = SearchSortOption.DATE_DESC
)
val toString = filters.toString()
assertNotNull(toString)
assert(toString.contains("feedIds"))
assert(toString.contains("sortOption"))
}
}

View File

@@ -0,0 +1,153 @@
package com.rssuper.models
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
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 SearchResultTest {
private lateinit var moshi: Moshi
private lateinit var adapter: com.squareup.moshi.JsonAdapter<SearchResult>
@Before
fun setup() {
moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
adapter = moshi.adapter(SearchResult::class.java)
}
@Test
fun testArticleSerialization() {
val result = SearchResult(
id = "article-1",
type = SearchResultType.ARTICLE,
title = "Test Article",
snippet = "This is a snippet",
link = "https://example.com/article",
feedTitle = "Tech News",
published = Date(1672531200000),
score = 0.95
)
val json = adapter.toJson(result)
assertNotNull(json)
}
@Test
fun testFeedSerialization() {
val result = SearchResult(
id = "feed-1",
type = SearchResultType.FEED,
title = "Tech News Feed",
snippet = "Technology news and updates",
link = "https://example.com",
score = 0.85
)
val json = adapter.toJson(result)
assertNotNull(json)
}
@Test
fun testArticleDeserialization() {
val json = """{
"id": "article-1",
"type": "article",
"title": "Test Article",
"snippet": "This is a snippet",
"link": "https://example.com/article",
"feedTitle": "Tech News",
"published": 1672531200000,
"score": 0.95
}"""
val result = adapter.fromJson(json)
assertNotNull(result)
assertEquals("article-1", result?.id)
assertEquals(SearchResultType.ARTICLE, result?.type)
assertEquals("Test Article", result?.title)
assertEquals("This is a snippet", result?.snippet)
}
@Test
fun testFeedDeserialization() {
val json = """{
"id": "feed-1",
"type": "feed",
"title": "Tech News Feed",
"snippet": "Technology news and updates",
"link": "https://example.com",
"score": 0.85
}"""
val result = adapter.fromJson(json)
assertNotNull(result)
assertEquals("feed-1", result?.id)
assertEquals(SearchResultType.FEED, result?.type)
}
@Test
fun testOptionalFieldsNull() {
val json = """{
"id": "article-1",
"type": "article",
"title": "Test Article"
}"""
val result = adapter.fromJson(json)
assertNotNull(result)
assertNull(result?.snippet)
assertNull(result?.link)
assertNull(result?.feedTitle)
assertNull(result?.published)
assertNull(result?.score)
}
@Test
fun testCopy() {
val original = SearchResult(
id = "article-1",
type = SearchResultType.ARTICLE,
title = "Original Title"
)
val modified = original.copy(title = "Modified Title", score = 0.99)
assertEquals("article-1", modified.id)
assertEquals(SearchResultType.ARTICLE, modified.type)
assertEquals("Modified Title", modified.title)
assertEquals(0.99, modified.score, 0.001)
}
@Test
fun testEqualsAndHashCode() {
val result1 = SearchResult(id = "article-1", type = SearchResultType.ARTICLE, title = "Test")
val result2 = SearchResult(id = "article-1", type = SearchResultType.ARTICLE, title = "Test")
val result3 = SearchResult(id = "article-2", type = SearchResultType.ARTICLE, title = "Test")
assertEquals(result1, result2)
assertEquals(result1.hashCode(), result2.hashCode())
assert(result1 != result3)
}
@Test
fun testToString() {
val result = SearchResult(
id = "article-1",
type = SearchResultType.ARTICLE,
title = "Test Article",
score = 0.95
)
val toString = result.toString()
assertNotNull(toString)
assert(toString.contains("id=article-1"))
assert(toString.contains("title=Test Article"))
}
}