From aded3982b30a7daba5b4ee4cc7225fade6cc59c7 Mon Sep 17 00:00:00 2001 From: miladsi90 Date: Sat, 27 Jul 2019 17:55:44 +0430 Subject: [PATCH] Create code verification test , closed #21 --- .../java/de/netalic/peacock/MainActivity.kt | 3 +- .../exception/ActivationCodeIsNotValid.kt | 3 + .../data/exception/BadRequestException.kt | 3 + .../data/exception/InvalidDeviceName.kt | 3 + .../data/exception/InvalidUdidOrPhone.kt | 3 + .../peacock/data/exception/ServerException.kt | 3 + .../CodeVerificationFragment.kt | 54 ++-- .../CodeVerificationViewModel.kt | 17 +- .../netalic/peacock/ui/util/FragmentUtil.kt | 25 -- .../res/layout/fragment_codeverification.xml | 4 +- .../java/de/netalic/peacock/base/BaseTest.kt | 2 +- .../CodeVerificationViewModelTest.kt | 233 ++++++++++++++++++ .../de/netalic/peacock/util/LiveDataUtil.kt | 30 +++ 13 files changed, 318 insertions(+), 65 deletions(-) create mode 100644 app/src/main/java/de/netalic/peacock/data/exception/ActivationCodeIsNotValid.kt create mode 100644 app/src/main/java/de/netalic/peacock/data/exception/BadRequestException.kt create mode 100644 app/src/main/java/de/netalic/peacock/data/exception/InvalidDeviceName.kt create mode 100644 app/src/main/java/de/netalic/peacock/data/exception/InvalidUdidOrPhone.kt create mode 100644 app/src/main/java/de/netalic/peacock/data/exception/ServerException.kt delete mode 100644 app/src/main/java/de/netalic/peacock/ui/util/FragmentUtil.kt create mode 100644 app/src/test/java/de/netalic/peacock/ui/registration/codeverification/CodeVerificationViewModelTest.kt create mode 100644 app/src/test/java/de/netalic/peacock/util/LiveDataUtil.kt diff --git a/app/src/main/java/de/netalic/peacock/MainActivity.kt b/app/src/main/java/de/netalic/peacock/MainActivity.kt index 28a6d41..5dc56a9 100644 --- a/app/src/main/java/de/netalic/peacock/MainActivity.kt +++ b/app/src/main/java/de/netalic/peacock/MainActivity.kt @@ -10,6 +10,7 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - supportFragmentManager.beginTransaction().add(R.id.frameLayout_mainActivity_fragmentContainer, CodeVerificationFragment()).commit() + supportFragmentManager.beginTransaction().add(R.id.frameLayout_mainActivity_fragmentContainer, + CodeVerificationFragment()).commit() } } diff --git a/app/src/main/java/de/netalic/peacock/data/exception/ActivationCodeIsNotValid.kt b/app/src/main/java/de/netalic/peacock/data/exception/ActivationCodeIsNotValid.kt new file mode 100644 index 0000000..ac08047 --- /dev/null +++ b/app/src/main/java/de/netalic/peacock/data/exception/ActivationCodeIsNotValid.kt @@ -0,0 +1,3 @@ +package de.netalic.peacock.data.exception + +class ActivationCodeIsNotValid :Throwable() \ No newline at end of file diff --git a/app/src/main/java/de/netalic/peacock/data/exception/BadRequestException.kt b/app/src/main/java/de/netalic/peacock/data/exception/BadRequestException.kt new file mode 100644 index 0000000..44d350e --- /dev/null +++ b/app/src/main/java/de/netalic/peacock/data/exception/BadRequestException.kt @@ -0,0 +1,3 @@ +package de.netalic.peacock.data.exception + +class BadRequestException : Throwable() \ No newline at end of file diff --git a/app/src/main/java/de/netalic/peacock/data/exception/InvalidDeviceName.kt b/app/src/main/java/de/netalic/peacock/data/exception/InvalidDeviceName.kt new file mode 100644 index 0000000..78d134a --- /dev/null +++ b/app/src/main/java/de/netalic/peacock/data/exception/InvalidDeviceName.kt @@ -0,0 +1,3 @@ +package de.netalic.peacock.data.exception + +class InvalidDeviceName:Throwable() \ No newline at end of file diff --git a/app/src/main/java/de/netalic/peacock/data/exception/InvalidUdidOrPhone.kt b/app/src/main/java/de/netalic/peacock/data/exception/InvalidUdidOrPhone.kt new file mode 100644 index 0000000..782a124 --- /dev/null +++ b/app/src/main/java/de/netalic/peacock/data/exception/InvalidUdidOrPhone.kt @@ -0,0 +1,3 @@ +package de.netalic.peacock.data.exception + +class InvalidUdidOrPhone :Throwable() \ No newline at end of file diff --git a/app/src/main/java/de/netalic/peacock/data/exception/ServerException.kt b/app/src/main/java/de/netalic/peacock/data/exception/ServerException.kt new file mode 100644 index 0000000..43be6b4 --- /dev/null +++ b/app/src/main/java/de/netalic/peacock/data/exception/ServerException.kt @@ -0,0 +1,3 @@ +package de.netalic.peacock.data.exception + +class ServerException : Throwable() \ No newline at end of file diff --git a/app/src/main/java/de/netalic/peacock/ui/registeration/codeverification/CodeVerificationFragment.kt b/app/src/main/java/de/netalic/peacock/ui/registeration/codeverification/CodeVerificationFragment.kt index a2ce2ca..9c99290 100644 --- a/app/src/main/java/de/netalic/peacock/ui/registeration/codeverification/CodeVerificationFragment.kt +++ b/app/src/main/java/de/netalic/peacock/ui/registeration/codeverification/CodeVerificationFragment.kt @@ -9,8 +9,8 @@ import android.view.View import android.view.ViewGroup import androidx.core.content.ContextCompat import androidx.lifecycle.Observer -import com.alimuzaffar.lib.pin.PinEntryEditText import de.netalic.peacock.R + import de.netalic.peacock.data.model.Status import de.netalic.peacock.data.model.User import de.netalic.peacock.ui.base.BaseFragment @@ -18,6 +18,7 @@ import kotlinx.android.synthetic.main.fragment_codeverification.* import org.koin.android.viewmodel.ext.android.viewModel import java.util.concurrent.TimeUnit + class CodeVerificationFragment : BaseFragment() { @@ -26,12 +27,13 @@ class CodeVerificationFragment : BaseFragment() { var sTimer = 30000 } - private var mIsRunning:Boolean = false - private lateinit var mCountDownTimer:CountDownTimer + private var mIsRunning: Boolean = false + private lateinit var mCountDownTimer: CountDownTimer private lateinit var mView: View private val mCodeVerificationViewModel: CodeVerificationViewModel by viewModel() + private val mImageView by lazy { imageView_codeVerification_icon } private val mTextViewPhoneNumber by lazy { textView_codeVerification_phoneNumber } private val mPinEntryEditText by lazy { pinEntryEditText_codeVerification_setPin } private val mButton by lazy { button_codeVerification_continue } @@ -39,7 +41,7 @@ class CodeVerificationFragment : BaseFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - mView = inflater.inflate(R.layout.fragment_codeverification, container, false) + mView = inflater.inflate(de.netalic.peacock.R.layout.fragment_codeverification, container, false) return mView } @@ -55,9 +57,9 @@ class CodeVerificationFragment : BaseFragment() { override fun initUiListener() { - button_codeVerification_continue.setOnClickListener { bind() } + mButton.setOnClickListener { bind() } - textView_codeVerification_resendTime.setOnClickListener { + mTextViewTimer.setOnClickListener { if (!mIsRunning) { setTimer() @@ -66,13 +68,8 @@ class CodeVerificationFragment : BaseFragment() { } } - pinEntryEditText_codeVerification_setPin.setOnPinEnteredListener(PinEntryEditText.OnPinEnteredListener { - - - - }) - pinEntryEditText_codeVerification_setPin.addTextChangedListener(object :TextWatcher{ + mPinEntryEditText.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(p0: Editable?) { } @@ -84,11 +81,10 @@ class CodeVerificationFragment : BaseFragment() { override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { - if (p0!!.length==6){ + if (p0!!.length == 6) { enableButton() - } - else{ + } else { disableButton() } } @@ -127,13 +123,7 @@ class CodeVerificationFragment : BaseFragment() { }) } - private fun navigationToEmailVerification() { - - //navigate to Email verification fragment here - - } - - private fun setTimer(){ + private fun setTimer() { mCountDownTimer = object : CountDownTimer(sTimer.toLong(), 1000) { @@ -143,33 +133,33 @@ class CodeVerificationFragment : BaseFragment() { val secondTimer = TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) - TimeUnit.MINUTES.toSeconds( TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished) ) - textView_codeVerification_resendTime.text = String.format("%02d:%02d ", minuteTimer, secondTimer) - textView_codeVerification_resendTime.isEnabled = false - textView_codeVerification_resendTime.setTextColor(ContextCompat.getColor(requireContext(),R.color.colorText)) + mTextViewTimer.text = String.format("%02d:%02d ", minuteTimer, secondTimer) + mTextViewTimer.isEnabled = false + mTextViewTimer.setTextColor(ContextCompat.getColor(requireContext(), R.color.colorText)) } override fun onFinish() { - textView_codeVerification_resendTime.isEnabled = true + mTextViewTimer.isEnabled = true mIsRunning = false if (context != null) { - textView_codeVerification_resendTime.text=getString(R.string.codeVerification_resendCode) - textView_codeVerification_resendTime.setTextColor(ContextCompat.getColor(requireContext(),R.color.colorTertiaryDark)) + mTextViewTimer.text = getString(R.string.codeVerification_resendCode) + mTextViewTimer.setTextColor(ContextCompat.getColor(requireContext(), R.color.colorTertiaryDark)) } } }.start() } - private fun disableButton(){ + private fun disableButton() { - button_codeVerification_continue.isEnabled=false + mButton.isEnabled = false } - private fun enableButton(){ + private fun enableButton() { - button_codeVerification_continue.isEnabled=true + mButton.isEnabled = true } } \ No newline at end of file diff --git a/app/src/main/java/de/netalic/peacock/ui/registeration/codeverification/CodeVerificationViewModel.kt b/app/src/main/java/de/netalic/peacock/ui/registeration/codeverification/CodeVerificationViewModel.kt index 6b8036b..488e0bc 100644 --- a/app/src/main/java/de/netalic/peacock/ui/registeration/codeverification/CodeVerificationViewModel.kt +++ b/app/src/main/java/de/netalic/peacock/ui/registeration/codeverification/CodeVerificationViewModel.kt @@ -2,6 +2,10 @@ package de.netalic.peacock.ui.registeration.codeverification import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import de.netalic.peacock.data.exception.ActivationCodeIsNotValid +import de.netalic.peacock.data.exception.BadRequestException +import de.netalic.peacock.data.exception.InvalidDeviceName +import de.netalic.peacock.data.exception.InvalidUdidOrPhone import de.netalic.peacock.data.model.MyResponse import de.netalic.peacock.data.model.User import de.netalic.peacock.data.repository.UserRepository @@ -34,12 +38,17 @@ class CodeVerificationViewModel(private val userRepository: UserRepository) : Ba mBindResponseLiveData.value = MyResponse.success(it.body()!!) } 400 -> { - mBindResponseLiveData.value = MyResponse.success(it.body()!!) + mBindResponseLiveData.value = MyResponse.failed(BadRequestException()) } - 700 -> { - mBindResponseLiveData.value + 710 -> { + mBindResponseLiveData.value = MyResponse.failed(InvalidUdidOrPhone()) + } + 711 -> { + mBindResponseLiveData.value = MyResponse.failed(ActivationCodeIsNotValid()) + } + 716 -> { + mBindResponseLiveData.value = MyResponse.failed(InvalidDeviceName()) } - } }, diff --git a/app/src/main/java/de/netalic/peacock/ui/util/FragmentUtil.kt b/app/src/main/java/de/netalic/peacock/ui/util/FragmentUtil.kt deleted file mode 100644 index d7a75bd..0000000 --- a/app/src/main/java/de/netalic/peacock/ui/util/FragmentUtil.kt +++ /dev/null @@ -1,25 +0,0 @@ -package de.netalic.peacock.ui.util - -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager - -class FragmentUtil { - - companion object { - fun replaceFragmentWithFragment(fragmentManager: FragmentManager, fragment: Fragment, frameId: Int) { - - val fragmentTransaction = fragmentManager.beginTransaction() - fragmentTransaction.replace(frameId, fragment) - fragmentTransaction.commit() - } - - fun replaceFragmentWithFragmentWithAdd(fragmentManager: FragmentManager, fragment: Fragment, frameId: Int) { - - val fragmentTransaction = fragmentManager.beginTransaction() - fragmentTransaction.replace(frameId, fragment) - fragmentTransaction.addToBackStack(fragment.javaClass.name) - fragmentTransaction.commit() - } - } - -} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_codeverification.xml b/app/src/main/res/layout/fragment_codeverification.xml index cf29d8a..7f305e8 100644 --- a/app/src/main/res/layout/fragment_codeverification.xml +++ b/app/src/main/res/layout/fragment_codeverification.xml @@ -32,7 +32,7 @@ diff --git a/app/src/test/java/de/netalic/peacock/base/BaseTest.kt b/app/src/test/java/de/netalic/peacock/base/BaseTest.kt index 974191a..ac65177 100644 --- a/app/src/test/java/de/netalic/peacock/base/BaseTest.kt +++ b/app/src/test/java/de/netalic/peacock/base/BaseTest.kt @@ -25,7 +25,7 @@ open class BaseTest { } override fun createWorker(): Worker { - return ExecutorScheduler.ExecutorWorker(Executor { it.run() }) + return ExecutorScheduler.ExecutorWorker(Executor { it.run()}) } } RxJavaPlugins.setInitIoSchedulerHandler { scheduler -> immediate } diff --git a/app/src/test/java/de/netalic/peacock/ui/registration/codeverification/CodeVerificationViewModelTest.kt b/app/src/test/java/de/netalic/peacock/ui/registration/codeverification/CodeVerificationViewModelTest.kt new file mode 100644 index 0000000..9e639dd --- /dev/null +++ b/app/src/test/java/de/netalic/peacock/ui/registration/codeverification/CodeVerificationViewModelTest.kt @@ -0,0 +1,233 @@ +package de.netalic.peacock.ui.registration.codeverification + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import de.netalic.peacock.base.BaseTest +import de.netalic.peacock.data.exception.ActivationCodeIsNotValid +import de.netalic.peacock.data.exception.BadRequestException +import de.netalic.peacock.data.exception.InvalidDeviceName +import de.netalic.peacock.data.exception.InvalidUdidOrPhone +import de.netalic.peacock.data.model.Status +import de.netalic.peacock.data.model.User +import de.netalic.peacock.data.repository.UserRepository +import de.netalic.peacock.ui.registeration.codeverification.CodeVerificationViewModel +import de.netalic.peacock.util.LiveDataTestUtil +import io.reactivex.Single +import io.reactivex.subjects.PublishSubject +import okhttp3.MediaType +import okhttp3.ResponseBody +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations +import retrofit2.Response + +class CodeVerificationViewModelTest : BaseTest() { + + + @get:Rule + val instantExecutorRule = InstantTaskExecutorRule() + + @Mock + private lateinit var mUserRepository: UserRepository + + private lateinit var mCodeVerificationViewModel: CodeVerificationViewModel + + private val mResponseBody = ResponseBody.create( + MediaType.parse("text/plain"), "" + ) + private val mUser = User( + "+989211221122", + "D89707AC55BAED9E8F23B826FB2A28E96095A190", + "salimi", + "android", + "eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJSUzI1NiIsICJraWQiOiAiODk0NTkyNzQzMzlkMzNlZmNmNTE3MDc4NGM5Z" + + "GU1MjUzMjEyOWVmZiJ9.eyJpc3MiOiAiZmlyZWJhc2UtYWRtaW5zZGstaXp1MTNAYWxwaGEtZDY0ZTQuaWFtLmdzZXJ2aWNlYWN" + + "jb3VudC5jb20iLCAic3ViIjogImZpcmViYXNlLWFkbWluc2RrLWl6dTEzQGFscGhhLWQ2NGU0LmlhbS5nc2VydmljZWFjY291bn" + + "QuY29tIiwgImF1ZCI6ICJodHRwczovL2lkZW50aXR5dG9vbGtpdC5nb29nbGVhcGlzLmNvbS9nb29nbGUuaWRlbnRpdHkuaWRlb" + + "nRpdHl0b29sa2l0LnYxLklkZW50aXR5VG9vbGtpdCIsICJ1aWQiOiAiKzk4OTIxMTQ5OTMwMiIsICJpYXQiOiAxNTYzOTYwNDU3" + + "LCAiZXhwIjogMTU2Mzk2NDA1N30.HOUVBzwbmGwsglQHukGwrijlUuSZ241KdN2Eol3Gy80mmd4Kxoc58m3VhL71AWv3WS99eE7" + + "uz6xctl--yLPilhN3WJ_z2nxySqkhxiZ9OtaH_U8sTek63SJgfINeTFzJFpWHkT_DlQNPTVoH_AqbXjh0gZwdpVdMyoLmmuJf-W" + + "Iqx2y7BdwudCTiAqY_RoK7DdDwS8Jf28J-czpWi7Q4neUo1pC0WLi986u9n0mZcfIhWoVB_fV0A2-fWRV6yhT647sfHntC2eSg-" + + "OJZKO-MAyBsgKDIZm_ubX7m3LHD6rahpnUHtY8m33eJyD-EfZcKboRWalJkmje69abirvep1A", + "082016" + ) + + @Before + fun setup() { + + MockitoAnnotations.initMocks(this) + mCodeVerificationViewModel = CodeVerificationViewModel(mUserRepository) + + } + + @Test + fun binUser_showSuccess() { + + Mockito.`when`(mUserRepository.bind(mUser)).thenReturn(Single.just(Response.success(mResponseBody))) + mCodeVerificationViewModel.bind(mUser) + Mockito.verify(mUserRepository).bind(mUser) + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()).status, + Status.SUCCESS + ) + + Assert.assertEquals(LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()).data, mResponseBody) + Assert.assertNull(LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()).throwable) + } + + @Test + fun bindUser_showBadRequest() { + + val delayer = PublishSubject.create() + + val singleResponse = Single.just( + Response.error(400, mResponseBody) + ).delaySubscription(delayer) + + Mockito.`when`(mUserRepository.bind(mUser)).thenReturn(singleResponse) + + mCodeVerificationViewModel.bind(mUser) + + Mockito.verify(mUserRepository).bind(mUser) + + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()).status, + Status.LOADING + ) + + delayer.onComplete() + + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()).status, + Status.FAILED + ) + + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()) + .throwable!!::class.java, BadRequestException::class.java + ) + + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()).data, + null + ) + } + + @Test + fun bindUser_invalidUdidOrPhone() { + + val delayer = PublishSubject.create() + + val singleResponse = Single.just( + Response.error(710, mResponseBody) + ).delaySubscription(delayer) + + Mockito.`when`(mUserRepository.bind(mUser)).thenReturn(singleResponse) + + mCodeVerificationViewModel.bind(mUser) + + Mockito.verify(mUserRepository).bind(mUser) + + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()).status, + Status.LOADING + ) + + delayer.onComplete() + + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()).status, + Status.FAILED + ) + + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()) + .throwable!!::class.java, InvalidUdidOrPhone::class.java + ) + + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()).data, + null + ) + } + + @Test + fun bindUser_invalidDeviceName() { + + val delayer = PublishSubject.create() + + val singleResponse = Single.just( + Response.error(716, mResponseBody) + ).delaySubscription(delayer) + + Mockito.`when`(mUserRepository.bind(mUser)).thenReturn(singleResponse) + + mCodeVerificationViewModel.bind(mUser) + + Mockito.verify(mUserRepository).bind(mUser) + + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()).status, + Status.LOADING + ) + + delayer.onComplete() + + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()).status, + Status.FAILED + ) + + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()) + .throwable!!::class.java, InvalidDeviceName::class.java + ) + + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()).data, + null + ) + } + + @Test + fun bindUser_activationCodeIsNotValid() { + + val delayer = PublishSubject.create() + + val singleResponse = Single.just( + Response.error(711, mResponseBody) + ).delaySubscription(delayer) + + Mockito.`when`(mUserRepository.bind(mUser)).thenReturn(singleResponse) + + mCodeVerificationViewModel.bind(mUser) + + Mockito.verify(mUserRepository).bind(mUser) + + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()).status, + Status.LOADING + ) + + delayer.onComplete() + + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()).status, + Status.FAILED + ) + + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()) + .throwable!!::class.java, ActivationCodeIsNotValid::class.java + ) + + Assert.assertEquals( + LiveDataTestUtil.getValue(mCodeVerificationViewModel.getBindLiveData()).data, + null + ) + } + +} \ No newline at end of file diff --git a/app/src/test/java/de/netalic/peacock/util/LiveDataUtil.kt b/app/src/test/java/de/netalic/peacock/util/LiveDataUtil.kt new file mode 100644 index 0000000..cd0385e --- /dev/null +++ b/app/src/test/java/de/netalic/peacock/util/LiveDataUtil.kt @@ -0,0 +1,30 @@ +package de.netalic.peacock.util + + +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +object LiveDataTestUtil { + + /** + * Get the value from a LiveData object. We're waiting for LiveData to emit, for 2 seconds. + * Once we got a notification via onChanged, we stop observing. + */ + @Throws(InterruptedException::class) + fun getValue(liveData: LiveData): T { + val data = arrayOfNulls(1) + val latch = CountDownLatch(1) + val observer = object : Observer { + override fun onChanged(o: T?) { + data[0] = o + latch.countDown() + liveData.removeObserver(this) + } + } + liveData.observeForever(observer) + latch.await(2, TimeUnit.SECONDS) + return data[0] as T + } +} \ No newline at end of file