Skip to content
Snippets Groups Projects
Unverified Commit f8f9eaf5 authored by Juraj Kusnier's avatar Juraj Kusnier Committed by GitHub
Browse files

Return default or most recent cached poster (EXPOSUREAPP-6079) (#2741)


* QrCodePosterTemplateCache implementation

* update tests

* remove QrCodePosterTemplateCache

* rename tests

* Timber.e > Timber.d

Co-authored-by: default avatarLukas Lechner <lukas.lechner@sap.com>
Co-authored-by: default avatarharambasicluka <64483219+harambasicluka@users.noreply.github.com>
parent a268c5e2
No related branches found
No related tags found
No related merge requests found
Showing with 101 additions and 42 deletions
File added
1e972018100828aa63bc2559713cffa22d8db62f3ce56b3edbe72ad8cb7adb16
\ No newline at end of file
package de.rki.coronawarnapp.eventregistration.events.server.qrcodepostertemplate
import android.content.Context
import dagger.Reusable
import de.rki.coronawarnapp.util.di.AppContext
import javax.inject.Inject
@Reusable
class DefaultQrCodePosterTemplateSource @Inject constructor(@AppContext private val context: Context) {
fun getDefaultQrCodePosterTemplate() =
context.assets.open("default_qr_code_poster_template_android.bin").readBytes()
}
package de.rki.coronawarnapp.eventregistration.events.server.qrcodepostertemplate package de.rki.coronawarnapp.eventregistration.events.server.qrcodepostertemplate
import androidx.annotation.VisibleForTesting
import com.google.protobuf.InvalidProtocolBufferException import com.google.protobuf.InvalidProtocolBufferException
import de.rki.coronawarnapp.server.protocols.internal.pt.QrCodePosterTemplate import de.rki.coronawarnapp.server.protocols.internal.pt.QrCodePosterTemplate
import de.rki.coronawarnapp.util.ZipHelper.readIntoMap import de.rki.coronawarnapp.util.ZipHelper.readIntoMap
import de.rki.coronawarnapp.util.ZipHelper.unzip import de.rki.coronawarnapp.util.ZipHelper.unzip
import de.rki.coronawarnapp.util.security.SignatureValidation import de.rki.coronawarnapp.util.security.SignatureValidation
import retrofit2.HttpException
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
...@@ -13,48 +13,54 @@ import javax.inject.Singleton ...@@ -13,48 +13,54 @@ import javax.inject.Singleton
@Singleton @Singleton
class QrCodePosterTemplateServer @Inject constructor( class QrCodePosterTemplateServer @Inject constructor(
private val api: QrCodePosterTemplateApiV1, private val api: QrCodePosterTemplateApiV1,
private val defaultTemplateSource: DefaultQrCodePosterTemplateSource,
private val signatureValidation: SignatureValidation private val signatureValidation: SignatureValidation
) { ) {
suspend fun downloadQrCodePosterTemplate(): QrCodePosterTemplate.QRCodePosterTemplateAndroid { suspend fun downloadQrCodePosterTemplate(): QrCodePosterTemplate.QRCodePosterTemplateAndroid {
Timber.d("Start download of QR-Code poster template.") Timber.d("Start download of QR-Code poster template.")
val binaryTemplate = getTemplateFromApiOrCache()
val response = api.getQrCodePosterTemplate() return try {
Timber.d("Received: %s", response) QrCodePosterTemplate.QRCodePosterTemplateAndroid.parseFrom(binaryTemplate)
} catch (exception: InvalidProtocolBufferException) {
if (!response.isSuccessful) { throw QrCodePosterTemplateInvalidResponseException(
// TODO return cached or default response message = "QR Code poster template could not be parsed",
throw HttpException(response) cause = exception
} )
if (response.body() == null) {
throw IllegalStateException("Response is successful, but body is empty.")
} }
}
val fileMap = response.body()!!.byteStream().unzip().readIntoMap() @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
suspend fun getTemplateFromApiOrCache(): ByteArray {
val exportBinary = fileMap[EXPORT_BINARY_FILE_NAME] return try {
val exportSignature = fileMap[EXPORT_SIGNATURE_FILE_NAME] val response = api.getQrCodePosterTemplate()
if (response.body() == null) {
throw IllegalStateException("Response is successful, but body is empty.")
}
if (exportBinary == null || exportSignature == null) { val fileMap = response.body()!!.byteStream().unzip().readIntoMap()
throw QrCodePosterTemplateInvalidResponseException(message = "Unknown files: ${fileMap.keys}")
}
val hasValidSignature = signatureValidation.hasValidSignature( val exportBinary = fileMap[EXPORT_BINARY_FILE_NAME]
exportBinary, val exportSignature = fileMap[EXPORT_SIGNATURE_FILE_NAME]
SignatureValidation.parseTEKStyleSignature(exportSignature)
)
if (!hasValidSignature) { if (exportBinary == null || exportSignature == null) {
throw QrCodePosterTemplateInvalidResponseException(message = "Invalid Signature!") throw QrCodePosterTemplateInvalidResponseException(message = "Unknown files: ${fileMap.keys}")
} }
return try { val hasValidSignature = signatureValidation.hasValidSignature(
QrCodePosterTemplate.QRCodePosterTemplateAndroid.parseFrom(exportBinary) exportBinary,
} catch (exception: InvalidProtocolBufferException) { SignatureValidation.parseTEKStyleSignature(exportSignature)
throw QrCodePosterTemplateInvalidResponseException(
message = "QR Code poster template could not be parsed",
cause = exception
) )
if (!hasValidSignature) {
throw QrCodePosterTemplateInvalidResponseException(message = "Invalid Signature!")
}
exportBinary
} catch (exception: Exception) {
Timber.d(exception, "Response is not successful, trying to load template from cache")
defaultTemplateSource.getDefaultQrCodePosterTemplate()
} }
} }
......
...@@ -20,7 +20,7 @@ import testhelpers.EmptyApplication ...@@ -20,7 +20,7 @@ import testhelpers.EmptyApplication
@Config(sdk = [Build.VERSION_CODES.P], application = EmptyApplication::class) @Config(sdk = [Build.VERSION_CODES.P], application = EmptyApplication::class)
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
class DefaultAppConfigSanityCheck : BaseTest() { class DefaultAppConfigTest : BaseTest() {
private val configName = "default_app_config_android.bin" private val configName = "default_app_config_android.bin"
private val checkSumName = "default_app_config_android.sha256" private val checkSumName = "default_app_config_android.sha256"
......
package de.rki.coronawarnapp.eventregistration.events.server.qrcodepostertemplate
import android.content.Context
import android.os.Build
import androidx.test.core.app.ApplicationProvider
import de.rki.coronawarnapp.util.HashExtensions.toSHA256
import io.kotest.matchers.shouldBe
import io.mockk.MockKAnnotations
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import testhelpers.BaseTest
import testhelpers.EmptyApplication
@Config(sdk = [Build.VERSION_CODES.P], application = EmptyApplication::class)
@RunWith(RobolectricTestRunner::class)
class DefaultQrCodePosterTemplateTest : BaseTest() {
private val templateName = "default_qr_code_poster_template_android.bin"
private val checkSumName = "default_qr_code_poster_template_android.sha256"
@Before
fun setup() {
MockKAnnotations.init(this)
}
val context: Context
get() = ApplicationProvider.getApplicationContext()
@Test
fun `current default matches checksum`() {
val template = context.assets.open(templateName).readBytes()
val sha256 = context.assets.open(checkSumName).readBytes().toString(Charsets.UTF_8)
sha256 shouldBe "1e972018100828aa63bc2559713cffa22d8db62f3ce56b3edbe72ad8cb7adb16"
template.toSHA256() shouldBe sha256
}
}
...@@ -20,6 +20,7 @@ internal class QrCodePosterTemplateServerTest : BaseTest() { ...@@ -20,6 +20,7 @@ internal class QrCodePosterTemplateServerTest : BaseTest() {
@MockK lateinit var api: QrCodePosterTemplateApiV1 @MockK lateinit var api: QrCodePosterTemplateApiV1
@MockK lateinit var signatureValidation: SignatureValidation @MockK lateinit var signatureValidation: SignatureValidation
@MockK lateinit var defaultTemplateSource: DefaultQrCodePosterTemplateSource
/** /**
* Info: [QrCodePosterTemplateApiV1Test] is testing if the ETag is set correctly * Info: [QrCodePosterTemplateApiV1Test] is testing if the ETag is set correctly
...@@ -30,11 +31,13 @@ internal class QrCodePosterTemplateServerTest : BaseTest() { ...@@ -30,11 +31,13 @@ internal class QrCodePosterTemplateServerTest : BaseTest() {
MockKAnnotations.init(this) MockKAnnotations.init(this)
every { signatureValidation.hasValidSignature(any(), any()) } returns true every { signatureValidation.hasValidSignature(any(), any()) } returns true
every { defaultTemplateSource.getDefaultQrCodePosterTemplate() } returns "CACHE".toByteArray()
} }
private fun createInstance() = QrCodePosterTemplateServer( private fun createInstance() = QrCodePosterTemplateServer(
api = api, api = api,
signatureValidation = signatureValidation signatureValidation = signatureValidation,
defaultTemplateSource = defaultTemplateSource
) )
@Test @Test
...@@ -62,16 +65,14 @@ internal class QrCodePosterTemplateServerTest : BaseTest() { ...@@ -62,16 +65,14 @@ internal class QrCodePosterTemplateServerTest : BaseTest() {
} }
@Test @Test
fun `should throw exception if signature is invalid`() = runBlockingTest { fun `should fallback to cached or default template if signature is invalid`() = runBlockingTest {
every { signatureValidation.hasValidSignature(any(), any()) } returns false every { signatureValidation.hasValidSignature(any(), any()) } returns false
coEvery { coEvery {
api.getQrCodePosterTemplate() api.getQrCodePosterTemplate()
} returns Response.success(POSTER_BUNDLE.toResponseBody()) } returns Response.success(POSTER_BUNDLE.toResponseBody())
shouldThrow<QrCodePosterTemplateInvalidResponseException> { createInstance().getTemplateFromApiOrCache() shouldBe "CACHE".toByteArray()
createInstance().downloadQrCodePosterTemplate()
}
} }
@Test @Test
...@@ -86,13 +87,12 @@ internal class QrCodePosterTemplateServerTest : BaseTest() { ...@@ -86,13 +87,12 @@ internal class QrCodePosterTemplateServerTest : BaseTest() {
} }
@Test @Test
fun `should return default poster template when response is not successful`() = runBlockingTest { fun `should fallback to cached or default template when response is not successful`() = runBlockingTest {
// TODO coEvery {
} api.getQrCodePosterTemplate()
} returns Response.error(404, "ERROR".toResponseBody())
@Test createInstance().getTemplateFromApiOrCache() shouldBe "CACHE".toByteArray()
fun `should return latest cached template when response is not successful`() = runBlockingTest {
// TODO
} }
companion object { companion object {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment