Merge branch 'feature_boards_list' into feature_integrate_login
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Raffaele Mignone 2019-12-08 19:51:08 +01:00
commit 91e6b5b8a7
Signed by: norangebit
GPG Key ID: F5255658CB220573
13 changed files with 629 additions and 87 deletions

View File

@ -57,9 +57,10 @@ dependencies {
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
// TESTING // TESTING
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
testImplementation "io.mockk:mockk:1.9.3"
testImplementation "com.squareup.okhttp3:mockwebserver:4.2.1"
androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

View File

@ -62,15 +62,29 @@ class BoardDaoTest {
} }
} }
@Test
fun replaceOnConflict() {
val board0 = Board("id", "title0")
val board1 = Board("id", "title1")
runBlocking {
dao.insert(board0)
dao.insert(board1)
}
dao.getAllBoard().observeOnce {
assertEquals(1, it.size)
assertEquals("title1", it[0].title)
}
}
@Test
fun getInAscendingOrder() { fun getInAscendingOrder() {
val board0 = Board("id0", "title0") val board0 = Board("id0", "title0")
val board1 = Board("id1", "title1") val board1 = Board("id1", "title1")
runBlocking { runBlocking {
dao.insert(board1) dao.insert(board1)
}
runBlocking {
dao.insert(board0) dao.insert(board0)
} }
@ -81,14 +95,12 @@ class BoardDaoTest {
} }
} }
@Test
fun delete() { fun delete() {
val board = Board("id", "title") val board = Board("id", "title")
runBlocking { runBlocking {
dao.insert(board) dao.insert(board)
}
runBlocking {
dao.delete(board) dao.delete(board)
} }

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="it.unisannio.ding.ids.wedroid.app"> package="it.unisannio.ding.ids.wedroid.app">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:allowBackup="true" android:allowBackup="true"
@ -11,12 +12,16 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity android:name=".view.LoginActivity"></activity> <activity android:name=".view.LoginActivity" />
<activity
android:name=".view.NewBoardActivity"
android:label="@string/title_activity_new_board"
android:theme="@style/AppTheme.NoActionBar" />
<activity <activity
android:name=".view.BoardsListsActivity" android:name=".view.BoardsListsActivity"
android:theme="@style/AppTheme.NoActionBar"> android:theme="@style/AppTheme.NoActionBar">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View File

@ -7,6 +7,7 @@ import it.unisannio.ding.ids.wedroid.app.data.entity.convert
import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader
import it.unisannio.ding.ids.wedroid.wrapper.api.BoardService import it.unisannio.ding.ids.wedroid.wrapper.api.BoardService
import it.unisannio.ding.ids.wedroid.wrapper.entity.BoardBackgroundColor import it.unisannio.ding.ids.wedroid.wrapper.entity.BoardBackgroundColor
import it.unisannio.ding.ids.wedroid.wrapper.entity.BoardPermission
import it.unisannio.ding.ids.wedroid.wrapper.entity.BoardPrototype import it.unisannio.ding.ids.wedroid.wrapper.entity.BoardPrototype
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -28,12 +29,52 @@ class BoardRepository(
synchronize() synchronize()
} }
fun insertBoard(title: String) { fun synchronize() {
service.getBoardsFromUser(reader.userId)
.enqueue(object :
Callback<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>> {
override fun onFailure(
call: Call<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>,
t: Throwable
) = logNetworkError(t.message)
override fun onResponse(
call: Call<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>,
response: Response<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>
) {
CoroutineScope(Dispatchers.IO).launch {
synchronizeCallback(response)
}
}
})
}
private suspend fun synchronizeCallback(
response: Response<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>
) {
if (!response.isSuccessful) {
logNetworkError("${response.code()} ${response.message()}")
return
}
// read boards from the body
val boards = (response.body() ?: return)
.map { it.convert() }
addNewBoardToDb(boards)
removeOldBoardsFromDb(boards)
}
fun insertBoard(title: String, isPrivate: Boolean, color: BoardBackgroundColor) {
val permission = if (isPrivate) BoardPermission.PRIVATE else BoardPermission.PUBLIC
service.newBoard( service.newBoard(
BoardPrototype.Builder() BoardPrototype.Builder()
.setOwner(reader.userId) .setOwner(reader.userId)
.setTitle(title) .setTitle(title)
.setBackgroundColor(BoardBackgroundColor.LIMEGREEN) .setBackgroundColor(color)
.setBoardPermission(permission)
.build() .build()
).enqueue(object : Callback<it.unisannio.ding.ids.wedroid.wrapper.entity.Board> { ).enqueue(object : Callback<it.unisannio.ding.ids.wedroid.wrapper.entity.Board> {
override fun onFailure( override fun onFailure(
@ -45,53 +86,30 @@ class BoardRepository(
call: Call<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>, call: Call<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>,
response: Response<it.unisannio.ding.ids.wedroid.wrapper.entity.Board> response: Response<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>
) { ) {
if (!response.isSuccessful) {
logNetworkError("${response.code()} ${response.message()}")
return
}
val board = response.body()
if (board == null) {
logNetworkError("empty body")
return
}
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
dao.insert(Board(board.id, title)) insertBoardCallback(response, title)
} }
} }
}) })
} }
fun synchronize() { private suspend fun insertBoardCallback(
service.getBoardsFromUser(reader.userId) response: Response<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>,
.enqueue(object : title: String
Callback<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>> { ) {
if (!response.isSuccessful) {
logNetworkError("${response.code()} ${response.message()}")
return
}
override fun onFailure( val board = response.body()
call: Call<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>,
t: Throwable
) = logNetworkError(t.message)
override fun onResponse( if (board == null) {
call: Call<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>, logNetworkError("empty body")
response: Response<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>> return
) { }
if (!response.isSuccessful) {
logNetworkError("${response.code()} ${response.message()}")
return
}
// read boards from the body dao.insert(Board(board.id, title))
val boards = (response.body() ?: return)
.map { it.convert() }
addNewBoardToDb(boards)
removeOldBoardsFromDb(boards)
}
})
} }
fun deleteBoard(id: String) { fun deleteBoard(id: String) {
@ -101,33 +119,39 @@ class BoardRepository(
logNetworkError(t.message) logNetworkError(t.message)
} }
override fun onResponse(call: Call<Void>, response: Response<Void>) { override fun onResponse(
if (!response.isSuccessful) { call: Call<Void>,
logNetworkError("${response.code()}, ${response.message()}") response: Response<Void>
return ) {
}
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
dao.delete(Board(id)) deleteBoardCallback(response, id)
} }
} }
}) })
} }
private fun addNewBoardToDb(boards: Collection<Board>) { private suspend fun deleteBoardCallback(
response: Response<Void>,
id: String
) {
if (!response.isSuccessful) {
logNetworkError("${response.code()} ${response.message()}")
return
}
dao.delete(Board(id))
}
private suspend fun addNewBoardToDb(boards: Collection<Board>) {
boards.forEach { boards.forEach {
CoroutineScope(Dispatchers.IO).launch { dao.insert(it)
dao.insert(it)
}
} }
} }
private fun removeOldBoardsFromDb(boards: Collection<Board>) { private suspend fun removeOldBoardsFromDb(boards: Collection<Board>) {
allBoards.value?.minus(boards) allBoards.value?.minus(boards)
?.forEach { ?.forEach {
CoroutineScope(Dispatchers.IO).launch {
dao.delete(it) dao.delete(it)
}
} }
} }

View File

@ -2,7 +2,7 @@ package it.unisannio.ding.ids.wedroid.app.view
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@ -14,6 +14,7 @@ import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader
import it.unisannio.ding.ids.wedroid.app.util.SharedPreferenceHelper import it.unisannio.ding.ids.wedroid.app.util.SharedPreferenceHelper
import it.unisannio.ding.ids.wedroid.app.view.adapter.BoardsListAdapter import it.unisannio.ding.ids.wedroid.app.view.adapter.BoardsListAdapter
import it.unisannio.ding.ids.wedroid.app.viewmodel.BoardsListViewModel import it.unisannio.ding.ids.wedroid.app.viewmodel.BoardsListViewModel
import it.unisannio.ding.ids.wedroid.wrapper.entity.BoardBackgroundColor
import kotlinx.android.synthetic.main.activity_boards_lists.* import kotlinx.android.synthetic.main.activity_boards_lists.*
import kotlinx.android.synthetic.main.content_boards_lists.* import kotlinx.android.synthetic.main.content_boards_lists.*
@ -25,12 +26,12 @@ class BoardsListsActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_boards_lists) setContentView(R.layout.activity_boards_lists)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
val reader: PreferenceReader = SharedPreferenceHelper(this) val reader: PreferenceReader = SharedPreferenceHelper(this)
if (reader.token == "") if (reader.token == "")
startActivityForResult( startActivityForResult(
Intent(this, LoginActivity::class.java), Intent(this, LoginActivity::class.java),
LoginActivity.LOGIN_REQUEST_CODE LOGIN_CODE
) )
else initializeUi() else initializeUi()
} }
@ -42,19 +43,21 @@ class BoardsListsActivity : AppCompatActivity() {
boardList.adapter = adapter boardList.adapter = adapter
boardList.layoutManager = LinearLayoutManager(this) boardList.layoutManager = LinearLayoutManager(this)
swipeLeftToDelete()
pullToRefresh.setColorSchemeColors(getColor(R.color.colorAccent))
viewModel.allBoards.observe(this, Observer { viewModel.allBoards.observe(this, Observer {
it.let { adapter.setBoards(it) } it.let { adapter.setBoards(it) }
pullToRefresh.isRefreshing = false pullToRefresh.isRefreshing = false
}) })
swipeLeftToDelete()
fab.setOnClickListener { fab.setOnClickListener {
viewModel.insertBoard("New board") startActivityForResult(
Intent(this, NewBoardActivity::class.java),
NEW_BOARD_CODE
)
} }
pullToRefresh.setColorSchemeColors(getColor(R.color.colorAccent))
pullToRefresh.setOnRefreshListener { pullToRefresh.setOnRefreshListener {
viewModel.refresh() viewModel.refresh()
} }
@ -85,15 +88,42 @@ class BoardsListsActivity : AppCompatActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
when (requestCode) { when (requestCode) {
LoginActivity.LOGIN_REQUEST_CODE -> { LOGIN_CODE -> onLoginResult(resultCode)
when (resultCode) { NEW_BOARD_CODE -> if (data != null) onAddBoardResult(resultCode, data)
LoginActivity.LOGIN_ERROR -> finish()
LoginActivity.LOGIN_OK -> initializeUi()
else -> finish()
}
}
else -> finish() else -> finish()
} }
} }
private fun onLoginResult(resultCode: Int) {
when (resultCode) {
LoginActivity.LOGIN_OK -> initializeUi()
LoginActivity.LOGIN_ERROR -> finish()
else -> finish()
}
}
private fun onAddBoardResult(resultCode: Int, data: Intent) {
if (resultCode != NewBoardActivity.RESULT_OK) {
Toast.makeText(this, R.string.on_add_new_board_error, Toast.LENGTH_LONG)
.show()
return
}
val title = data.getStringExtra(NewBoardActivity.BOARD_NAME)
if (title == null) {
Toast.makeText(this, R.string.on_null_new_board_name, Toast.LENGTH_LONG)
.show()
return
}
val isPrivate = data.getBooleanExtra(NewBoardActivity.BOARD_PRIVATE, true)
viewModel.insertBoard(title, isPrivate, BoardBackgroundColor.LIMEGREEN) //TODO
}
companion object {
const val NEW_BOARD_CODE = 17
const val LOGIN_CODE = 19
}
} }

