Skip to content
Snippets Groups Projects
Unverified Commit c5f53e86 authored by Chilja Gossow's avatar Chilja Gossow Committed by GitHub
Browse files

Vaccination QR code scan screen, error handling, navigation (EXPOSUREAPP-6726) (#3124)


* extractor

* decoding

* decoding, error handling

* clean up

* klint

* detekt

* simplify condition

* comment failing unit test

* unit test

* change lib

* Unit tests for RA/PCRCoronaTest.isFinal

* Add license text.

* add header

* clean up

* scan screen, error handling, navigation

* clean up

* klint detekt

* merge

* move strings

* comments

* klint

* revert version change

* fix fun name

* fix merge conflicts

* show dialog

* add navigation

* fix error dialog

* fix error dialog

* fix error dialog

* fix error dialog

* simplify

* merge main

* fix package

* review comments

* circular progress spinner

* fix test

Co-authored-by: default avatarMatthias Urhahn <matthias.urhahn@sap.com>
parent 9749838d
No related branches found
No related tags found
No related merge requests found
......@@ -76,6 +76,9 @@
<action
android:id="@+id/action_mainFragment_to_submissionTestResultKeysSharedFragment"
app:destination="@id/submissionTestResultKeysSharedFragment" />
<action
android:id="@+id/action_mainFragment_to_vaccinationNavGraph"
app:destination="@id/vaccination_nav_graph" />
</fragment>
<fragment
......@@ -809,4 +812,5 @@
android:name="testType"
app:argType="de.rki.coronawarnapp.coronatest.type.CoronaTest$Type" />
</fragment>
</navigation>
......@@ -59,7 +59,7 @@
android:id="@+id/scanCheckInQrCodeFragment"
android:name="de.rki.coronawarnapp.ui.presencetracing.attendee.scan.ScanCheckInQrCodeFragment"
android:label="ScanCheckInQrCodeFragment"
tools:layout="@layout/fragment_scan_check_in_qr_code" />
tools:layout="@layout/fragment_scan_qr_code" />
<fragment
android:id="@+id/checkInsFragment"
......@@ -97,4 +97,4 @@
app:destination="@id/checkInOnboardingFragment" />
</fragment>
</navigation>
\ No newline at end of file
</navigation>
......@@ -3,7 +3,19 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/vaccination_nav_graph"
app:startDestination="@id/vaccinationDetailsFragment">
app:startDestination="@id/vaccinationQrCodeScanFragment">
<fragment
android:id="@+id/vaccinationQrCodeScanFragment"
android:name="de.rki.coronawarnapp.vaccination.ui.scan.VaccinationQrCodeScanFragment"
android:label="VaccinationQrCodeScanFragment"
tools:layout="@layout/fragment_scan_qr_code">
<action
android:id="@+id/action_vaccinationQrCodeScanFragment_to_vaccinationDetailsFragment"
app:destination="@id/vaccinationDetailsFragment"
app:popUpTo="@id/vaccinationQrCodeScanFragment"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/vaccinationListFragment"
......@@ -28,4 +40,4 @@
android:name="vaccinationCertificateId"
app:argType="string" />
</fragment>
</navigation>
\ No newline at end of file
</navigation>
......@@ -70,4 +70,15 @@
<string name="vaccination_card_status_vaccination_name">SARS-CoV-2 Impfschutz</string>
<!-- XTXT: Homescreen vaccination status card vaccination status label -->
<string name="vaccination_card_status_vaccination_incomplete">Unvollständiger Impfschutz</string>
</resources>
\ No newline at end of file
<!-- XTXT: Vaccination QR code scan error message-->
<string name="error_vc_invalid">Dieser QR-Code ist kein gültiges Impfzertifikat.</string>
<!-- XTXT: Vaccination QR code scan error message-->
<string name="error_vc_not_yet_supported">Dieses Impfzertifikat wird in Ihrer App-Version noch nicht unterstützt. Bitte aktualisieren Sie Ihre App oder wenden Sie sich an die technische Hotline unter „App-Informationen“.</string>
<!-- XTXT: Vaccination QR code scan error message-->
<string name="error_vc_scan_again">Das Impfzertifikat konnte nicht auf Ihrem Smartphone gespeichert werden. Bitte versuchen Sie es später noch einmal oder wenden Sie sich an die technische Hotline unter „App-Informationen“.</string>
<!-- XTXT: Vaccination QR code scan error message-->
<string name="error_vc_already_registered">Das Impfzertifikat ist bereits in Ihrer App registriert.</string>
<!-- XTXT: Vaccination QR code scan error message-->
<string name="error_vc_different_person">Die persönlichen Daten dieses Impfzertifikats stimmen nicht mit denen der bereits registrierten Zertifikate überein. Sie können in der App nur Zertifikate einer Person registrieren.</string>
</resources>
......@@ -70,4 +70,15 @@
<string name="vaccination_card_status_vaccination_name">SARS-CoV-2 Impfschutz</string>
<!-- XTXT: Homescreen card vaccination status label -->
<string name="vaccination_card_status_vaccination_incomplete">Unvollständiger Impfschutz</string>
</resources>
\ No newline at end of file
<!-- XTXT: Vaccination QR code scan error message-->
<string name="error_vc_invalid"></string>
<!-- XTXT: Vaccination QR code scan error message-->
<string name="error_vc_not_yet_supported"></string>
<!-- XTXT: Vaccination QR code scan error message-->
<string name="error_vc_scan_again"></string>
<!-- XTXT: Vaccination QR code scan error message-->
<string name="error_vc_already_registered"></string>
<!-- XTXT: Vaccination QR code scan error message-->
<string name="error_vc_different_person"></string>
</resources>
package de.rki.coronawarnapp.vaccination.core.qrcode
import com.google.gson.Gson
import de.rki.coronawarnapp.vaccination.core.qrcode.InvalidHealthCertificateException.ErrorCode.HC_BASE45_DECODING_FAILED
import de.rki.coronawarnapp.vaccination.core.qrcode.InvalidHealthCertificateException.ErrorCode.HC_CBOR_DECODING_FAILED
import de.rki.coronawarnapp.vaccination.core.qrcode.InvalidHealthCertificateException.ErrorCode.HC_ZLIB_DECOMPRESSION_FAILED
import de.rki.coronawarnapp.vaccination.core.qrcode.InvalidHealthCertificateException.ErrorCode.VC_HC_CWT_NO_ISS
import de.rki.coronawarnapp.vaccination.core.qrcode.InvalidHealthCertificateException.ErrorCode.VC_NO_VACCINATION_ENTRY
import de.rki.coronawarnapp.vaccination.decoder.Base45Decoder
import de.rki.coronawarnapp.vaccination.decoder.ZLIBDecompressor
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.shouldBe
import org.joda.time.Instant
import org.joda.time.LocalDate
import org.junit.jupiter.api.Test
import testhelpers.BaseTest
......@@ -16,7 +19,7 @@ class VaccinationQRCodeExtractorTest : BaseTest() {
private val base45Decoder = Base45Decoder()
private val ZLIBDecompressor = ZLIBDecompressor()
private val healthCertificateCOSEDecoder = HealthCertificateCOSEDecoder()
private val vaccinationCertificateV1Decoder = VaccinationCertificateV1Parser()
private val vaccinationCertificateV1Decoder = VaccinationCertificateV1Parser(Gson())
private val extractor = VaccinationQRCodeExtractor(
base45Decoder,
......@@ -36,10 +39,47 @@ class VaccinationQRCodeExtractorTest : BaseTest() {
}
@Test
fun `valid encoding but not a health certificate fails with HC_CBOR_DECODING_FAILED`() {
fun `happy path extraction with data`() {
val qrCode = extractor.extract(VaccinationQrCodeTestData.validVaccinationQrCode3)
with(qrCode.parsedData.header) {
issuer shouldBe "AT"
issuedAt shouldBe Instant.ofEpochSecond(1620392021)
expiresAt shouldBe Instant.ofEpochSecond(1620564821)
}
with(qrCode.parsedData.vaccinationCertificate) {
with(nameData) {
familyName shouldBe "Musterfrau-Gößinger"
familyNameStandardized shouldBe "MUSTERFRAU<GOESSINGER"
givenName shouldBe "Gabriele"
givenNameStandardized shouldBe "GABRIELE"
}
dob shouldBe "1998-02-26"
dateOfBirth shouldBe LocalDate.parse("1998-02-26")
version shouldBe "1.0.0"
with(vaccinationDatas[0]) {
uniqueCertificateIdentifier shouldBe "urn:uvci:01:AT:10807843F94AEE0EE5093FBC254BD813P"
countryOfVaccination shouldBe "AT"
doseNumber shouldBe 1
dt shouldBe "2021-02-18"
certificateIssuer shouldBe "BMSGPK Austria"
marketAuthorizationHolderId shouldBe "ORG-100030215"
medicalProductId shouldBe "EU/1/20/1528"
totalSeriesOfDoses shouldBe 2
targetId shouldBe "840539006"
vaccineId shouldBe "1119305005"
vaccinatedAt shouldBe LocalDate.parse("2021-02-18")
}
}
}
@Test
fun `valid encoding but not a health certificate fails with VC_HC_CWT_NO_ISS`() {
shouldThrow<InvalidHealthCertificateException> {
extractor.extract(VaccinationQrCodeTestData.validEncoded)
}.errorCode shouldBe HC_CBOR_DECODING_FAILED
}.errorCode shouldBe VC_HC_CWT_NO_ISS
}
@Test
......
......@@ -5,4 +5,5 @@ public class VaccinationQrCodeTestData {
static public String validVaccinationQrCode2 = "HC1:NCFOXN%TS3DHZN4HAF*PQFKKGTNA.Q/R8WRU2FC6L9N*CH PC.IU:N AJPJPC%OQHIZC4.OI1RM8ZA.A53XHMKN4NN3F85QNCY0O%0VZ001HOC9JU0D0HT0HO1PM:K$$09B9LW4T*8+DC%H0PZBITH$*SBAKYE9*FJTJAHD4UDADPSDJIM4KF/B0C2SFIH:9$GCQOS62PR6WPHN6D7LLK*2HG%89UV-0LZ 2ZJJ4FF86O:HO73SM1IO-O.Z80GHS-O:S9UZ4+FJE 4Y3LL/II 07LPMIH-O9XZQSH9R$FXQGDVBK*RZP3:*DG1W7SGT$7S%RMSG2UQYI9*FGCPAXRQ3E2N+E .1:L7O:7X/5Q+MSA7G6MBYO+JQLHP71RJW63X7VUONC6V35HW6SZ6FT5D75W9AV88E34+V4YC5/HQWOQ6$S4N4N31SHPO3Q0E447H9VAK:6.5G$N3ZF7W2SBJT7QG+8UJII3MACIBG2U76MGX3$YB.S7PIJRVOBTN6DTEUIOS7ZKJJEL%.B PT2LO36KT8SP50M/O$4";
static public String validEncoded = "6BFB 9B8OYK3DR3D92BSQAQAHSOMEQ3%1GEVQT4H4O8G3.13G$H6+DH.157SWEV21SD7F2OPY1O-9LRFG0NGCUEPS5LLKJ:1CEJTLA2SADI887A/P3UHL20FTA9ZTRPSVUXO19LEZBQF3VJE$77D5FFC91ZFKCPP%90VS09P2QDQBCMY7-AE0/RW1R:ICP76XRS5UGC82WDNRJ9R7SX331MI9C7WNE5ZL1795NTA/P-35.N65O65ZQ8SU2:KY:C9K9PKD6+K%DI$YQ-9A:CKZ+5HPQNIF7N3K UEU6GEKHCO03MC%QN+LN+C5TTB1B94EC$38QC5O5DP262N:X7JYR/XH/A8%-1KZFTODRY3I 859G-IS9TMY4JM21TAV$N2NK3%BW8K7GI6%O8DUKUT036EF$8:32RBK*0IHJISK5SLTT21KYE7 U/316$I08A/XBU4IZYAGD3UVOJQI2YH3JMXHS1IPE%FOJN$HOV%B3FWCDCP65/%RKP2W2M4A9X7GETNASOXZ0Q/Q5LUNMJ QH+-2:4FW$33+4 +AY7GV-15/717GXY4H4O.:RM/USWV70PV8NGL5XP15NQ3K217GC:1WQEJNBK1RU6J.4K9/J%VQOHA+EW I0YMQ 0";
static public String certificateMissing = "HC1:NCFNA0%00FFWTWGVLKJ99K83X4C8DTTMMX*4P8B3XK2F3$8JVJG2F3$%IQJG/IC6TAY50.FK6ZK6:ETPCBEC8ZKW.CNWE.Y92OAGY82+8UB8-R7/0A1OA1C9K09UIAW.CE$E7%E7WE KEVKER EB39W4N*6K3/D5$CMPCG/DA8DBB85IAAY8WY8I3DA8D0EC*KE: CZ CO/EZKEZ96446C56GVC*JC1A6NA73W5KF6TF627BSKL*8F.MLCM6$-I99MG$8THRJSCJVM/*V:0EY1QU 77*D9KR$SKIP5S-I2-RA1CC06+CHPYQX96*SUF3WZ36NM3XPK1P8.MAFZ6SHB";
static public String validVaccinationQrCode3 = "HC1:NCFOXN%TS3DH3ZSUZK+.V0ETD%65NL-AH%TAIOOW%I-1W0658WA/UAN9AAT4V22F/8X*G3M9JUPY0BX/KR96R/S09T./0LWTKD33236J3TA3M*4VV2 73-E3ND3DAJ-43%*48YIB73A*G3W19UEBY5:PI0EGSP4*2D$43B+2SEB7:I/2DY73CIBC:G 7376BXBJBAJ UNFMJCRN0H3PQN*E33H3OA70M3FMJIJN523S+0B/S7-SN2H N37J3JFTULJ5CB3ZCIATULV:SNS8F-67N%21Q21$48X2+36D-I/2DBAJDAJCNB-43SZ4RZ4E%5B/9OK53:UCT16DEZIE IE9.M CVCT1+9V*QERU1MK93P5 U02Y9.G9/G9F:QQ28R3U6/V.*NT*QM.SY$N-P1S29 34S0BYBRC.UYS1U%O6QKN*Q5-QFRMLNKNM8JI0EUGP$I/XK$M8-L9KDI:ZH2E4EVS6O0FVAQNJT:EZ6Q%D0*T1.XSDYV0.VI2OKSNODA.BOD:C.OTXS02:M5OGJIF4LHJW7FFJ2NLGFL/EE%CJF+KM%V$AUS:H+NARLK IBMMG";
}
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