diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt index 5eebce6b2d80266b23191aadf9bc2d6790a307bd..a6e180fa4c69bbb1d47f447603ee9412e26ef3e6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt @@ -332,9 +332,15 @@ class HomeFragmentViewModel @AssistedInject constructor( ) } ) - VaccinatedPerson.Status.IMMUNITY -> { - throw NotImplementedError() - } + // TODO wrong card, just placeholder + VaccinatedPerson.Status.IMMUNITY -> CompleteVaccinationHomeCard.Item( + vaccinatedPerson = vaccinatedPerson, + onClickAction = { + popupEvents.postValue( + HomeFragmentEvents.GoToVaccinationList(vaccinatedPerson.identifier.code) + ) + } + ) } add(card) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/VaccinatedPersonIdentifier.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/VaccinatedPersonIdentifier.kt index 0663df8d042146b08b0fbf14a14ed1c9d9d69da0..aea20ab9b2e412ec1f7862b0bb8f12f8d1c710e3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/VaccinatedPersonIdentifier.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/VaccinatedPersonIdentifier.kt @@ -1,11 +1,12 @@ package de.rki.coronawarnapp.vaccination.core import de.rki.coronawarnapp.util.HashExtensions.toSHA256 +import de.rki.coronawarnapp.vaccination.core.certificate.InvalidHealthCertificateException +import de.rki.coronawarnapp.vaccination.core.certificate.InvalidHealthCertificateException.ErrorCode import de.rki.coronawarnapp.vaccination.core.certificate.VaccinationDGCV1 import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateQRCode -import de.rki.coronawarnapp.vaccination.core.repository.errors.VaccinationDateOfBirthMissmatchException -import de.rki.coronawarnapp.vaccination.core.repository.errors.VaccinationNameMissmatchException import org.joda.time.LocalDate +import timber.log.Timber data class VaccinatedPersonIdentifier( val dateOfBirth: LocalDate, @@ -33,19 +34,16 @@ data class VaccinatedPersonIdentifier( fun requireMatch(other: VaccinatedPersonIdentifier) { if (lastNameStandardized != other.lastNameStandardized) { - throw VaccinationNameMissmatchException( - "Family name does not match, got ${other.lastNameStandardized}, expected $lastNameStandardized" - ) + Timber.d("Family name does not match, got ${other.lastNameStandardized}, expected $lastNameStandardized") + throw InvalidHealthCertificateException(ErrorCode.VC_NAME_MISMATCH) } if (firstNameStandardized != other.firstNameStandardized) { - throw VaccinationNameMissmatchException( - "Given name does not match, got ${other.firstNameStandardized}, expected $firstNameStandardized" - ) + Timber.d("Given name does not match, got ${other.firstNameStandardized}, expected $firstNameStandardized") + throw InvalidHealthCertificateException(ErrorCode.VC_NAME_MISMATCH) } if (dateOfBirth != other.dateOfBirth) { - throw VaccinationDateOfBirthMissmatchException( - "Date of birth does not match, got ${other.dateOfBirth}, expected $dateOfBirth" - ) + Timber.d("Date of birth does not match, got ${other.dateOfBirth}, expected $dateOfBirth") + throw InvalidHealthCertificateException(ErrorCode.VC_DOB_MISMATCH) } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/VaccinationRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/VaccinationRepository.kt index 1621f9eea96f84d4e1af2dcda9450efd73ab6fcc..ab3b35f4dfc2e3c48e4f5c94e586fcf66f7e6c6e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/VaccinationRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/VaccinationRepository.kt @@ -9,6 +9,8 @@ import de.rki.coronawarnapp.util.flow.combine import de.rki.coronawarnapp.vaccination.core.VaccinatedPerson import de.rki.coronawarnapp.vaccination.core.VaccinatedPersonIdentifier import de.rki.coronawarnapp.vaccination.core.VaccinationCertificate +import de.rki.coronawarnapp.vaccination.core.certificate.InvalidHealthCertificateException +import de.rki.coronawarnapp.vaccination.core.certificate.InvalidHealthCertificateException.ErrorCode import de.rki.coronawarnapp.vaccination.core.personIdentifier import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateQRCode import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationQRCodeExtractor @@ -24,7 +26,6 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.launch import kotlinx.coroutines.plus import timber.log.Timber import javax.inject.Inject @@ -93,13 +94,16 @@ class VaccinationRepository @Inject constructor( } } else { VaccinatedPerson( - data = VaccinatedPersonData( - vaccinations = emptySet() - ), + data = VaccinatedPersonData(), valueSet = null, ) } + if (originalPerson.data.vaccinations.any { it.certificateId == qrCode.uniqueCertificateIdentifier }) { + Timber.tag(TAG).e("Certificate is already registered: %s", qrCode.uniqueCertificateIdentifier) + throw InvalidHealthCertificateException(ErrorCode.VC_ALREADY_REGISTERED) + } + val newCertificate = qrCode.toVaccinationContainer( scannedAt = timeStamper.nowUTC, qrCodeExtractor = vaccinationQRCodeExtractor, @@ -154,25 +158,25 @@ class VaccinationRepository @Inject constructor( ) deletedVaccination = target.data.vaccinations.single { - it.certificateId != vaccinationCertificateId + it.certificateId == vaccinationCertificateId } - val newTarget = target.copy( - data = target.data.copy( - vaccinations = target.data.vaccinations.filter { - it.certificateId != vaccinationCertificateId - }.toSet() + val newTarget = if (target.data.vaccinations.size > 1) { + target.copy( + data = target.data.copy( + vaccinations = target.data.vaccinations.filter { it != deletedVaccination }.toSet() + ) ) - ) + } else { + Timber.tag(TAG).w("Person has no certificate after removal, removing person.") + null + } - this.map { - if (it != target) newTarget else it - }.toSet() + this.mapNotNull { if (it == target) newTarget else it }.toSet() } deletedVaccination?.let { - Timber.tag(TAG).i("Deleted vaccination was eligble for proof, refreshing: %s", deletedVaccination) - appScope.launch { refresh(it.personIdentifier) } + Timber.tag(TAG).i("Deleted vaccination certificate: %s", it.certificateId) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/errors/VaccinationDateOfBirthMissmatchException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/errors/VaccinationDateOfBirthMissmatchException.kt deleted file mode 100644 index bedc695d22726250f93bcb4273b84dc17fc21430..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/errors/VaccinationDateOfBirthMissmatchException.kt +++ /dev/null @@ -1,10 +0,0 @@ -package de.rki.coronawarnapp.vaccination.core.repository.errors - -import de.rki.coronawarnapp.vaccination.core.VaccinationException - -class VaccinationDateOfBirthMissmatchException( - message: String -) : VaccinationException( - message = message, - cause = null -) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/errors/VaccinationNameMissmatchException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/errors/VaccinationNameMissmatchException.kt deleted file mode 100644 index 3e27e652d6519f83fa9473c2a0e064c4b77e1411..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/errors/VaccinationNameMissmatchException.kt +++ /dev/null @@ -1,10 +0,0 @@ -package de.rki.coronawarnapp.vaccination.core.repository.errors - -import de.rki.coronawarnapp.vaccination.core.VaccinationException - -class VaccinationNameMissmatchException( - message: String -) : VaccinationException( - message = message, - cause = null -) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinatedPersonData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinatedPersonData.kt index d5fea3bef653c4aef0e5b1670869a0fcd2ad50c3..06978d4907f11613336a3c1e85929a4228fa8021 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinatedPersonData.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinatedPersonData.kt @@ -4,7 +4,7 @@ import com.google.gson.annotations.SerializedName import de.rki.coronawarnapp.vaccination.core.VaccinatedPersonIdentifier data class VaccinatedPersonData( - @SerializedName("vaccinationData") val vaccinations: Set<VaccinationContainer> + @SerializedName("vaccinationData") val vaccinations: Set<VaccinationContainer> = emptySet() ) { val identifier: VaccinatedPersonIdentifier get() = vaccinations.first().personIdentifier diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListVaccinationCardItemVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListVaccinationCardItemVH.kt index 455beedf1b9a02a3b57d8c21ba385dd460c23542..e2f158b36e8772b596fdcff645e870006048a408 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListVaccinationCardItemVH.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListVaccinationCardItemVH.kt @@ -57,7 +57,8 @@ class VaccinationListVaccinationCardItemVH( } } IMMUNITY -> { - throw NotImplementedError() + // TODO + R.drawable.ic_vaccination_complete_final } } vaccinationIcon.setImageResource(iconRes) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/VaccinatedPersonIdentifierTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/VaccinatedPersonIdentifierTest.kt index 686426f4001791be9b32b9a9b141282c45a22ed4..5822f0e6951db3d730211a1a609c0c3d8c5fc608 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/VaccinatedPersonIdentifierTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/VaccinatedPersonIdentifierTest.kt @@ -1,5 +1,9 @@ package de.rki.coronawarnapp.vaccination.core +import de.rki.coronawarnapp.vaccination.core.certificate.InvalidHealthCertificateException +import de.rki.coronawarnapp.vaccination.core.certificate.InvalidHealthCertificateException.ErrorCode +import io.kotest.assertions.throwables.shouldNotThrowAny +import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import org.joda.time.LocalDate import org.junit.jupiter.api.Test @@ -43,4 +47,23 @@ class VaccinatedPersonIdentifierTest : BaseTest() { person1.code shouldBe person2.code person1.codeSHA256 shouldBe person2.codeSHA256 } + + @Test + fun `required matching`() { + shouldNotThrowAny { + testPersonMaxData.requireMatch(testPersonMaxData) + } + + shouldThrow<InvalidHealthCertificateException> { + testPersonMaxData.requireMatch(testPersonMaxData.copy(firstNameStandardized = "nope")) + }.errorCode shouldBe ErrorCode.VC_NAME_MISMATCH + + shouldThrow<InvalidHealthCertificateException> { + testPersonMaxData.requireMatch(testPersonMaxData.copy(lastNameStandardized = "nope")) + }.errorCode shouldBe ErrorCode.VC_NAME_MISMATCH + + shouldThrow<InvalidHealthCertificateException> { + testPersonMaxData.requireMatch(testPersonMaxData.copy(dateOfBirth = LocalDate.parse("1900-12-31"))) + }.errorCode shouldBe ErrorCode.VC_DOB_MISMATCH + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/VaccinationTestComponent.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/VaccinationTestComponent.kt index 16b9d5cf00f6d1d5cb181993956c66a541d015cb..181970bd621adbbc1eb156126f6bd70dcbd01dd7 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/VaccinationTestComponent.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/VaccinationTestComponent.kt @@ -4,6 +4,7 @@ import dagger.Component import dagger.Module import de.rki.coronawarnapp.util.serialization.SerializationModule import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationQRCodeExtractorTest +import de.rki.coronawarnapp.vaccination.core.repository.VaccinationRepositoryTest import de.rki.coronawarnapp.vaccination.core.repository.storage.VaccinationContainerTest import de.rki.coronawarnapp.vaccination.core.repository.storage.VaccinationStorageTest import javax.inject.Singleton @@ -21,6 +22,7 @@ interface VaccinationTestComponent { fun inject(testClass: VaccinationContainerTest) fun inject(testClass: VaccinationQRCodeExtractorTest) fun inject(testClass: VaccinatedPersonTest) + fun inject(testClass: VaccinationRepositoryTest) @Component.Factory interface Factory { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/VaccinationTestData.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/VaccinationTestData.kt index ed7422a60be963c8e4ea221ec058229b00887c31..0c5117d63094019d089ac68493a1edd962fda0cf 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/VaccinationTestData.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/VaccinationTestData.kt @@ -1,6 +1,9 @@ package de.rki.coronawarnapp.vaccination.core +import de.rki.coronawarnapp.vaccination.core.certificate.HealthCertificateHeader import de.rki.coronawarnapp.vaccination.core.certificate.VaccinationDGCV1 +import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateData +import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateQRCode import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationQRCodeExtractor import de.rki.coronawarnapp.vaccination.core.repository.storage.VaccinatedPersonData import de.rki.coronawarnapp.vaccination.core.repository.storage.VaccinationContainer @@ -12,7 +15,7 @@ class VaccinationTestData @Inject constructor( ) { // AndreasAstra1.pdf - val personAVac1QR = + val personAVac1QRCodeString = "HC1:6BFOXN*TS0BI\$ZD.P9UOL97O4-2HH77HRM3DSPTLRR+%3KXH9M9ESIGUBA KWML%6S5B9-+P70Q5VC9:BPCNYKMXEE1JAA/CXGG0JK1WL260X638J3-E3ND3DAJ-43TTTO3HK1H3QBCWNZ83UQJ:T0/8F7V0HKN:Q8.HBV+0SZ4GH00T9UKP0T9WC5PF6846A\$Q$76QW6%V98T5\$FQMI5DN9QZ5Y0Q\$UPE%5MZ5*T57ZA\$O7T6LEJOA+MZ55EII-EB1EKC422JBBD0D2K.EJJ14B2MP41WTRZPQEC5L64HX6IAS 8S8FT/MAMXP6QS03L0QIRR97I2HOAXL92L0. KOKG8VG5SI:TU+MMPZ55%PBT1YEGEA7IB65C94JBQ2NLEE:NQ% GC3MXHFLF9OIFN0IZ95LJL80P1FDLW452I8941:HH3M41GTNP8EFUNT$.FTD852IWKP/HLIJL8JF8JF172IMAS EDAHMXFBFBQSKJE72KV\$FHJ%3O%6:XM+1QD+T2/VKKER3L3%1THL7MGY.1S:T:GLOX6OCE7+RWYL3.C-L27WNV0G::M74O%K7C50AAEI4" val personAVac1Certificate = VaccinationDGCV1( @@ -40,15 +43,31 @@ class VaccinationTestData @Inject constructor( ) ) + val personAVac1CertificateHeader = HealthCertificateHeader( + issuer = "DE", + issuedAt = Instant.parse("2021-05-11T09:25:00.000Z"), + expiresAt = Instant.parse("2022-05-11T09:25:00.000Z"), + ) + + val personAVac1CertificateData = VaccinationCertificateData( + certificate = personAVac1Certificate, + header = personAVac1CertificateHeader + ) + + val personAVac1QRCode = VaccinationCertificateQRCode( + qrCodeString = personAVac1QRCodeString, + parsedData = personAVac1CertificateData, + ) + val personAVac1Container = VaccinationContainer( scannedAt = Instant.ofEpochMilli(1620062834471), - vaccinationQrCode = personAVac1QR, + vaccinationQrCode = personAVac1QRCodeString, ).apply { qrCodeExtractor = this@VaccinationTestData.qrCodeExtractor } // AndreasAstra2.pdf - val personAVac2QR = + val personAVac2QRCodeString = "6BFOXN*TS0BI\$ZD.P9UOL97O4-2HH77HRM3DSPTLRR+%3D H9M9ESIGUBA KWMLYX1HXK 0DV:D5VC9:BPCNYKMXEE1JAA/CZIK0JK1WL260X638J3-E3ND3DAJ-43TTTMDF6S8:B73QN VNZ.0K6HYI3CNN96BPHNW*0I85V.499TXY9KK9%OC+G9QJPNF67J6QW67KQ9G66PPM4MLJE+.PDB9L6Q2+PFQ5DB96PP5/P-59A%N+892 7J235II3NJ7PK7SLQMIPUBN9CIZI.EJJ14B2MP41IZRZPQEC5L64HX6IAS 8SAFT/MAMXP6QS03L0QIRR97I2HOAXL92L0. KOKGGVG5SI:TU+MMPZ55%PBT1YEGEA7IB65C94JBQ2NLEE:NQ% GC3MXHFLF9OIFN0IZ95LJL80P1FDLW452I8941:HH3M41GTNP8EFUNT\$.FTD852IWKP/HLIJL8JF8JF172E2JA0K*WDQMPB8T3%KLUSR43M.F\$QBQDR\$VT7V01Y7J0BOZLH+D-QF6MO\$R3%XB+.4QI596GY\$SITJP5BS0DFROC.7B.2RTB*UNYSM$*00HIL+H" val personAVac2Certificate = VaccinationDGCV1( @@ -76,18 +95,90 @@ class VaccinationTestData @Inject constructor( ) ) + val personAVac2CertificateHeader = HealthCertificateHeader( + issuer = "DE", + issuedAt = Instant.parse("2021-05-11T09:26:08.000Z"), + expiresAt = Instant.parse("2022-05-11T09:26:08.000Z"), + ) + + val personAVac2CertificateData = VaccinationCertificateData( + certificate = personAVac2Certificate, + header = personAVac2CertificateHeader + ) + + val personAVac2QRCode = VaccinationCertificateQRCode( + qrCodeString = personAVac2QRCodeString, + parsedData = personAVac2CertificateData, + ) + val personAVac2Container = VaccinationContainer( scannedAt = Instant.ofEpochMilli(1620069934471), - vaccinationQrCode = personAVac2QR, + vaccinationQrCode = personAVac2QRCodeString, ).apply { qrCodeExtractor = this@VaccinationTestData.qrCodeExtractor } - val personAData2Vac1Proof = VaccinatedPersonData( + val personAData2Vac = VaccinatedPersonData( vaccinations = setOf(personAVac1Container, personAVac2Container) ) - val personWithoutCountryContainer = VaccinationContainer( + // BorisJohnson1.pdf + val personBVac1QRCodeString = + "HC1:6BFOXN*TS0BI\$ZD.P9UOL97O4-2HH77HRM3DSPTLRR+%3QVH9M9ESIGUBA KWML:SPHXK 0DMYF5VC9:BPCNYKMXEE1JAA/CZIK0JK1WL260X638J3-E3ND3DAJ-43 QTCPFFIJRF3O8H43HX37DUF GFE VMJJYC3SM74E5V.499TXY9KK9+OC+G9QJPNF67J6QW67KQ2G66PPM4MLJE+.PDB9L6Q2+PFQ5DB96PP5/P-59A%N+892 7J235II3NJ7PK7SLQMIPUBN9CIZI.EJJ14B2MP41AZRSEQEC5L64HX6IAS3DS2980IQ.DPUHLW\$GAHLW 70SO:GOLIROGO3T59YLQM14+OP\$I/XK\$M8CL6PZB*L8PK99Q9E\$BDZIF9J8-I\$GI0 J1ALL:F71APC9*KF6LF/NLR/FZ.COKEH-BB4OQ9OG4C5AO**HOELK2AZ7LBLEH-BHPLV5GK3DNKE\$JDVPLW1KD0KCZG.M1LUSB5BCQRJ\$DB5N9%V/GO4IHIBBJ-BI%NWRS%LR%\$KR46325NABFDDAFHD9PZP11COD5U*2KQXCA5W8HH/K51DQO8O0-SOSENFH9101U8$3" + + val personBVac1Certificate = VaccinationDGCV1( + version = "1.0.0", + nameData = VaccinationDGCV1.NameData( + givenName = "Boris", + givenNameStandardized = "BORIS", + familyName = "Johnson Gültig", + familyNameStandardized = "JOHNSON<GUELTIG", + ), + dob = "1966-11-11", + vaccinationDatas = listOf( + VaccinationDGCV1.VaccinationData( + targetId = "840539006", + vaccineId = "1119305005", + medicalProductId = "EU/1/20/1525", + marketAuthorizationHolderId = "ORG-100001417", + doseNumber = 1, + totalSeriesOfDoses = 1, + dt = "2021-04-20", + countryOfVaccination = "DE", + certificateIssuer = "Bundesministerium für Gesundheit - Test01", + uniqueCertificateIdentifier = "01DE/00001/1119305005/3H24U2KVOTPCSINK7N64F2OB9#S", + ) + ) + ) + + val personBVac1CertificateHeader = HealthCertificateHeader( + issuer = "DE", + issuedAt = Instant.parse("2021-05-11T09:23:03.000Z"), + expiresAt = Instant.parse("2022-05-11T09:23:03.000Z"), + ) + + val personBVac1CertificateData = VaccinationCertificateData( + certificate = personBVac1Certificate, + header = personBVac1CertificateHeader + ) + + val personBVac1QRCode = VaccinationCertificateQRCode( + qrCodeString = personBVac1QRCodeString, + parsedData = personBVac1CertificateData, + ) + + val personBVac1Container = VaccinationContainer( + scannedAt = Instant.ofEpochMilli(1620069934471), + vaccinationQrCode = personBVac1QRCodeString, + ).apply { + qrCodeExtractor = this@VaccinationTestData.qrCodeExtractor + } + + val personBData1Vac = VaccinatedPersonData( + vaccinations = setOf(personBVac1Container) + ) + + val personXVac1ContainerBadCountryData = VaccinationContainer( scannedAt = Instant.ofEpochMilli(1620062834471), vaccinationQrCode = VaccinationQrCodeTestData.qrCodeWithNonsenseCountry, ).apply { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationQRCodeExtractorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationQRCodeExtractorTest.kt index b58dd524e24a8585a079d34e1f10283cb8dfd224..ef2d3bd05a7f431767e5bf92ce403b1a1f3b1100 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationQRCodeExtractorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationQRCodeExtractorTest.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.vaccination.core.qrcode import de.rki.coronawarnapp.vaccination.core.DaggerVaccinationTestComponent import de.rki.coronawarnapp.vaccination.core.VaccinationQrCodeTestData +import de.rki.coronawarnapp.vaccination.core.VaccinationTestData import de.rki.coronawarnapp.vaccination.core.certificate.InvalidHealthCertificateException import de.rki.coronawarnapp.vaccination.core.certificate.InvalidHealthCertificateException.ErrorCode.HC_BASE45_DECODING_FAILED import de.rki.coronawarnapp.vaccination.core.certificate.InvalidHealthCertificateException.ErrorCode.HC_ZLIB_DECOMPRESSION_FAILED @@ -19,6 +20,7 @@ import javax.inject.Inject class VaccinationQRCodeExtractorTest : BaseTest() { @Inject lateinit var extractor: VaccinationQRCodeExtractor + @Inject lateinit var vaccinationTestData: VaccinationTestData @BeforeEach fun setup() { @@ -104,4 +106,16 @@ class VaccinationQRCodeExtractorTest : BaseTest() { extractor.extract(VaccinationQrCodeTestData.certificateMissing) }.errorCode shouldBe VC_NO_VACCINATION_ENTRY } + + @Test + fun `test data person A check`() { + val extracted = extractor.extract(vaccinationTestData.personAVac1QRCodeString) + extracted shouldBe vaccinationTestData.personAVac1QRCode + } + + @Test + fun `test data person B check`() { + val extracted = extractor.extract(vaccinationTestData.personBVac1QRCodeString) + extracted shouldBe vaccinationTestData.personBVac1QRCode + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/VaccinationRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/VaccinationRepositoryTest.kt index bcefa337179dab6e378ec59efcac4de629151b8c..f0703302cb0f92b034fdb75003771bfd254ef56e 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/VaccinationRepositoryTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/VaccinationRepositoryTest.kt @@ -1,14 +1,30 @@ package de.rki.coronawarnapp.vaccination.core.repository import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.vaccination.core.DaggerVaccinationTestComponent +import de.rki.coronawarnapp.vaccination.core.VaccinationTestData +import de.rki.coronawarnapp.vaccination.core.certificate.InvalidHealthCertificateException +import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationQRCodeExtractor +import de.rki.coronawarnapp.vaccination.core.repository.errors.VaccinationCertificateNotFoundException import de.rki.coronawarnapp.vaccination.core.repository.storage.VaccinatedPersonData import de.rki.coronawarnapp.vaccination.core.repository.storage.VaccinationStorage import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationValueSet +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.every import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf import org.joda.time.Instant +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest +import testhelpers.TestDispatcherProvider import testhelpers.coroutines.runBlockingTest2 +import timber.log.Timber +import javax.inject.Inject class VaccinationRepositoryTest : BaseTest() { @@ -17,107 +33,171 @@ class VaccinationRepositoryTest : BaseTest() { @MockK lateinit var storage: VaccinationStorage @MockK lateinit var valueSetsRepository: ValueSetsRepository @MockK lateinit var vaccinationValueSet: VaccinationValueSet + @MockK lateinit var qrCodeExtractor: VaccinationQRCodeExtractor private var testStorage: Set<VaccinatedPersonData> = emptySet() - private var nowUTC = Instant.ofEpochMilli(1234567890) - -// @BeforeEach -// fun setup() { -// MockKAnnotations.init(this) -// -// every { timeStamper.nowUTC } returns nowUTC -// -// every { valueSetsRepository.latestValueSet } returns flowOf(vaccinationValueSet) -// -// coEvery { vaccinationProofServer.getProofCertificate(any()) } returns VaccinationTestData.PERSON_A_PROOF_1_RESPONSE -// -// storage.apply { -// every { personContainers } answers { testStorage } -// every { personContainers = any() } answers { testStorage = arg(0) } -// } -// } -// -// private fun createInstance(scope: CoroutineScope) = VaccinationRepository( -// appScope = scope, -// dispatcherProvider = TestDispatcherProvider(), -// timeStamper = timeStamper, -// storage = storage, -// valueSetsRepository = valueSetsRepository, -// vaccinationProofServer = vaccinationProofServer, -// ) -// -// @Test -// fun `add new certificate - no prior data`() = runBlockingTest2(ignoreActive = true) { -// val instance = createInstance(this) -// -// advanceUntilIdle() -// -// instance.registerVaccination(VaccinationTestData.PERSON_A_VAC_1_QRCODE).apply { -// Timber.i("Returned cert is %s", this) -// this.personIdentifier shouldBe VaccinationTestData.PERSON_A_VAC_1_CONTAINER.personIdentifier -// } -// } + @Inject lateinit var vaccinationTestData: VaccinationTestData + + // Few days after issued dates of person A in test data. + private var nowUTC = Instant.parse("2021-05-13T09:25:00.000Z") + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + DaggerVaccinationTestComponent.factory().create().inject(this) + + every { timeStamper.nowUTC } returns nowUTC + + every { valueSetsRepository.latestValueSet } returns flowOf(vaccinationValueSet) + + storage.apply { + every { personContainers } answers { testStorage } + every { personContainers = any() } answers { testStorage = arg(0) } + } + } + + private fun createInstance(scope: CoroutineScope) = VaccinationRepository( + appScope = scope, + dispatcherProvider = TestDispatcherProvider(), + timeStamper = timeStamper, + storage = storage, + valueSetsRepository = valueSetsRepository, + vaccinationQRCodeExtractor = qrCodeExtractor, + ) @Test - fun `add new certificate - existing data`() = runBlockingTest2(ignoreActive = true) { -// val dataBefore = VaccinationTestData.PERSON_A_DATA_2VAC_PROOF.copy( -// vaccinations = setOf(VaccinationTestData.PERSON_A_VAC_1_CONTAINER), -// proofs = emptySet() -// ) -// val dataAfter = VaccinationTestData.PERSON_A_DATA_2VAC_PROOF.copy( -// vaccinations = setOf( -// VaccinationTestData.PERSON_A_VAC_1_CONTAINER, -// VaccinationTestData.PERSON_A_VAC_2_CONTAINER.copy(scannedAt = nowUTC) -// ), -// proofs = emptySet() -// ) -// testStorage = setOf(dataBefore) -// -// val instance = createInstance(this) -// -// advanceUntilIdle() -// -// instance.registerVaccination(VaccinationTestData.PERSON_A_VAC_2_QRCODE).apply { -// Timber.i("Returned cert is %s", this) -// this.personIdentifier shouldBe VaccinationTestData.PERSON_A_VAC_2_CONTAINER.personIdentifier -// } -// -// testStorage.first() shouldBe dataAfter + fun `add new certificate - no prior data`() = runBlockingTest2(ignoreActive = true) { + val instance = createInstance(this) + advanceUntilIdle() + + instance.registerVaccination(vaccinationTestData.personAVac1QRCode).apply { + Timber.i("Returned cert is %s", this) + this.personIdentifier shouldBe vaccinationTestData.personAVac1Container.personIdentifier + } } @Test - fun `add new certificate - if eligble for proof, start request`() = runBlockingTest2(ignoreActive = true) { -// TODO() + fun `add new certificate - existing data`() = runBlockingTest2(ignoreActive = true) { + val dataBefore = vaccinationTestData.personAData2Vac.copy( + vaccinations = setOf(vaccinationTestData.personAVac1Container), + ) + val dataAfter = vaccinationTestData.personAData2Vac.copy( + vaccinations = setOf( + vaccinationTestData.personAVac1Container, + vaccinationTestData.personAVac2Container.copy(scannedAt = nowUTC) + ), + ) + testStorage = setOf(dataBefore) + + val instance = createInstance(this) + advanceUntilIdle() + + instance.registerVaccination(vaccinationTestData.personAVac2QRCode).apply { + Timber.i("Returned cert is %s", this) + this.personIdentifier shouldBe vaccinationTestData.personAVac2Container.personIdentifier + } + + testStorage.first() shouldBe dataAfter } @Test - fun `add new certificate - does not match existing person`() { -// TODO() + fun `add new certificate - does not match existing person`() = runBlockingTest2(ignoreActive = true) { + testStorage = setOf(vaccinationTestData.personAData2Vac) + + val instance = createInstance(this) + advanceUntilIdle() + + shouldThrow<InvalidHealthCertificateException> { + instance.registerVaccination(vaccinationTestData.personBVac1QRCode) + }.errorCode shouldBe InvalidHealthCertificateException.ErrorCode.VC_NAME_MISMATCH + + testStorage shouldBe setOf(vaccinationTestData.personAData2Vac) } @Test - fun `add new certificate - duplicate certificate`() { -// TODO() + fun `add new certificate - duplicate certificate`() = runBlockingTest2(ignoreActive = true) { + val dataBefore = vaccinationTestData.personAData2Vac.copy( + vaccinations = setOf(vaccinationTestData.personAVac1Container), + ) + + testStorage = setOf(dataBefore) + + val instance = createInstance(this) + advanceUntilIdle() + + shouldThrow<InvalidHealthCertificateException> { + instance.registerVaccination(vaccinationTestData.personAVac1QRCode) + }.errorCode shouldBe InvalidHealthCertificateException.ErrorCode.VC_ALREADY_REGISTERED + + testStorage.first() shouldBe dataBefore } @Test - fun `clear data`() { -// TODO() + fun `clear data`() = runBlockingTest2(ignoreActive = true) { + testStorage = setOf(vaccinationTestData.personAData2Vac) + + val instance = createInstance(this) + advanceUntilIdle() + + instance.vaccinationInfos.first().single().data shouldBe vaccinationTestData.personAData2Vac + + instance.clear() + + testStorage shouldBe emptySet() + instance.vaccinationInfos.first() shouldBe emptySet() } @Test - fun `remove certificate`() { -// TODO() + fun `remove certificate`() = runBlockingTest2(ignoreActive = true) { + val before = vaccinationTestData.personAData2Vac + val after = vaccinationTestData.personAData2Vac.copy( + vaccinations = setOf(vaccinationTestData.personAVac1Container) + ) + val toRemove = vaccinationTestData.personAVac2Container + + testStorage = setOf(before) + + val instance = createInstance(this) + advanceUntilIdle() + + instance.vaccinationInfos.first().single().data shouldBe vaccinationTestData.personAData2Vac + + instance.deleteVaccinationCertificate(toRemove.certificateId) + advanceUntilIdle() + + testStorage shouldBe setOf(after) + instance.vaccinationInfos.first().single().data shouldBe after } @Test - fun `remove certificate - starts proof check if we deleted a vaccination that was eligble for proof`() { -// TODO() + fun `remove certificate - unknown certificate`() = runBlockingTest2(ignoreActive = true) { + testStorage = setOf(vaccinationTestData.personAData2Vac) + + val instance = createInstance(this) + advanceUntilIdle() + + instance.vaccinationInfos.first().single().data shouldBe vaccinationTestData.personAData2Vac + + shouldThrow<VaccinationCertificateNotFoundException> { + instance.deleteVaccinationCertificate(vaccinationTestData.personBVac1Container.certificateId) + } } @Test - fun `check for new proof certificate`() { -// TODO() + fun `remove certificate - last certificate for person`() = runBlockingTest2(ignoreActive = true) { + testStorage = setOf(vaccinationTestData.personBData1Vac) + + val instance = createInstance(this) + advanceUntilIdle() + + instance.vaccinationInfos.first().single().data shouldBe vaccinationTestData.personBData1Vac + + instance.deleteVaccinationCertificate(vaccinationTestData.personBVac1Container.certificateId) + advanceUntilIdle() + + instance.vaccinationInfos.first() shouldBe emptySet() + testStorage shouldBe emptySet() } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationContainerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationContainerTest.kt index 4de4d70a1bc0e0c46e29a062df85c07f048a6a29..c362bebab70f4e7cfdccaa0e760174263e15374e 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationContainerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationContainerTest.kt @@ -138,7 +138,7 @@ class VaccinationContainerTest : BaseTest() { @Test fun `nonsense country code appears unchanged`() { - testData.personWithoutCountryContainer.toVaccinationCertificate(null).apply { + testData.personXVac1ContainerBadCountryData.toVaccinationCertificate(null).apply { certificateCountry shouldBe "YY" } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationStorageTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationStorageTest.kt index a888628a60455a202632c24e041d74366ce81758..9af9da65bd8172c743c89b1078d28416502a678b 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationStorageTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationStorageTest.kt @@ -61,7 +61,7 @@ class VaccinationStorageTest : BaseTest() { @Test fun `store one person`() { val instance = createInstance() - instance.personContainers = setOf(testData.personAData2Vac1Proof) + instance.personContainers = setOf(testData.personAData2Vac) val json = (mockPreferences.dataMapPeek["vaccination.person.1966-11-11#ASTRA<EINS#ANDREAS"] as String) @@ -70,10 +70,10 @@ class VaccinationStorageTest : BaseTest() { { "vaccinationData": [ { - "vaccinationQrCode": "${testData.personAVac1QR}", + "vaccinationQrCode": "${testData.personAVac1QRCodeString}", "scannedAt": 1620062834471 }, { - "vaccinationQrCode": "${testData.personAVac2QR}", + "vaccinationQrCode": "${testData.personAVac2QRCodeString}", "scannedAt": 1620069934471 } ] @@ -81,7 +81,7 @@ class VaccinationStorageTest : BaseTest() { """.toComparableJsonPretty() instance.personContainers.single().apply { - this shouldBe testData.personAData2Vac1Proof + this shouldBe testData.personAData2Vac this.vaccinations shouldBe setOf( testData.personAVac1Container, testData.personAVac2Container,