View File

@ -2,7 +2,6 @@ package it.unisannio.ding.ids.wedroid.app.view
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import it.unisannio.ding.ids.wedroid.app.R import it.unisannio.ding.ids.wedroid.app.R
@ -88,7 +87,6 @@ class LoginActivity : AppCompatActivity() {
} }
companion object { companion object {
const val LOGIN_REQUEST_CODE = 13
const val LOGIN_OK = 0 const val LOGIN_OK = 0
const val LOGIN_ERROR = 1 const val LOGIN_ERROR = 1
} }

View File

@ -0,0 +1,45 @@
package it.unisannio.ding.ids.wedroid.app.view;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.view.View;
import android.widget.EditText;
import android.widget.Switch;
import it.unisannio.ding.ids.wedroid.app.R;
public class NewBoardActivity extends AppCompatActivity {
private EditText boardName;
private Switch isPrivate;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_new_board);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
boardName = findViewById(R.id.newBoardName);
isPrivate = findViewById(R.id.newBoardPermission);
}
public void onDone(View v) {
Intent data = new Intent();
data.putExtra(BOARD_NAME, boardName.getText().toString());
data.putExtra(BOARD_PRIVATE, isPrivate.isChecked());
setResult(RESULT_OK, data);
finish();
}
public void onCancel(View v) {
finish();
}
public static final int RESULT_OK = 17;
public static final String BOARD_NAME = "BOARD_NAME";
public static final String BOARD_PRIVATE = "BOARD_PRIVATE";
}

