Compare commits

...
28 Commits
Author SHA1 Message Date
davidoskky c8759cc035 Fix tags tests
continuous-integration/drone/pr Build is running
2022-09-27 23:37:30 +02:00
davidoskky cb4f2f02ef Fix repository.tags() returning null 2022-09-27 23:26:44 +02:00
davidoskky 7517626ab7 Include database return definition within test function 2022-09-27 23:25:47 +02:00
davidoskky 41c951b659 Add test cases for repository instantiation cases
continuous-integration/drone/pr Build is passing
2022-09-27 23:16:30 +02:00
davidoskky e2afff0b8e Add comment
continuous-integration/drone/pr Build is passing
2022-09-26 23:19:31 +02:00
davidoskky a382fc89ea Test item caching
continuous-integration/drone/pr Build is running
2022-09-26 23:11:26 +02:00
davidoskky 3f0a3903ae Test refresh login information
continuous-integration/drone/pr Build is passing
2022-09-26 22:50:55 +02:00
davidoskky f46f98cef0 Test login
continuous-integration/drone/pr Build is running
2022-09-26 22:46:37 +02:00
davidoskky bf6f1a917e Test update remote
continuous-integration/drone/pr Build is running
2022-09-26 22:42:24 +02:00
davidoskky 71c0a4d340 Test delete source
continuous-integration/drone/pr Build is passing
2022-09-26 22:26:01 +02:00
davidoskky 63c550ead3 Test create source
continuous-integration/drone/pr Build is running
2022-09-26 22:21:48 +02:00
davidoskky 366b2e10f1 Adjust tests to changes in the data models
continuous-integration/drone/pr Build is passing
2022-09-25 22:02:25 +02:00
davidoskky d2436bb976 Merge branch 'master' into repository_tests
# Conflicts:
#	.drone.yml
2022-09-25 20:24:46 +02:00
Amine Louveau da71de6806 Merge pull request 'bugfix/ktor-404' (#62) from bugfix/ktor-404 into master
continuous-integration/drone/push Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/62
2022-09-24 14:55:44 +00:00
aminecmi 0264da8ccc Fixing #54.
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-09-24 15:00:33 +02:00
aminecmi 270d959ee0 Cleaning.
continuous-integration/drone/push Build is running
2022-09-24 14:55:06 +02:00
aminecmi 6d11dfb80c Fixing #59. 2022-09-24 14:43:24 +02:00
aminecmi 4184bbb900 Update gradle. 2022-09-24 13:54:48 +02:00
davidoskky ef994460c1 get sources tests
continuous-integration/drone/pr Build is failing
2022-09-18 18:14:14 +02:00
davidoskky 758708e18d Tags tests
continuous-integration/drone/pr Build is failing
2022-09-17 22:04:24 +02:00
davidoskky c0381144d1 Add CI test step
continuous-integration/drone/pr Build is failing
2022-09-17 21:29:37 +02:00
davidoskky cda3ba6cb4 Test badge fetching
continuous-integration/drone/pr Build is passing
2022-09-16 12:04:05 +02:00
davidoskky a4636cc0c8 Add item fetching tests
continuous-integration/drone/pr Build is passing
2022-09-15 14:07:50 +02:00
aminecmi 4c12c9d570 added badge.
continuous-integration/drone/push Build is passing
2022-09-14 14:54:13 +02:00
Amine Louveauandaminecmi f4db02521d drone-sign (#57)
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
## Types of changes

- [ ] I have read the **CONTRIBUTING** document.
- [ ] My code follows the code style of this project.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
- [ ] This is **NOT** translation related.

This closes issue #XXX

This is implements feature #YYY

This finishes chore #ZZZ

Co-authored-by: aminecmi <aminecmi@gmail.com>
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/57
2022-09-14 10:32:00 +00:00
davidoskky 60c24fc75a Check that the api is being used rather than the db
continuous-integration/drone/pr Build is passing
2022-09-10 09:37:14 +02:00
davidoskky 5853a19937 Normal items fetch test
continuous-integration/drone/pr Build is passing
2022-09-10 09:08:26 +02:00
davidoskky 99f2c04bf6 Initial testing setup
continuous-integration/drone/pr Build is passing
2022-09-09 13:43:53 +02:00
19 changed files with 1154 additions and 125 deletions
+101 -4
View File
@@ -1,21 +1,118 @@
kind: pipeline
type: docker
name: android
name: test
steps:
- name: code-analysis
- name: AnylyseBuildTest
image: mingc/android-build-box:latest
failure: ignore
commands:
- ls -la
- echo "---------------------------------------------------------"
- echo "Analysing..."
- ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\""
- echo "---------------------------------------------------------"
- echo "Building..."
- ./gradlew :androidApp:build -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
- echo "---------------------------------------------------------"
- echo "Testing..."
- echo "---------------------------------------------------------"
- ./gradlew test -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
environment:
SONAR_HOST_URL:
from_secret: sonarScannerHostUrl
SONAR_LOGIN:
from_secret: sonarScannerLogin
trigger:
event:
- push
- pull_request
---
kind: pipeline
type: docker
name: Publish
steps:
- name: createTag
image: ubuntu:latest
commands:
- apt-get update && apt-get install -y git
- ./build.sh --publish --from-ci
- git remote add pushing https://$GITEA_USR:$GITEA_PASS@gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform.git
- git push pushing --tags
environment:
GITEA_USR:
from_secret: giteaUsr
GITEA_PASS:
from_secret: giteaPass
- name: scpFiles
image: appleboy/drone-scp
settings:
host: amine-louveau.fr
username: ubuntu
key:
from_secret: privateKey
port: 22
target: /home/ubuntu/
source: version.txt
- name: deploy
image: appleboy/drone-ssh
settings:
host: amine-louveau.fr
user: ubuntu
key:
from_secret: privateKey
command_timeout: 2m
script:
- cd /home/ubuntu
- sudo rm -rf /var/www/amine/version.txt
- sudo chown www-data:www-data ./version.txt
- sudo mv version.txt /var/www/amine/
trigger:
event:
- promote
target:
- production
---
kind: pipeline
type: docker
name: Release
steps:
- name: build
image: mingc/android-build-box:latest
commands:
- ./gradlew :androidApp:build -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
- echo "Generate APK"
- ./gradlew :androidApp:assembleGithubConfigRelease -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
- echo "---------------------------------------------------------"
- echo "Get Key"
- wget https://amine-louveau.fr/key
- echo "---------------------------------------------------------"
- echo "Zipalign"
- $ANDROID_HOME/build-tools/31.0.0/zipalign -f -v 4 androidApp/build/outputs/apk/githubConfig/release/androidApp-githubConfig-release-unsigned.apk androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk
- echo "---------------------------------------------------------"
- echo "Sign"
- $ANDROID_HOME/build-tools/31.0.0/apksigner sign -v --out signed.apk --ks ./key --ks-key-alias $YOUR_KEY_ALIAS --ks-pass pass:$YOUR_KEYSTORE_PASSWORD --v1-signing-enabled true --v2-signing-enabled true androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk
- echo "---------------------------------------------------------"
- echo "Verify"
- $ANDROID_HOME/build-tools/31.0.0/apksigner verify signed.apk
environment:
YOUR_KEYSTORE_PASSWORD:
from_secret: keyPass
YOUR_KEY_ALIAS:
from_secret: keyAlias
- name: gitea_release
image: plugins/gitea-release
settings:
api_key:
from_secret: giteaAPI
base_url: https://gitea.amine-louveau.fr
files: signed.apk
trigger:
event:
- tag
+1 -1
View File
@@ -1,4 +1,4 @@
# ReaderForSelfoss-multiplatform
# ReaderForSelfoss-multiplatform [![Build Status](https://build.amine-louveau.fr/api/badges/Louvorg/ReaderForSelfoss-multiplatform/status.svg)](https://build.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/readerforselfoss/localized.svg)](https://crowdin.com/project/readerforselfoss)
+1
View File
@@ -101,6 +101,7 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
namespace = "bou.amine.apps.readerforselfossv2.android"
}
+1 -2
View File
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="bou.amine.apps.readerforselfossv2.android">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -9,7 +9,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
@@ -84,7 +84,7 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
super.onResume()
val baseUrl = appSettingsService.getBaseUrl()
if (baseUrl.isEmpty() || !baseUrl.isBaseUrlValid(this@AddSourceActivity)) {
if (baseUrl.isEmpty() || baseUrl.isBaseUrlInvalid(this@AddSourceActivity)) {
mustLoginToAddSource()
} else {
handleSpoutsSpinner(binding.spoutsSpinner, binding.progress, binding.formContainer)
@@ -14,7 +14,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.mikepenz.aboutlibraries.LibsBuilder
@@ -115,14 +115,14 @@ class LoginActivity : AppCompatActivity(), DIAware {
binding.passwordView.error = null
// Store values at the time of the login attempt.
val url = binding.urlView.text.toString()
val login = binding.loginView.text.toString()
val password = binding.passwordView.text.toString()
val url = binding.urlView.text.toString().trim()
val login = binding.loginView.text.toString().trim()
val password = binding.passwordView.text.toString().trim()
var cancel = false
var focusView: View? = null
if (!url.isBaseUrlValid(this@LoginActivity)) {
if (url.isBaseUrlInvalid(this@LoginActivity)) {
binding.urlView.error = getString(R.string.login_url_problem)
focusView = binding.urlView
cancel = true
@@ -268,8 +268,9 @@ class ArticleFragment : Fragment(), DIAware {
private fun getContentFromMercury(customTabsIntent: CustomTabsIntent) {
if (repository.isNetworkAvailable()) {
binding.progressBar.visibility = View.VISIBLE
val parser = MercuryApi()
binding.progressBar.visibility = View.VISIBLE
// TODO: The api should be accessed through the repository
val parser = MercuryApi()
parser.parseUrl(url).enqueue(
object : Callback<ParsedContent> {
@@ -163,7 +163,7 @@ private fun openInBrowser(linkDecoded: String, app: Activity) {
fun String.isUrlValid(): Boolean =
this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches()
fun String.isBaseUrlValid(ctx: Context): Boolean {
fun String.isBaseUrlInvalid(ctx: Context): Boolean {
val baseUrl = this.toHttpUrlOrNull()
var existsAndEndsWithSlash = false
if (baseUrl != null) {
@@ -171,7 +171,7 @@ fun String.isBaseUrlValid(ctx: Context): Boolean {
existsAndEndsWithSlash = "" == pathSegments[pathSegments.size - 1]
}
return Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash
return !(Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash)
}
fun Context.openInBrowserAsNewTask(i: SelfossModel.Item) {
+1 -1
View File
@@ -6,7 +6,7 @@ buildscript {
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
classpath("com.android.tools.build:gradle:7.2.2")
classpath("com.android.tools.build:gradle:7.3.0")
// sonarquve
classpath("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513")
+2 -2
View File
@@ -21,11 +21,11 @@ VERSION="${INITIAL_VERSION}${TODAYS_VERSION}"
PARAMS_EXCEPT_PUBLISH=$(echo $1 | sed 's/\-\-publish//')
./version.sh ${VERSION} ${PARAMS_EXCEPT_PUBLISH}
./version.sh ${VERSION} ${PARAMS_EXCEPT_PUBLISH} $@
if [[ "$@" == *'--publish'* ]]
then
./publish-version.sh ${VERSION}
./publish-version.sh ${VERSION} $@
else
echo "Did not publish. If you wanted to do so, call the script with \"--publish\" or \"--publish-local\"."
fi
+1 -1
View File
@@ -1,6 +1,6 @@
#Wed Feb 09 17:05:19 CET 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
+8 -4
View File
@@ -5,7 +5,11 @@
rm -f version.txt
printf "versionName=$1-github\nversionCode=$1" >> version.txt
# You'll need to change server as your server and define a VERSION_PATH.
scp version.txt server:$VERSION_PATH
rm version.txt
if [[ "$@" == *'--from-ci'* ]]
then
echo "File created. HANDLE IN CI"
else
# You'll need to change server as your server and define a VERSION_PATH.
scp version.txt server:$VERSION_PATH
rm version.txt
fi
+3
View File
@@ -56,6 +56,8 @@ kotlin {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
implementation("io.mockk:mockk:1.12.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
}
}
val androidMain by getting {
@@ -112,6 +114,7 @@ android {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
namespace = "bou.amine.apps.readerforselfossv2"
}
sqldelight {
+1 -1
View File
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="bou.amine.apps.readerforselfossv2" />
<manifest />
@@ -105,4 +105,16 @@ class SelfossModel {
return this
}
}
class StatusAndData<T>(val success: Boolean, val data: T? = null) {
companion object {
fun <T> succes(d: T): StatusAndData<T> {
return StatusAndData(true, d)
}
fun <T> error(): StatusAndData<T> {
return StatusAndData(false)
}
}
}
}
@@ -11,6 +11,7 @@ import io.github.aakira.napier.Napier
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class Repository(private val api: SelfossApi, private val appSettingsService: AppSettingsService, connectivityStatus: ConnectivityStatus, private val db: ReaderForSelfossDB) {
@@ -38,7 +39,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
init {
// TODO: Dispatchers.IO not available in KMM, an alternative solution should be found
CoroutineScope(Dispatchers.Main).launch {
runBlocking {
updateApiVersion()
dateUtils = DateUtils(appSettingsService)
reloadBadges()
@@ -47,7 +48,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
// TODO: Use the updatedSince parameter
var fetchedItems: List<SelfossModel.Item>? = null
var fetchedItems: SelfossModel.StatusAndData<List<SelfossModel.Item>> = SelfossModel.StatusAndData.error()
if (isNetworkAvailable()) {
fetchedItems = api.getItems(
displayedItems.type,
@@ -59,23 +60,25 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
)
} else {
if (appSettingsService.isItemCachingEnabled()) {
fetchedItems = getDBItems().filter {
displayedItems == ItemType.ALL ||
(it.unread && displayedItems == ItemType.UNREAD) ||
(it.starred && displayedItems == ItemType.STARRED)
}.map { it.toView() }
fetchedItems = SelfossModel.StatusAndData.succes(
getDBItems().filter {
displayedItems == ItemType.ALL ||
(it.unread && displayedItems == ItemType.UNREAD) ||
(it.starred && displayedItems == ItemType.STARRED)
}.map { it.toView() }
)
}
}
if (fetchedItems != null) {
items = ArrayList(fetchedItems)
if (fetchedItems.success && fetchedItems.data != null) {
items = ArrayList(fetchedItems.data!!)
sortItems()
}
return items
}
suspend fun getOlderItems(): ArrayList<SelfossModel.Item> {
var fetchedItems: List<SelfossModel.Item>? = null
var fetchedItems: SelfossModel.StatusAndData<List<SelfossModel.Item>> = SelfossModel.StatusAndData.error()
if (isNetworkAvailable()) {
val offset = items.size
fetchedItems = api.getItems(
@@ -88,16 +91,16 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
)
} // When using the db cache, we load everything the first time, so there should be nothing more to load.
if (fetchedItems != null) {
items.addAll(fetchedItems)
if (fetchedItems.success && fetchedItems.data != null) {
items.addAll(fetchedItems.data!!)
sortItems()
}
return items
}
private suspend fun getMaxItemsForBackground(itemType: ItemType): List<SelfossModel.Item>? {
private suspend fun getMaxItemsForBackground(itemType: ItemType): List<SelfossModel.Item> {
return if (isNetworkAvailable()) {
api.getItems(
val items = api.getItems(
itemType.type,
0,
tagFilter?.tag,
@@ -106,6 +109,11 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
null,
200
)
return if (items.success && items.data != null) {
items.data
} else {
emptyList()
}
} else {
emptyList()
}
@@ -119,10 +127,10 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
var success = false
if (isNetworkAvailable()) {
val response = api.stats()
if (response != null) {
badgeUnread = response.unread
badgeAll = response.total
badgeStarred = response.starred
if (response.success && response.data != null) {
badgeUnread = response.data.unread
badgeAll = response.data.total
badgeStarred = response.data.starred
success = true
}
} else {
@@ -135,21 +143,27 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
return success
}
suspend fun getTags(): List<SelfossModel.Tag>? {
suspend fun getTags(): List<SelfossModel.Tag> {
return if (isNetworkAvailable()) {
val apiTags = api.tags()
if (apiTags != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) {
resetDBTagsWithData(apiTags)
if (apiTags.success && apiTags.data != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) {
resetDBTagsWithData(apiTags.data)
}
apiTags
apiTags.data ?: emptyList()
} else {
getDBTags().map { it.toView() }
}
}
// TODO: Add tests
suspend fun getSpouts(): Map<String, SelfossModel.Spout>? {
return if (isNetworkAvailable()) {
api.spouts()
val spouts = api.spouts()
return if (spouts.success && spouts.data != null) {
spouts.data
} else {
emptyMap() // TODO: do something here
}
} else {
throw NetworkUnavailableException()
}
@@ -158,15 +172,16 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
suspend fun getSources(): ArrayList<SelfossModel.Source>? {
return if (isNetworkAvailable()) {
val apiSources = api.sources()
if (apiSources != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) {
resetDBSourcesWithData(apiSources)
if (apiSources.success && apiSources.data != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) {
resetDBSourcesWithData(apiSources.data)
}
apiSources
apiSources.data
} else {
ArrayList(getDBSources().map { it.toView() })
}
}
// TODO: Add tests
suspend fun markAsRead(item: SelfossModel.Item): Boolean {
val success = markAsReadById(item.id)
@@ -178,14 +193,14 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private suspend fun markAsReadById(id: Int): Boolean {
return if (isNetworkAvailable()) {
api.markAsRead(id.toString())?.isSuccess == true
api.markAsRead(id.toString())?.isSuccess
} else {
insertDBAction(id.toString(), read = true)
true
}
}
// TODO: Add tests
suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
val success = unmarkAsReadById(item.id)
@@ -197,13 +212,14 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private suspend fun unmarkAsReadById(id: Int): Boolean {
return if (isNetworkAvailable()) {
api.unmarkAsRead(id.toString())?.isSuccess == true
api.unmarkAsRead(id.toString())?.isSuccess
} else {
insertDBAction(id.toString(), unread = true)
true
}
}
// TODO: Add tests
suspend fun starr(item: SelfossModel.Item): Boolean {
val success = starrById(item.id)
@@ -215,13 +231,14 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private suspend fun starrById(id: Int): Boolean {
return if (isNetworkAvailable()) {
api.starr(id.toString())?.isSuccess == true
api.starr(id.toString())?.isSuccess
} else {
insertDBAction(id.toString(), starred = true)
true
}
}
// TODO: Add tests
suspend fun unstarr(item: SelfossModel.Item): Boolean {
val success = unstarrById(item.id)
@@ -233,17 +250,18 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private suspend fun unstarrById(id: Int): Boolean {
return if (isNetworkAvailable()) {
api.unstarr(id.toString())?.isSuccess == true
api.unstarr(id.toString())?.isSuccess
} else {
insertDBAction(id.toString(), starred = true)
true
}
}
// TODO: Add tests
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
var success = false
if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true) {
if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() })?.isSuccess) {
success = true
for (item in items) {
markAsReadLocally(item)
@@ -332,7 +350,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
suspend fun updateRemote(): Boolean {
return if (isNetworkAvailable()) {
api.update()?.equals("finished") ?: false
api.update()?.equals("finished")
} else {
false
}
@@ -360,17 +378,20 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
api.refreshLoginInformation()
}
// TODO: This should be private
suspend fun updateApiVersion() {
val apiMajorVersion = appSettingsService.getApiVersion()
if (isNetworkAvailable()) {
val fetchedVersion = api.version()
if (fetchedVersion != null && fetchedVersion.getApiMajorVersion() != apiMajorVersion) {
appSettingsService.updateApiVersion(fetchedVersion.getApiMajorVersion())
if (fetchedVersion.success && fetchedVersion.data != null && fetchedVersion.data.getApiMajorVersion() != apiMajorVersion) {
appSettingsService.updateApiVersion(fetchedVersion.data.getApiMajorVersion())
}
}
}
// TODO: This should be private (since all api calls are made through the repository
// no other entity needs to know about the connectivity status)
fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride
private fun getDBActions(): List<ACTION> =
@@ -379,11 +400,13 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun deleteDBAction(action: ACTION) =
db.actionsQueries.deleteAction(action.id)
// TODO: This function should be private
fun getDBTags(): List<TAG> = db.tagsQueries.tags().executeAsList()
// TODO: This function should be private
fun getDBSources(): List<SOURCE> = db.sourcesQueries.sources().executeAsList()
fun resetDBTagsWithData(tagEntities: List<SelfossModel.Tag>) {
private fun resetDBTagsWithData(tagEntities: List<SelfossModel.Tag>) {
db.tagsQueries.deleteAllTags()
db.tagsQueries.transaction {
@@ -393,7 +416,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
}
}
fun resetDBSourcesWithData(sources: List<SelfossModel.Source>) {
private fun resetDBSourcesWithData(sources: List<SelfossModel.Source>) {
db.sourcesQueries.deleteAllSources()
db.sourcesQueries.transaction {
@@ -420,12 +443,12 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
db.itemsQueries.updateItem(item.datetime, item.title.getHtmlDecoded(), item.content, item.unread, item.starred, item.thumbnail, item.icon, item.link, item.sourcetitle, item.tags.joinToString(","), item.id.toString())
suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item>? {
suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> {
try {
val newItems = getMaxItemsForBackground(ItemType.UNREAD)
val allItems = getMaxItemsForBackground(ItemType.ALL)
val starredItems = getMaxItemsForBackground(ItemType.STARRED)
insertDBItems(newItems.orEmpty() + allItems.orEmpty() + starredItems.orEmpty())
insertDBItems(newItems + allItems + starredItems)
return newItems
} catch (e: Throwable) {
// We do nothing
@@ -433,6 +456,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
return emptyList()
}
// TODO: Add tests
suspend fun handleDBActions() {
val actions: List<ACTION> = getDBActions()
@@ -10,6 +10,7 @@ import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
@@ -34,7 +35,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
appSettingsService.logApiCalls(message)
}
}
level = LogLevel.ALL
level = LogLevel.INFO
}
install(HttpTimeout) {
requestTimeoutMillis = appSettingsService.getApiTimeout()
@@ -65,11 +66,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
client = createHttpClient()
}
suspend fun login(): SelfossModel.SuccessResponse? =
client.get(url("/login")) {
suspend fun login(): SelfossModel.SuccessResponse =
maybeResponse(client.get(url("/login")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun getItems(
type: String,
@@ -79,8 +80,8 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
search: String?,
updatedSince: String?,
items: Int? = null
): List<SelfossModel.Item>? =
client.get(url("/items")) {
): SelfossModel.StatusAndData<List<SelfossModel.Item>> =
bodyOrFailure(client.get(url("/items")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
parameter("type", type)
@@ -90,74 +91,74 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("updatedsince", updatedSince)
parameter("items", items ?: appSettingsService.getItemsNumber())
parameter("offset", offset)
}.body()
})
suspend fun stats(): SelfossModel.Stats? =
client.get(url("/stats")) {
suspend fun stats(): SelfossModel.StatusAndData<SelfossModel.Stats> =
bodyOrFailure(client.get(url("/stats")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun tags(): List<SelfossModel.Tag>? =
client.get(url("/tags")) {
suspend fun tags(): SelfossModel.StatusAndData<List<SelfossModel.Tag>> =
bodyOrFailure(client.get(url("/tags")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun update(): String? =
client.get(url("/update")) {
suspend fun update(): SelfossModel.StatusAndData<String> =
bodyOrFailure(client.get(url("/update")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun spouts(): Map<String, SelfossModel.Spout>? =
client.get(url("/sources/spouts")) {
suspend fun spouts(): SelfossModel.StatusAndData<Map<String, SelfossModel.Spout>> =
bodyOrFailure(client.get(url("/sources/spouts")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun sources(): ArrayList<SelfossModel.Source>? =
client.get(url("/sources/list")) {
suspend fun sources(): SelfossModel.StatusAndData<ArrayList<SelfossModel.Source>> =
bodyOrFailure(client.get(url("/sources/list")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun version(): SelfossModel.ApiVersion? =
client.get(url("/api/about")).body()
suspend fun version(): SelfossModel.StatusAndData<SelfossModel.ApiVersion> =
bodyOrFailure(client.get(url("/api/about")))
suspend fun markAsRead(id: String): SelfossModel.SuccessResponse? =
client.post(url("/mark/$id")) {
suspend fun markAsRead(id: String): SelfossModel.SuccessResponse =
maybeResponse(client.post(url("/mark/$id")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun unmarkAsRead(id: String): SelfossModel.SuccessResponse? =
client.post(url("/unmark/$id")) {
suspend fun unmarkAsRead(id: String): SelfossModel.SuccessResponse =
maybeResponse(client.post(url("/unmark/$id")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun starr(id: String): SelfossModel.SuccessResponse? =
client.post(url("/starr/$id")) {
suspend fun starr(id: String): SelfossModel.SuccessResponse =
maybeResponse(client.post(url("/starr/$id")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun unstarr(id: String): SelfossModel.SuccessResponse? =
client.post(url("/unstarr/$id")) {
suspend fun unstarr(id: String): SelfossModel.SuccessResponse =
maybeResponse(client.post(url("/unstarr/$id")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun markAllAsRead(ids: List<String>): SelfossModel.SuccessResponse? =
client.submitForm(
suspend fun markAllAsRead(ids: List<String>): SelfossModel.SuccessResponse =
maybeResponse(client.submitForm(
url = url("/mark"),
formParameters = Parameters.build {
append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword())
ids.map { append("ids[]", it) }
}
).body()
))
suspend fun createSourceForVersion(
title: String,
@@ -166,12 +167,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
tags: String,
filter: String,
version: Int
): SelfossModel.SuccessResponse? =
if (version > 1) {
createSource2(title, url, spout, tags, filter)
} else {
createSource(title, url, spout, tags, filter)
}
): SelfossModel.SuccessResponse =
maybeResponse(
if (version > 1) {
createSource2(title, url, spout, tags, filter)
} else {
createSource(title, url, spout, tags, filter)
}
)
suspend fun createSource(
title: String,
@@ -179,7 +182,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
spout: String,
tags: String,
filter: String
): SelfossModel.SuccessResponse? =
): HttpResponse =
client.submitForm(
url = url("/source?username=${appSettingsService.getUserName()}&password=${appSettingsService.getPassword()}"),
formParameters = Parameters.build {
@@ -189,7 +192,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
append("tags", tags)
append("filter", filter)
}
).body()
)
suspend fun createSource2(
title: String,
@@ -197,7 +200,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
spout: String,
tags: String,
filter: String
): SelfossModel.SuccessResponse? =
): HttpResponse =
client.submitForm(
url = url("/source?username=${appSettingsService.getUserName()}&password=${appSettingsService.getPassword()}"),
formParameters = Parameters.build {
@@ -207,11 +210,27 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
append("tags[]", tags)
append("filter", filter)
}
).body()
)
suspend fun deleteSource(id: Int): SelfossModel.SuccessResponse? =
client.delete(url("/source/$id")) {
suspend fun deleteSource(id: Int): SelfossModel.SuccessResponse =
maybeResponse(client.delete(url("/source/$id")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun maybeResponse(r: HttpResponse): SelfossModel.SuccessResponse {
return if (r.status.isSuccess()) {
r.body()
} else {
SelfossModel.SuccessResponse(false)
}
}
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse): SelfossModel.StatusAndData<T> {
return if (r.status.isSuccess()) {
SelfossModel.StatusAndData.succes(r.body())
} else {
SelfossModel.StatusAndData.error()
}
}
}
@@ -0,0 +1,866 @@
package bou.amine.apps.readerforselfossv2.repository
import bou.amine.apps.readerforselfossv2.dao.ITEM
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
import bou.amine.apps.readerforselfossv2.dao.SOURCE
import bou.amine.apps.readerforselfossv2.dao.TAG
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.ItemType
import bou.amine.apps.readerforselfossv2.utils.toView
import com.github.ln_12.library.ConnectivityStatus
import io.mockk.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
import kotlin.test.*
class RepositoryTest() {
private val connectivityStatus = mockk<ConnectivityStatus>()
private val db = mockk<ReaderForSelfossDB>()
private val appSettingsService = mockk<AppSettingsService>()
private val api = mockk<SelfossApi>()
private val NUMBER_ARTICLES = 100
private val NUMBER_UNREAD = 50
private val NUMBER_STARRED = 20
@BeforeTest
fun setup() {
clearAllMocks()
every { appSettingsService.getApiVersion() } returns 4
every { appSettingsService.getBaseUrl() } returns "https://test.com/selfoss/"
every { appSettingsService.isItemCachingEnabled() } returns false
every { appSettingsService.isUpdateSourcesEnabled() } returns true
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(true)
coEvery { api.version() } returns SelfossModel.StatusAndData(success = true, data = SelfossModel.ApiVersion("2.19-ba1e8e3", "4.0.0"))
coEvery { api.stats() } returns SelfossModel.StatusAndData(success = true, data = SelfossModel.Stats(NUMBER_ARTICLES, NUMBER_UNREAD, NUMBER_STARRED))
every { db.itemsQueries.items().executeAsList() } returns generateTestDBItems()
every { db.tagsQueries.deleteAllTags() } returns Unit
every { db.tagsQueries.transaction(any(), any()) } returns Unit
every { db.tagsQueries.insertTag(any()) } returns Unit
}
@Test
fun `Instantiate repository`() {
val success = try {
Repository(api, appSettingsService, connectivityStatus, db)
true
} catch (e: Exception) {
false
}
assertEquals(true, success)
}
@Test
fun `Instantiate repository without api version`() {
every { appSettingsService.getApiVersion() } returns -1
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(false)
val success = try {
Repository(api, appSettingsService, connectivityStatus, db)
true
} catch (e: Exception) {
false
}
assertEquals(true, success)
}
@Test
fun `Instantiate repository with negative stats`() {
coEvery { api.stats() } returns SelfossModel.StatusAndData(success = true, data = SelfossModel.Stats(-100, -50, -20))
val success = try {
Repository(api, appSettingsService, connectivityStatus, db)
true
} catch (e: Exception) {
false
}
assertEquals(true, success)
}
@Test
fun `Get api 4 date with api 1 version stored`() {
every { appSettingsService.getApiVersion() } returns 1
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = true, data = generateTestApiItem())
every { appSettingsService.updateApiVersion(any()) } returns Unit
val repository = Repository(api, appSettingsService, connectivityStatus, db)
runBlocking {
repository.getNewerItems()
}
assertSame(repository.items.size, 1)
verify(exactly = 1) { appSettingsService.updateApiVersion(4) }
}
@Test
fun `Get api 1 date with api 4 version stored`() {
every { appSettingsService.getApiVersion() } returns 4
coEvery { api.version() } returns SelfossModel.StatusAndData(success = false, null)
val itemParameters = FakeItemParameters()
itemParameters.datetime = "2021-04-23 11:45:32"
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = true, data = generateTestApiItem(itemParameters))
val repository = Repository(api, appSettingsService, connectivityStatus, db)
runBlocking {
repository.getNewerItems()
}
assertSame(1, repository.items.size)
}
@Test
fun `Get newer items`() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = true, data = generateTestApiItem())
val repository = Repository(api, appSettingsService, connectivityStatus, db)
runBlocking {
repository.getNewerItems()
}
assertSame(repository.items.size, 1)
coVerify(exactly = 1) { api.getItems("unread", 0, null, null, null, null, any()) }
verify(exactly = 0) { db.itemsQueries.items().executeAsList()}
}
@Test
fun `Get all newer items`() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = true, data = generateTestApiItem())
val repository = Repository(api, appSettingsService, connectivityStatus, db)
repository.displayedItems = ItemType.ALL
runBlocking {
repository.getNewerItems()
}
assertSame(repository.items.size, 1)
coVerify(exactly = 1) { api.getItems("all", 0, null, null, null, null, any()) }
verify(exactly = 0) { db.itemsQueries.items().executeAsList()}
}
@Test
fun `Get newer starred items`() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = true, data = generateTestApiItem())
val repository = Repository(api, appSettingsService, connectivityStatus, db)
repository.displayedItems = ItemType.STARRED
runBlocking {
repository.getNewerItems()
}
assertSame(repository.items.size, 1)
coVerify(exactly = 1) { api.getItems("starred", 0, null, null, null, null, any()) }
verify(exactly = 0) { db.itemsQueries.items().executeAsList()}
}
@Test
fun `Get newer items without connectivity`() {
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(false)
every { appSettingsService.isItemCachingEnabled() } returns true
val repository = Repository(api, appSettingsService, connectivityStatus, db)
runBlocking {
repository.getNewerItems()
}
assertSame(repository.items.size, 1)
coVerify(exactly = 0) { api.getItems("unread", 0, null, null, null, null, any()) }
verify(atLeast = 1) { db.itemsQueries.items().executeAsList()}
}
@Test
fun `Get older items`() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = true, data = generateTestApiItem())
val repository = Repository(api, appSettingsService, connectivityStatus, db)
repository.items = ArrayList(generateTestApiItem())
runBlocking {
repository.getOlderItems()
}
assertSame(repository.items.size, 2)
coVerify(exactly = 1) { api.getItems("unread", 1, null, null, null, null, any()) }
verify(exactly = 0) { db.itemsQueries.items().executeAsList()}
}
@Test
fun `Get all older items`() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = true, data = generateTestApiItem())
val repository = Repository(api, appSettingsService, connectivityStatus, db)
repository.items = ArrayList(generateTestApiItem())
repository.displayedItems = ItemType.ALL
runBlocking {
repository.getOlderItems()
}
assertSame(repository.items.size, 2)
coVerify(exactly = 1) { api.getItems("all", 1, null, null, null, null, any()) }
verify(exactly = 0) { db.itemsQueries.items().executeAsList()}
}
@Test
fun `Get older starred items`() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = true, data = generateTestApiItem())
val repository = Repository(api, appSettingsService, connectivityStatus, db)
repository.displayedItems = ItemType.STARRED
repository.items = ArrayList(generateTestApiItem())
runBlocking {
repository.getOlderItems()
}
assertSame(repository.items.size, 2)
coVerify(exactly = 1) { api.getItems("starred", 1, null, null, null, null, any()) }
verify(exactly = 0) { db.itemsQueries.items().executeAsList()}
}
@Test
fun `Reload badges`() {
var success = false
val repository = Repository(api, appSettingsService, connectivityStatus, db)
runBlocking {
success = repository.reloadBadges()
}
assertSame(true, success)
assertSame(NUMBER_ARTICLES, repository.badgeAll)
assertSame(NUMBER_UNREAD, repository.badgeUnread)
assertSame(NUMBER_STARRED, repository.badgeStarred)
coVerify(atLeast = 1) { api.stats() }
verify(exactly = 0) { db.itemsQueries.items().executeAsList()}
}
@Test
fun `Reload badges without response`() {
coEvery { api.stats() } returns SelfossModel.StatusAndData(success = false, data = null)
var success = false
val repository = Repository(api, appSettingsService, connectivityStatus, db)
runBlocking {
success = repository.reloadBadges()
}
assertSame(false, success)
assertSame(0, repository.badgeAll)
assertSame(0, repository.badgeUnread)
assertSame(0, repository.badgeStarred)
coVerify(atLeast = 1) { api.stats() }
verify(exactly = 0) { db.itemsQueries.items().executeAsList()}
}
@Test
fun `Reload badges with items caching`() {
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(false)
every { appSettingsService.isItemCachingEnabled() } returns true
every { db.itemsQueries.items().executeAsList() } returns generateTestDBItems()
val itemParameter = FakeItemParameters()
itemParameter.starred = true
itemParameter.unread = false
var success = false
val repository = Repository(api, appSettingsService, connectivityStatus, db)
repository.items = ArrayList(generateTestApiItem(itemParameter))
runBlocking {
success = repository.reloadBadges()
}
assertSame(true, success)
assertSame(1, repository.badgeAll)
assertSame(1, repository.badgeUnread)
assertSame(1, repository.badgeStarred)
coVerify(exactly = 0) { api.stats() }
verify(atLeast = 1) { db.itemsQueries.items().executeAsList()}
}
@Test
fun `Reload badges without items caching`() {
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(false)
every { appSettingsService.isItemCachingEnabled() } returns false
val itemParameter = FakeItemParameters()
itemParameter.starred = true
itemParameter.unread = false
var success = false
val repository = Repository(api, appSettingsService, connectivityStatus, db)
repository.items = ArrayList(generateTestApiItem(itemParameter))
runBlocking {
success = repository.reloadBadges()
}
assertSame(false, success)
assertSame(0, repository.badgeAll)
assertSame(0, repository.badgeUnread)
assertSame(0, repository.badgeStarred)
coVerify(exactly = 0) { api.stats() }
verify(exactly = 0) { db.itemsQueries.items().executeAsList()}
}
@Test
fun `Get tags`() {
val tags = listOf(SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0))
coEvery { api.tags() } returns SelfossModel.StatusAndData(success = true, data = tags)
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var testTags: List<SelfossModel.Tag>? = null
runBlocking {
testTags = repository.getTags()
}
assertSame(tags, testTags)
verify(exactly = 0) { db.tagsQueries.tags().executeAsList() }
}
@Test
fun `Get tags with sources update disabled`() {
val tags = listOf(SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0))
val tagsDB = listOf(TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0))
coEvery { api.tags() } returns SelfossModel.StatusAndData(success = true, data = tags)
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
every { appSettingsService.isUpdateSourcesEnabled() } returns false
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var testTags: List<SelfossModel.Tag> = emptyList()
runBlocking {
testTags = repository.getTags()
testTags = repository.getTags()
}
assertNotSame(tags, testTags)
assertContentEquals(tagsDB.map { it.toView() }, testTags)
coVerify(exactly = 1) { api.tags() }
verify(atLeast = 1) { db.tagsQueries.tags().executeAsList() }
}
@Test
fun `Get tags with sources update and items caching disabled`() {
val tags = listOf(SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0))
val tagsDB = listOf(TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0))
coEvery { api.tags() } returns SelfossModel.StatusAndData(success = true, data = tags)
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
every { appSettingsService.isUpdateSourcesEnabled() } returns false
every { appSettingsService.isItemCachingEnabled() } returns false
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var testTags: List<SelfossModel.Tag> = emptyList()
runBlocking {
testTags = repository.getTags()
}
assertSame(emptyList(), testTags)
coVerify(exactly = 0) { api.tags() }
verify(exactly = 0) { db.tagsQueries.tags().executeAsList() }
}
@Test
fun `Get tags without connection`() {
val tags = listOf(SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0))
val tagsDB = listOf(TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0))
coEvery { api.tags() } returns SelfossModel.StatusAndData(success = true, data = tags)
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(false)
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var testTags: List<SelfossModel.Tag> = emptyList()
runBlocking {
testTags = repository.getTags()
}
assertNotSame(tags, testTags)
assertSame(tagsDB.first().name, testTags.first().tag)
coVerify(exactly = 0) { api.tags() }
verify(atLeast = 1) { db.tagsQueries.tags().executeAsList() }
}
@Test
fun `Get tags without connection and items caching disabled`() {
val tags = listOf(SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0))
val tagsDB = listOf(TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0))
coEvery { api.tags() } returns SelfossModel.StatusAndData(success = true, data = tags)
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(false)
every { appSettingsService.isItemCachingEnabled() } returns false
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var testTags: List<SelfossModel.Tag> = emptyList()
runBlocking {
testTags = repository.getTags()
}
assertContentEquals(tagsDB.map { it.toView() }, testTags)
coVerify(exactly = 0) { api.tags() }
verify(atLeast = 1) { db.tagsQueries.tags().executeAsList() }
}
@Test
fun `Get tags without connection and sources update disabled`() {
val tags = listOf(SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0))
val tagsDB = listOf(TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0))
coEvery { api.tags() } returns SelfossModel.StatusAndData(success = true, data = tags)
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(false)
every { appSettingsService.isUpdateSourcesEnabled() } returns false
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var testTags: List<SelfossModel.Tag> = emptyList()
runBlocking {
testTags = repository.getTags()
}
assertNotSame(tags, testTags)
assertContentEquals(tagsDB.map { it.toView() }, testTags)
coVerify(exactly = 0) { api.tags() }
verify(atLeast = 1) { db.tagsQueries.tags().executeAsList() }
}
@Test
fun `get sources`() {
val sources = arrayListOf(SelfossModel.Source(1, "First source", listOf("Test", "second"),"spouts\\rss\\fulltextrss", "", "d8c92cdb1ef119ea85c4b9205c879ca7.png"),
SelfossModel.Source(2, "Second source", listOf("second"),"spouts\\rss\\fulltextrss", "", "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"))
coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources)
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var testSources: List<SelfossModel.Source>? = null
runBlocking {
testSources = repository.getSources()
}
assertSame(sources, testSources)
coVerify(exactly = 1) { api.sources() }
verify(exactly = 0) { db.sourcesQueries.sources().executeAsList() }
}
@Test
fun `get sources without connection`() {
val sources = arrayListOf(SelfossModel.Source(1, "First source", listOf("Test", "second"),"spouts\\rss\\fulltextrss", "", "d8c92cdb1ef119ea85c4b9205c879ca7.png"),
SelfossModel.Source(2, "Second source", listOf("second"),"spouts\\rss\\fulltextrss", "", "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"))
val sourcesDB = listOf<SOURCE>(SOURCE("1", "First source", "Test,second","spouts\\rss\\fulltextrss", "", "d8c92cdb1ef119ea85c4b9205c879ca7.png"),
SOURCE("2", "Second source", "second","spouts\\rss\\fulltextrss", "", "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"))
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(false)
coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var testSources: List<SelfossModel.Source>? = null
runBlocking {
testSources = repository.getSources()
}
assertContentEquals(sources, testSources)
coVerify(exactly = 0) { api.sources() }
verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
}
@Test
fun `get sources without connection and items caching disabled`() {
val sources = arrayListOf(SelfossModel.Source(1, "First source", listOf("Test", "second"),"spouts\\rss\\fulltextrss", "", "d8c92cdb1ef119ea85c4b9205c879ca7.png"),
SelfossModel.Source(2, "Second source", listOf("second"),"spouts\\rss\\fulltextrss", "", "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"))
val sourcesDB = listOf<SOURCE>(SOURCE("1", "First source", "Test,second","spouts\\rss\\fulltextrss", "", "d8c92cdb1ef119ea85c4b9205c879ca7.png"),
SOURCE("2", "Second source", "second","spouts\\rss\\fulltextrss", "", "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"))
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(false)
every { appSettingsService.isItemCachingEnabled() } returns false
coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var testSources: List<SelfossModel.Source>? = null
runBlocking {
testSources = repository.getSources()
}
assertSame(null, testSources)
coVerify(exactly = 0) { api.sources() }
verify(exactly = 0) { db.sourcesQueries.sources().executeAsList() }
}
@Test
fun `get sources with sources update disabled`() {
val sources = arrayListOf(SelfossModel.Source(1, "First source", listOf("Test", "second"),"spouts\\rss\\fulltextrss", "", "d8c92cdb1ef119ea85c4b9205c879ca7.png"),
SelfossModel.Source(2, "Second source", listOf("second"),"spouts\\rss\\fulltextrss", "", "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"))
val sourcesDB = listOf<SOURCE>(SOURCE("1", "First source", "Test,second","spouts\\rss\\fulltextrss", "", "d8c92cdb1ef119ea85c4b9205c879ca7.png"),
SOURCE("2", "Second source", "second","spouts\\rss\\fulltextrss", "", "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"))
every { appSettingsService.isUpdateSourcesEnabled() } returns false
coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var testSources: List<SelfossModel.Source>? = null
runBlocking {
testSources = repository.getSources()
}
assertContentEquals(sources, testSources)
coVerify(exactly = 0) { api.sources() }
verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
}
@Test
fun `get sources with sources update and items caching disabled`() {
val sources = arrayListOf(SelfossModel.Source(1, "First source", listOf("Test", "second"),"spouts\\rss\\fulltextrss", "", "d8c92cdb1ef119ea85c4b9205c879ca7.png"),
SelfossModel.Source(2, "Second source", listOf("second"),"spouts\\rss\\fulltextrss", "", "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"))
val sourcesDB = listOf<SOURCE>(SOURCE("1", "First source", "Test,second","spouts\\rss\\fulltextrss", "", "d8c92cdb1ef119ea85c4b9205c879ca7.png"),
SOURCE("2", "Second source", "second","spouts\\rss\\fulltextrss", "", "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"))
every { appSettingsService.isUpdateSourcesEnabled() } returns false
every { appSettingsService.isItemCachingEnabled() } returns false
coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var testSources: List<SelfossModel.Source>? = null
runBlocking {
testSources = repository.getSources()
}
assertSame(null, testSources)
coVerify(exactly = 0) { api.sources() }
verify(exactly = 0) { db.sourcesQueries.sources().executeAsList() }
}
@Test
fun `create source`() {
coEvery { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) } returns
SelfossModel.SuccessResponse(true)
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var response = false
runBlocking {
response = repository.createSource("test", "https://test.com/feed", "spouts\\rss\\fulltextrss", "Test, New", "")
}
coVerify(exactly = 1) { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) }
assertSame(true, response)
}
@Test
fun `create source but response fails`() {
coEvery { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) } returns
SelfossModel.SuccessResponse(false)
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var response = false
runBlocking {
response = repository.createSource("test", "https://test.com/feed", "spouts\\rss\\fulltextrss", "Test, New", "")
}
coVerify(exactly = 1) { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) }
assertSame(false, response)
}
@Test
fun `create source without connection`() {
coEvery { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) } returns
SelfossModel.SuccessResponse(true)
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(false)
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var response = false
runBlocking {
response = repository.createSource("test", "https://test.com/feed", "spouts\\rss\\fulltextrss", "Test, New", "")
}
coVerify(exactly = 0) { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) }
assertSame(false, response)
}
@Test
fun `delete source`() {
coEvery { api.deleteSource(any())} returns SelfossModel.SuccessResponse(true)
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var response = false
runBlocking {
response = repository.deleteSource(5)
}
coVerify(exactly = 1) { api.deleteSource(5) }
assertSame(true, response)
}
@Test
fun `delete source but response fails`() {
coEvery { api.deleteSource(any())} returns SelfossModel.SuccessResponse(false)
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var response = false
runBlocking {
response = repository.deleteSource(5)
}
coVerify(exactly = 1) { api.deleteSource(5) }
assertSame(false, response)
}
@Test
fun `delete source without connection`() {
coEvery { api.deleteSource(any())} returns SelfossModel.SuccessResponse(false)
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(false)
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var response = false
runBlocking {
response = repository.deleteSource(5)
}
coVerify(exactly = 0) { api.deleteSource(5) }
assertSame(false, response)
}
@Test
fun `update remote`() {
coEvery { api.update()} returns SelfossModel.StatusAndData(success = true, data = "finished")
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var response = false
runBlocking {
response = repository.updateRemote()
}
coVerify(exactly = 1) { api.update() }
assertSame(true, response)
}
@Test
fun `update remote but response fails`() {
coEvery { api.update()} returns SelfossModel.StatusAndData(success = false, data = "undocumented...")
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var response = false
runBlocking {
response = repository.updateRemote()
}
coVerify(exactly = 1) { api.update() }
assertSame(false, response)
}
@Test
fun `update remote without connection`() {
coEvery { api.update()} returns SelfossModel.StatusAndData(success = true, data = "undocumented...")
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(false)
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var response = false
runBlocking {
response = repository.updateRemote()
}
coVerify(exactly = 0) { api.update() }
assertSame(false, response)
}
@Test
fun login() {
coEvery { api.login() } returns SelfossModel.SuccessResponse(success = true)
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var response = false
runBlocking {
response = repository.login()
}
coVerify(exactly = 1) { api.login() }
assertSame(true, response)
}
@Test
fun `login but response fails`() {
coEvery { api.login() } returns SelfossModel.SuccessResponse(success = false)
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var response = false
runBlocking {
response = repository.login()
}
coVerify(exactly = 1) { api.login() }
assertSame(false, response)
}
@Test
fun `login but without connection`() {
coEvery { api.login() } returns SelfossModel.SuccessResponse(success = true)
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(false)
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var response = false
runBlocking {
response = repository.login()
}
coVerify(exactly = 0) { api.login() }
assertSame(false, response)
}
@Test
fun `refresh login information`() {
coEvery { api.refreshLoginInformation() } returns Unit
coEvery { appSettingsService.refreshLoginInformation(any(), any(), any()) } returns Unit
val repository = Repository(api, appSettingsService, connectivityStatus, db)
repository.refreshLoginInformation("https://test.com/selfoss/", "login", "password")
coVerify(exactly = 1) { api.refreshLoginInformation() }
coVerify(exactly = 1) {appSettingsService.refreshLoginInformation("https://test.com/selfoss/", "login", "password")}
}
@Test
fun `cache items`() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = true, data = generateTestApiItem())
val repository = Repository(api, appSettingsService, connectivityStatus, db)
repository.tagFilter = SelfossModel.Tag("Tag", "read", 0)
repository.sourceFilter = SelfossModel.Source(
1,
"First source",
listOf("Test", "second"),
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
)
repository.searchFilter = "search"
var items = emptyList<SelfossModel.Item>()
runBlocking {
items = repository.tryToCacheItemsAndGetNewOnes()
}
coVerify(exactly = 3) { api.getItems(any(), 0, null, null, null, null, 200) }
assertSame(3, items.size)
}
@Test
fun `cache items but response fails`() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = false, data = generateTestApiItem())
val repository = Repository(api, appSettingsService, connectivityStatus, db)
repository.tagFilter = SelfossModel.Tag("Tag", "read", 0)
repository.sourceFilter = SelfossModel.Source(
1,
"First source",
listOf("Test", "second"),
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
)
repository.searchFilter = "search"
var items = emptyList<SelfossModel.Item>()
runBlocking {
items = repository.tryToCacheItemsAndGetNewOnes()
}
coVerify(exactly = 3) { api.getItems(any(), 0, null, null, null, null, 200) }
assertSame(0, items.size)
}
@Test
fun `cache items without connection`() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = false, data = generateTestApiItem())
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(false)
val repository = Repository(api, appSettingsService, connectivityStatus, db)
repository.tagFilter = SelfossModel.Tag("Tag", "read", 0)
repository.sourceFilter = SelfossModel.Source(
1,
"First source",
listOf("Test", "second"),
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
)
repository.searchFilter = "search"
var items = emptyList<SelfossModel.Item>()
runBlocking {
items = repository.tryToCacheItemsAndGetNewOnes()
}
coVerify(exactly = 0) { api.getItems(any(), 0, null, null, null, null, 200) }
assertSame(emptyList<SelfossModel.Item>(), items)
}
}
fun generateTestDBItems(item : FakeItemParameters = FakeItemParameters()) : List<ITEM> {
return listOf(ITEM(
id = item.id,
datetime = item.datetime,
title = item.title,
content = item.content,
unread = item.unread,
starred = item.starred,
thumbnail = item.thumbnail,
icon = item.icon,
link = item.link,
sourcetitle = item.sourcetitle,
tags = item.tags
))}
fun generateTestApiItem(item : FakeItemParameters = FakeItemParameters()) : List<SelfossModel.Item> {
return listOf(
SelfossModel.Item(
id = item.id.toInt(),
datetime = item.datetime,
title = item.title,
content = item.content,
unread = item.unread,
starred = item.starred,
thumbnail = item.thumbnail,
icon = item.icon,
link = item.link,
sourcetitle = item.sourcetitle,
tags = item.tags.split(',')
)
)
}
class FakeItemParameters() {
val id = "20"
var datetime = "2022-09-09T03:32:01-04:00"
val title = "Etica della ricerca sotto i riflettori."
val content = "<p><strong>Luigi Campanella, già Presidente SCI</strong></p>\n<p>Letica della scienza è di certo ambito di cui continuiamo a scoprire nuovi aspetti e risvolti.</p>\n<p>Lultimo è quello delle intelligenze artificiali capaci di creare opere complesse basate su immagini e parole memorizzate con il rischio di fake news e di contenuti disturbanti.</p>\n<p>Per evitare che ciò accada si sta procedendo filtrando secondo criteri di autocensura i dati da cui lintelligenza artificiale parte.</p>\n<p>Comincia ad intravedersi un futuro prossimo di competizione fra autori umani ed artificiali nel quale sarà importante, quando i loro prodotti saranno indistinguibili, dichiararne lorigine.</p>\n<p>Come si comprende, si conferma che gli aspetti etici dellinnovazione e della ricerca si diversificato sempre di più.</p>\n<p>La biologia molecolare e la genetica già in passato hanno posto allattenzione comune aspetti di etica della scienza che hanno indotto a nuove riflessioni circa i limiti delle ricerche.</p>\n<p>Largomento, sempre attuale, torna sulle prime pagine a seguito della pubblicazione di una ricerca della Università di Cambridge che ha sviluppato una struttura cellulare di un topo con un cuore che batte regolarmente.</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image002-1.png?w=481\" alt=\"\" width=\"697\" height=\"430\" /><img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image003-1.png?w=906\" alt=\"\" /><p>Magdalena Zernicka-Goetz</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image004.jpg?w=474\" alt=\"\" width=\"622\" height=\"465\" /><p>Gianluca Amadei</p>\n<p>Del gruppo fa parte anche uno scienziato italiano Gianluca Amadei,che dinnanzi alle obiezioni di natura etica sulla realizzazione della vita artificiale si è affrettato a sostenere che non è creare nuove vite il fine primario della ricerca, ma quello di salvare quelle esistenti, di dare contributi essenziali alla medicina citando il caso del fallimento tuttora non interpretato di alcune gravidanze e di superare la sperimentazione animale, così contribuendo positivamente alla soluzione di un altro dilemma etico.</p>\n<p>Lembrione sintetico ha ovviamente come primo traguardo il contributo ai trapianti oggi drammaticamente carenti nellofferta rispetto alla domanda, con attese fino a 4 anni per i trapianti di cuore ed a 2 anni per quelli di fegato. Il lavoro dovrebbe adesso continuare presso lAteneo di Padova per creare nuovi organi e nuovi farmaci.</p>"
var unread = true
var starred = true
val thumbnail = null
val icon = "ba79e238383ce83c23a169929c8906ef.png"
val link = "https://ilblogdellasci.wordpress.com/2022/09/09/etica-della-ricerca-sotto-i-riflettori/"
val sourcetitle = "La Chimica e la Società"
val tags = "Chimica, Testing"
}
+10 -7
View File
@@ -1,11 +1,14 @@
#!/bin/bash
# You can pass --force as first parameter to force push and tag creation.
echo "Creating tag $@"
echo "Creating tag $1"
TAG="v$@"
git tag ${TAG}
TAG="v$1"
git tag -a ${TAG} -m ${TAG}
echo "Pushing tag"
git push origin ${TAG}
if [[ "$@" == *'--from-ci'* ]]
then
echo "Tag created. HANDLE IN CI"
else
echo "Pushing tag"
git push origin ${TAG}
fi