View File

@ -3,7 +3,6 @@ package it.unisannio.ding.ids.wedroid.app.viewmodel;
import android.app.Application; import android.app.Application;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.app.SharedElementCallback;
import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
@ -15,6 +14,7 @@ import it.unisannio.ding.ids.wedroid.app.data.repository.BoardRepository;
import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader; import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader;
import it.unisannio.ding.ids.wedroid.app.util.ServicesFactory; import it.unisannio.ding.ids.wedroid.app.util.ServicesFactory;
import it.unisannio.ding.ids.wedroid.app.util.SharedPreferenceHelper; import it.unisannio.ding.ids.wedroid.app.util.SharedPreferenceHelper;
import it.unisannio.ding.ids.wedroid.wrapper.entity.BoardBackgroundColor;
public class BoardsListViewModel extends AndroidViewModel { public class BoardsListViewModel extends AndroidViewModel {
private BoardRepository repository; private BoardRepository repository;
@ -36,12 +36,15 @@ public class BoardsListViewModel extends AndroidViewModel {
return allBoards; return allBoards;
} }
public void insertBoard(String title) { public void insertBoard(String title, boolean isPrivate, BoardBackgroundColor color) {
repository.insertBoard(title); repository.insertBoard(title, isPrivate, color);
} }
public void deleteBoard(int position) { public void deleteBoard(int position) {
repository.deleteBoard(allBoards.getValue().get(position).getId()); List<Board> boards = allBoards.getValue();
if (boards != null)
repository.deleteBoard(boards.get(position).getId());
} }
public void refresh() { public void refresh() {

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.NewBoardActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/content_new_board" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".view.NewBoardActivity"
tools:showIn="@layout/activity_new_board">
<EditText
android:id="@+id/newBoardName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:hint="@string/new_board_name_field"
android:inputType="textPersonName"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.25"
android:importantForAutofill="no" />
<Switch
android:id="@+id/newBoardPermission"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/new_board_switch"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/newBoardName"
app:layout_constraintStart_toStartOf="@+id/newBoardName"
app:layout_constraintTop_toBottomOf="@+id/newBoardName"
app:layout_constraintVertical_bias="0.1" />
<Button
android:id="@+id/newBoardDone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onDone"
android:text="@string/new_board_done_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/newBoardPermission"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/newBoardPermission"
app:layout_constraintVertical_bias="0.40" />
<Button
android:id="@+id/newBoardCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onCancel"
android:text="@string/new_board_cancel_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/newBoardPermission"
app:layout_constraintTop_toBottomOf="@+id/newBoardPermission"
app:layout_constraintVertical_bias="0.40" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,5 +1,11 @@
<resources> <resources>
<string name="app_name">wedroid</string> <string name="app_name">wedroid</string>
<string name="title_activity_boards_list">BoardsListActivity</string> <string name="title_activity_boards_lists">BoardsListActivity</string>
<string name="title_activity_boards_lists">Boards</string> <string name="title_activity_new_board">NewBoardActivity</string>
<string name="new_board_name_field">Board name</string>
<string name="new_board_switch">Private</string>
<string name="new_board_cancel_button">Cancel</string>
<string name="new_board_done_button">Done</string>
<string name="on_null_new_board_name">There was a problem with the name of the new board</string>
<string name="on_add_new_board_error">It was not possible to add a new board</string>
</resources> </resources>

View File

@ -0,0 +1,13 @@
package it.unisannio.ding.ids.wedroid.app
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.full.declaredFunctions
import kotlin.reflect.jvm.isAccessible
fun getPrivateFun(name: String, kClass: KClass<*>): KFunction<*>? {
return kClass.declaredFunctions
.find { it.name == name }
.also { it?.isAccessible = true }
}

View File

@ -0,0 +1,318 @@
package it.unisannio.ding.ids.wedroid.app.data.repository
import android.util.Log
import androidx.lifecycle.LiveData
import io.mockk.*
import it.unisannio.ding.ids.wedroid.app.data.dao.BoardDao
import it.unisannio.ding.ids.wedroid.app.data.entity.Board
import it.unisannio.ding.ids.wedroid.app.getPrivateFun
import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader
import it.unisannio.ding.ids.wedroid.wrapper.api.BoardService
import junit.framework.TestCase.*
import kotlinx.coroutines.*
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Test
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import kotlin.reflect.full.callSuspend
class BoardRepositoryTest {
private val reader = mockk<PreferenceReader>()
private val webServer = MockWebServer()
private lateinit var service: BoardService
@Before
fun setUp() {
webServer.start()
service = Retrofit.Builder()
.baseUrl(webServer.url("/"))
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(BoardService::class.java)
mockkStatic(Log::class)
every { reader.userId } returns "user id"
every { Log.e(any(), any()) } returns 0
}
@After
fun teardown() {
webServer.close()
}
@Test
fun addNewBoardsToDb() {
val dao = mockk<BoardDao>()
var count = 0
coEvery {
dao.insert(any())
} answers {
count++
}
val addNewBoardToDb = getPrivateFun(
"addNewBoardToDb", BoardRepository::class
)
val repository = BoardRepository(
dao, service, reader
)
val board0 = Board("id0", "title0")
val board1 = Board("id1", "title1")
val board2 = Board("id2", "title2")
runBlocking {
addNewBoardToDb?.callSuspend(
repository,
listOf(board0, board1, board2)
)
}
coVerifyAll {
dao.insert(board0)
dao.insert(board1)
dao.insert(board2)
}
assertEquals(3, count)
}
@Test
fun insertBoardCallbackSuccess() {
val dao = mockk<BoardDao>()
val response =
mockk<Response<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>()
val board = mockk<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>()
every { response.isSuccessful } returns true
every { response.body() } returns board
every { board.id } returns "id"
coEvery { dao.insert(any()) } answers {}
val insertBoard = getPrivateFun(
"insertBoardCallback", BoardRepository::class
)
val repository = BoardRepository(
dao, service, reader
)
runBlocking {
insertBoard?.callSuspend(
repository,
response,
"title"
)
}
coVerify { dao.insert(Board("id", "title")) }
}
@Test
fun insertBoardCallbackEmptyBody() {
val dao = mockk<BoardDao>()
val response =
mockk<Response<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>()
every { response.isSuccessful } returns true
every { response.body() } returns null
val insertBoard = getPrivateFun(
"insertBoardCallback", BoardRepository::class
)
val repository = BoardRepository(
dao, service, reader
)
runBlocking {
insertBoard?.callSuspend(
repository,
response,
"title"
)
}
verify { Log.e("RETROFIT", "empty body") }
}
@Test
fun insertBoardCallbackError() {
val dao = mockk<BoardDao>()
val response =
mockk<Response<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>()
every { response.isSuccessful } returns false
every { response.code() } returns 400
every { response.message() } returns "Error"
val insertBoard = getPrivateFun(
"insertBoardCallback", BoardRepository::class
)
val repository = BoardRepository(
dao, service, reader
)
runBlocking {
insertBoard?.callSuspend(
repository,
response,
"title"
)
}
verify { Log.e("RETROFIT", "400 Error") }
}
@Test
fun deleteBoardCallbackSuccess() {
val dao = mockk<BoardDao>()
val response =
mockk<Response<Void>>()
every { response.isSuccessful } returns true
coEvery { dao.delete(any()) } answers {}
val deleteBoard = getPrivateFun(
"deleteBoardCallback", BoardRepository::class
)
val repository = BoardRepository(
dao, service, reader
)
runBlocking {
deleteBoard?.callSuspend(
repository,
response,
"id"
)
}
coVerify { dao.delete(Board("id")) }
}
@Test
fun deleteBoardCallbackError() {
val dao = mockk<BoardDao>()
val response =
mockk<Response<Void>>()
every { response.isSuccessful } returns false
every { response.code() } returns 400
every { response.message() } returns "Error"
val deleteBoard = getPrivateFun(
"deleteBoardCallback", BoardRepository::class
)
val repository = BoardRepository(
dao, service, reader
)
runBlocking {
deleteBoard?.callSuspend(
repository,
response,
"id"
)
}
verify { Log.e("RETROFIT", "400 Error") }
}
@Test
fun deleteOldBoardFromDb() {
val dao = mockk<BoardDao>()
val dbBoards = mockk<LiveData<List<Board>>>()
val board0 = Board("id0", "title0")
val board1 = Board("id1", "title1")
val board2 = Board("id2", "title2")
val board3 = Board("id2", "title3")
every { dbBoards.value } returns listOf(
board0, board1, board2, board3
)
coEvery { dao.getAllBoard() } returns dbBoards
coEvery { dao.delete(any()) } answers {}
val removeOldBoards = getPrivateFun(
"removeOldBoardsFromDb", BoardRepository::class
)
val repository = BoardRepository(
dao, service, reader
)
runBlocking {
removeOldBoards?.callSuspend(
repository,
listOf(
board0, board1, board3
)
)
}
coVerify { dao.delete(board2) }
}
@Test
fun synchronizeCallbackError() {
val dao = mockk<BoardDao>()
val response =
mockk<Response<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>>()
every { response.isSuccessful } returns false
every { response.code() } returns 400
every { response.message() } returns "Error"
val synchronize = getPrivateFun(
"synchronizeCallback", BoardRepository::class
)
val repository = BoardRepository(
dao, service, reader
)
runBlocking {
synchronize?.callSuspend(
repository,
response
)
}
verify { Log.e("RETROFIT", "400 Error") }
}
@Test
fun synchronizeCallbackEmptyBody() {
val dao = mockk<BoardDao>()
val response =
mockk<Response<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>>()
every { response.isSuccessful } returns true
every { response.body() } returns null
val synchronize = getPrivateFun(
"synchronizeCallback", BoardRepository::class
)
val repository = BoardRepository(
dao, service, reader
)
runBlocking {
synchronize?.callSuspend(
repository,
response
)
}
}
}