diff --git a/pom.xml b/pom.xml index 74d547520f6d2fb56363f69473268c153fe25ce1..7868e64e6cbf59d7906ec247af32945ee6a5b895 100644 --- a/pom.xml +++ b/pom.xml @@ -70,10 +70,6 @@ <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency> - <dependency> - <groupId>org.springframework.retry</groupId> - <artifactId>spring-retry</artifactId> - </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> diff --git a/src/main/kotlin/fr/gouv/stopc/submissioncode/Application.kt b/src/main/kotlin/fr/gouv/stopc/submissioncode/Application.kt index 16a17f734dd149d54936af6a7591e82a26b25c3b..5a3b194da84d620cb8e8816eb328b5331ae87a72 100644 --- a/src/main/kotlin/fr/gouv/stopc/submissioncode/Application.kt +++ b/src/main/kotlin/fr/gouv/stopc/submissioncode/Application.kt @@ -4,11 +4,9 @@ import fr.gouv.stopc.submissioncode.configuration.SubmissionProperties import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.runApplication -import org.springframework.retry.annotation.EnableRetry @SpringBootApplication @EnableConfigurationProperties(SubmissionProperties::class) -@EnableRetry class Application fun main(args: Array<String>) { diff --git a/src/main/kotlin/fr/gouv/stopc/submissioncode/configuration/SubmissionProperties.kt b/src/main/kotlin/fr/gouv/stopc/submissioncode/configuration/SubmissionProperties.kt index 91eb1156b83094ffa4130e69080269f8dc0e6dfb..cb8e1ec62d171f4846b871a8c9fe08b2d91f2d49 100644 --- a/src/main/kotlin/fr/gouv/stopc/submissioncode/configuration/SubmissionProperties.kt +++ b/src/main/kotlin/fr/gouv/stopc/submissioncode/configuration/SubmissionProperties.kt @@ -8,12 +8,6 @@ import java.time.Duration @ConfigurationProperties(prefix = "submission") data class SubmissionProperties( - val longCodeLifetime: Duration, - - val shortCodeLifetime: Duration, - - val testCodeLifetime: Duration, - val jwtCodeLifetime: Duration, val jwtPublicKeys: Map<String, String> diff --git a/src/main/kotlin/fr/gouv/stopc/submissioncode/controller/SubmissionCodeController.kt b/src/main/kotlin/fr/gouv/stopc/submissioncode/controller/SubmissionCodeController.kt index fdceeb729ae63828151581351788db97c2672769..eedcf9f3c4e227c68acfc63de961b03d00090444 100644 --- a/src/main/kotlin/fr/gouv/stopc/submissioncode/controller/SubmissionCodeController.kt +++ b/src/main/kotlin/fr/gouv/stopc/submissioncode/controller/SubmissionCodeController.kt @@ -1,16 +1,11 @@ package fr.gouv.stopc.submissioncode.controller import fr.gouv.stopc.submissioncode.api.SubmissionCodeApi -import fr.gouv.stopc.submissioncode.api.model.CodeType -import fr.gouv.stopc.submissioncode.api.model.CodeType.short -import fr.gouv.stopc.submissioncode.api.model.CodeType.test -import fr.gouv.stopc.submissioncode.api.model.SubmissionCodeGenerationResponse import fr.gouv.stopc.submissioncode.api.model.SubmissionCodeValidationResponse import fr.gouv.stopc.submissioncode.service.SubmissionCodeService import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import java.time.ZoneOffset.UTC @RestController @RequestMapping("/api/v1") @@ -18,24 +13,9 @@ class SubmissionCodeController( private val submissionCodeService: SubmissionCodeService ) : SubmissionCodeApi { - override fun generate(codeType: CodeType): ResponseEntity<SubmissionCodeGenerationResponse> { - val generatedCode = when (codeType) { - short -> submissionCodeService.generateShortCode() - test -> submissionCodeService.generateTestCode() - } - return ResponseEntity.ok( - SubmissionCodeGenerationResponse( - code = generatedCode.code, - dateGenerate = generatedCode.dateGeneration.atOffset(UTC), - validFrom = generatedCode.dateAvailable.atOffset(UTC), - validUntil = generatedCode.dateEndValidity.atOffset(UTC) - ) - ) - } - override fun verify(code: String, deprecatedCodeType: Int?): ResponseEntity<SubmissionCodeValidationResponse> { return ResponseEntity.ok( - SubmissionCodeValidationResponse(submissionCodeService.validateAndUse(code)) + SubmissionCodeValidationResponse(submissionCodeService.validateJwt(code)) ) } } diff --git a/src/main/kotlin/fr/gouv/stopc/submissioncode/repository/SubmissionCodeRepository.kt b/src/main/kotlin/fr/gouv/stopc/submissioncode/repository/SubmissionCodeRepository.kt deleted file mode 100644 index 8d95e73d0e034dd76ece9e74f2503a821faab7e0..0000000000000000000000000000000000000000 --- a/src/main/kotlin/fr/gouv/stopc/submissioncode/repository/SubmissionCodeRepository.kt +++ /dev/null @@ -1,44 +0,0 @@ -package fr.gouv.stopc.submissioncode.repository - -import fr.gouv.stopc.submissioncode.repository.model.SubmissionCode -import org.springframework.data.jpa.repository.Query -import org.springframework.data.repository.PagingAndSortingRepository -import org.springframework.stereotype.Repository -import java.time.Instant - -@Repository -interface SubmissionCodeRepository : PagingAndSortingRepository<SubmissionCode, Long> { - - fun findByCode(code: String): SubmissionCode? - - @Query( - """ - select count(s.id) - from SubmissionCode s - where s.type = :type - and :start <= s.dateUse and s.dateUse < :endExclusive - """ - ) - fun countByTypeAndUsedBetween(type: String, start: Instant, endExclusive: Instant): Long - - @Query( - """ - select count(s.id) - from SubmissionCode s - where s.dateUse is null - and s.type = :type - and :start <= s.dateEndValidity and s.dateEndValidity < :endExclusive - """ - ) - fun countByTypeAndExpired(type: String, start: Instant, endExclusive: Instant): Long - - @Query( - """ - select count(s.id) - from SubmissionCode s - where s.type = :type - and :start <= s.dateGeneration and s.dateGeneration < :endExclusive - """ - ) - fun countByTypeNewlyGenerated(type: String, start: Instant, endExclusive: Instant): Long -} diff --git a/src/main/kotlin/fr/gouv/stopc/submissioncode/repository/model/SubmissionCode.kt b/src/main/kotlin/fr/gouv/stopc/submissioncode/repository/model/SubmissionCode.kt deleted file mode 100644 index 0c13ed8a074ad34f09fc8de048d3c471db6cfed1..0000000000000000000000000000000000000000 --- a/src/main/kotlin/fr/gouv/stopc/submissioncode/repository/model/SubmissionCode.kt +++ /dev/null @@ -1,52 +0,0 @@ -package fr.gouv.stopc.submissioncode.repository.model - -import java.time.Instant -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.GeneratedValue -import javax.persistence.GenerationType -import javax.persistence.Id -import javax.persistence.Table - -@Entity -@Table(name = "submission_code") -data class SubmissionCode( - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - val id: Long = 0, - - @Column(name = "code") - val code: String, - - @Column(name = "type_code") - val type: String, - - @Column(name = "date_end_validity") - val dateEndValidity: Instant, - - @Column(name = "date_available") - val dateAvailable: Instant, - - @Column(name = "date_generation") - val dateGeneration: Instant, - - @Column(name = "date_use") - val dateUse: Instant? = null, - - @Column(name = "used") - val used: Boolean -) { - - fun getType() = Type.fromDbValue(type) - - enum class Type(val dbValue: String) { - LONG("1"), - SHORT("2"), - TEST("3"); - - companion object { - fun fromDbValue(type: String): Type = values().find { type == it.dbValue } - ?: throw IllegalStateException("Database entry has inconsistent code type value") - } - } -} diff --git a/src/main/kotlin/fr/gouv/stopc/submissioncode/service/KpiService.kt b/src/main/kotlin/fr/gouv/stopc/submissioncode/service/KpiService.kt index 12b9a62c06b65e6c9b8a4d9adec705ea14f4a0c0..ba693cbbec7c3db306f4d7d45b5173ea324d2c21 100644 --- a/src/main/kotlin/fr/gouv/stopc/submissioncode/service/KpiService.kt +++ b/src/main/kotlin/fr/gouv/stopc/submissioncode/service/KpiService.kt @@ -2,8 +2,6 @@ package fr.gouv.stopc.submissioncode.service import fr.gouv.stopc.submissioncode.api.model.Kpi import fr.gouv.stopc.submissioncode.repository.JwtRepository -import fr.gouv.stopc.submissioncode.repository.SubmissionCodeRepository -import fr.gouv.stopc.submissioncode.repository.model.SubmissionCode import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.time.LocalDate @@ -12,7 +10,7 @@ import kotlin.streams.toList @Service @Transactional(readOnly = true) -class KpiService(private val submissionCodeRepository: SubmissionCodeRepository, private val jwtRepository: JwtRepository) { +class KpiService(private val jwtRepository: JwtRepository) { fun computeKpi(startDate: LocalDate, endDate: LocalDate): List<Kpi> { return startDate.datesUntil(endDate.plusDays(1)) @@ -21,50 +19,10 @@ class KpiService(private val submissionCodeRepository: SubmissionCodeRepository, val endOfDay = it.atStartOfDay().plusDays(1).toInstant(UTC) Kpi( date = it, - nbShortCodesUsed = submissionCodeRepository.countByTypeAndUsedBetween( - SubmissionCode.Type.SHORT.dbValue, - startOfDay, - endOfDay - ), - nbLongCodesUsed = submissionCodeRepository.countByTypeAndUsedBetween( - SubmissionCode.Type.LONG.dbValue, - startOfDay, - endOfDay - ), - nbTestCodesUsed = submissionCodeRepository.countByTypeAndUsedBetween( - SubmissionCode.Type.TEST.dbValue, - startOfDay, - endOfDay - ), nbJwtUsed = jwtRepository.countByUsedBetween( startOfDay, endOfDay - ), - nbShortExpiredCodes = submissionCodeRepository.countByTypeAndExpired( - SubmissionCode.Type.SHORT.dbValue, - startOfDay, - endOfDay - ), - nbLongExpiredCodes = submissionCodeRepository.countByTypeAndExpired( - SubmissionCode.Type.LONG.dbValue, - startOfDay, - endOfDay - ), - nbTestExpiredCodes = submissionCodeRepository.countByTypeAndExpired( - SubmissionCode.Type.TEST.dbValue, - startOfDay, - endOfDay - ), - nbShortCodesGenerated = submissionCodeRepository.countByTypeNewlyGenerated( - SubmissionCode.Type.SHORT.dbValue, - startOfDay, - endOfDay - ), - nbTestCodesGenerated = submissionCodeRepository.countByTypeNewlyGenerated( - SubmissionCode.Type.TEST.dbValue, - startOfDay, - endOfDay - ), + ) ) } .toList() diff --git a/src/main/kotlin/fr/gouv/stopc/submissioncode/service/MetricsService.kt b/src/main/kotlin/fr/gouv/stopc/submissioncode/service/MetricsService.kt index a0db67f40e6aa8cd59f17d3b5c7482641e4562a0..f56a7e397e8a7bc935e1f82732046ed1382913c4 100644 --- a/src/main/kotlin/fr/gouv/stopc/submissioncode/service/MetricsService.kt +++ b/src/main/kotlin/fr/gouv/stopc/submissioncode/service/MetricsService.kt @@ -1,27 +1,23 @@ package fr.gouv.stopc.submissioncode.service -import fr.gouv.stopc.submissioncode.service.model.CodeType import io.micrometer.core.instrument.Counter import io.micrometer.core.instrument.MeterRegistry import org.springframework.stereotype.Service @Service -class MetricsService(private val meterRegistry: MeterRegistry) { +class MetricsService(meterRegistry: MeterRegistry) { - private val codesCounters = CodeType.values().associateWith { codeType -> + private val codesCounters = mapOf( true to Counter.builder("submission.verify.code") .description("Submission codes verified") - .tag("code type", codeType.name) .tag("valid", "true") .register(meterRegistry), false to Counter.builder("submission.verify.code") .description("Submission codes rejected") - .tag("code type", codeType.name) .tag("valid", "false") .register(meterRegistry) ) - } - fun countCodeUsed(codeType: CodeType, valid: Boolean) = codesCounters[codeType]!![valid]!!.increment() + fun countCodeUsed(valid: Boolean) = codesCounters[valid]!!.increment() } diff --git a/src/main/kotlin/fr/gouv/stopc/submissioncode/service/SubmissionCodeService.kt b/src/main/kotlin/fr/gouv/stopc/submissioncode/service/SubmissionCodeService.kt index 8980c45ee6421f540da0e677519d0b780907744d..a4f686de99f586f81a20c3e19e1a8bb435f2c914 100644 --- a/src/main/kotlin/fr/gouv/stopc/submissioncode/service/SubmissionCodeService.kt +++ b/src/main/kotlin/fr/gouv/stopc/submissioncode/service/SubmissionCodeService.kt @@ -5,147 +5,31 @@ import com.nimbusds.jose.JWSVerifier import com.nimbusds.jwt.SignedJWT import fr.gouv.stopc.submissioncode.configuration.SubmissionProperties import fr.gouv.stopc.submissioncode.repository.JwtRepository -import fr.gouv.stopc.submissioncode.repository.SubmissionCodeRepository import fr.gouv.stopc.submissioncode.repository.model.JwtUsed -import fr.gouv.stopc.submissioncode.repository.model.SubmissionCode -import fr.gouv.stopc.submissioncode.service.model.CodeType -import fr.gouv.stopc.submissioncode.service.model.CodeType.JWT -import fr.gouv.stopc.submissioncode.service.model.CodeType.LONG -import fr.gouv.stopc.submissioncode.service.model.CodeType.SHORT -import fr.gouv.stopc.submissioncode.service.model.CodeType.TEST import org.slf4j.LoggerFactory import org.springframework.dao.DataIntegrityViolationException -import org.springframework.retry.annotation.Backoff -import org.springframework.retry.annotation.Recover -import org.springframework.retry.annotation.Retryable import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.text.ParseException import java.time.Instant -import java.time.temporal.ChronoUnit.DAYS @Service @Transactional class SubmissionCodeService( - private val random: RandomGenerator, private val submissionProperties: SubmissionProperties, private val jwtSignatureVerifiers: Map<String, JWSVerifier>, - private val submissionCodeRepository: SubmissionCodeRepository, private val jwtRepository: JwtRepository, private val metricsService: MetricsService ) { private val log = LoggerFactory.getLogger(this::class.java) - @Retryable( - value = [DataIntegrityViolationException::class], - maxAttempts = 10, - backoff = Backoff(delay = 0), - recover = "throwErrorTooManyAttempts" - ) - fun generateShortCode(): SubmissionCode { - val now = Instant.now() - val code = random.upperCaseString(6) - return submissionCodeRepository.save( - SubmissionCode( - code = code, - dateGeneration = now, - dateAvailable = now, - dateEndValidity = now.plus(submissionProperties.shortCodeLifetime), - type = SubmissionCode.Type.SHORT.dbValue, - used = false - ) - ) - } - - @Retryable( - value = [DataIntegrityViolationException::class], - maxAttempts = 10, - backoff = Backoff(delay = 0), - recover = "throwErrorTooManyAttempts" - ) - fun generateTestCode(): SubmissionCode { - val now = Instant.now() - val code = random.upperCaseString(12) - return submissionCodeRepository.save( - SubmissionCode( - code = code, - dateGeneration = now, - dateAvailable = now, - dateEndValidity = now.plus(submissionProperties.testCodeLifetime).truncatedTo(DAYS), - type = SubmissionCode.Type.TEST.dbValue, - used = false - ) - ) - } - - @Recover - fun throwErrorTooManyAttempts(e: DataIntegrityViolationException): SubmissionCode { - throw RuntimeException("No unique code could be generated after 10 random attempts") - } - - fun validateAndUse(code: String): Boolean = when (val codeType = CodeType.ofCode(code)) { - LONG, SHORT, TEST -> validateCode(codeType, code) - JWT -> validateJwt(code) - else -> { - log.info("Code $code does not match any code type and can't be validated") - false - } - } - - private fun validateCode(codeType: CodeType, code: String): Boolean { - - val now = Instant.now() - val submissionCode = submissionCodeRepository.findByCode(code) - - if (submissionCode == null) { - metricsService.countCodeUsed(codeType, false) - log.info("Code $code doesn't exist") - return false - } - if (submissionCode.type != codeType.databaseRepresentation?.dbValue) { - metricsService.countCodeUsed(codeType, false) - log.info("'$code' seems to be a $codeType code but database knows it as a ${submissionCode.getType()} code") - return false - } - if (submissionCode.used || submissionCode.dateUse != null) { - metricsService.countCodeUsed(codeType, false) - log.info("$codeType code '$code' has already been used") - return false - } - if (submissionCode.dateAvailable.isAfter(now)) { - metricsService.countCodeUsed(codeType, false) - log.info("$codeType code '$code' is not yet available (availability date is ${submissionCode.dateAvailable})") - return false - } - if (submissionCode.dateEndValidity.isBefore(now)) { - metricsService.countCodeUsed(codeType, false) - log.info("$codeType code '$code' has expired on ${submissionCode.dateEndValidity}") - return false - } - - return try { - submissionCodeRepository.save( - submissionCode.copy( - used = true, - dateUse = now - ) - ) - metricsService.countCodeUsed(codeType, true) - true - } catch (e: DataIntegrityViolationException) { - metricsService.countCodeUsed(JWT, false) - log.info("Code $code ($codeType) has already been used.") - false - } - } - - private fun validateJwt(jwt: String): Boolean { + fun validateJwt(jwt: String): Boolean { val signedJwt = try { SignedJWT.parse(jwt) } catch (e: Exception) { - metricsService.countCodeUsed(JWT, false) + metricsService.countCodeUsed(false) log.info("JWT could not be parsed: ${e.message ?: e::class.simpleName}, $jwt") return false } @@ -153,7 +37,7 @@ class SubmissionCodeService( val claims = try { signedJwt.jwtClaimsSet } catch (e: ParseException) { - metricsService.countCodeUsed(JWT, false) + metricsService.countCodeUsed(false) log.info("JWT claims set could not be parsed: ${e.message}, $jwt") return false } @@ -164,54 +48,54 @@ class SubmissionCodeService( val now = Instant.now() if (jti.isNullOrBlank() || iat == null || exp == null) { - metricsService.countCodeUsed(JWT, false) + metricsService.countCodeUsed(false) log.info("JWT is missing claim jti ($jti) or iat ($iat): $jwt") return false } if (iat.isAfter(now)) { - metricsService.countCodeUsed(JWT, false) + metricsService.countCodeUsed(false) log.info("JWT is issued at a future time ($iat): $jwt") return false } if (exp.isBefore(now)) { - metricsService.countCodeUsed(JWT, false) + metricsService.countCodeUsed(false) log.info("JWT has expired, it was issued at $iat, so it expired on $exp: $jwt") return false } val kid = signedJwt.header.keyID if (!submissionProperties.jwtPublicKeys.containsKey(kid)) { - metricsService.countCodeUsed(JWT, false) + metricsService.countCodeUsed(false) log.info("No public key found in configuration for kid '$kid': $jwt") return false } try { if (!signedJwt.verify(jwtSignatureVerifiers[kid])) { - metricsService.countCodeUsed(JWT, false) + metricsService.countCodeUsed(false) log.info("JWT signature is invalid: $jwt") return false } } catch (e: JOSEException) { - metricsService.countCodeUsed(JWT, false) + metricsService.countCodeUsed(false) log.info("JWT signature can't be verified: ${e.message}, $jwt") return false } if (jwtRepository.existsByJti(jti)) { - metricsService.countCodeUsed(JWT, false) + metricsService.countCodeUsed(false) log.info("JWT with jti '$jti' has already been used: $jwt") return false } return try { jwtRepository.save(JwtUsed(jti = jti, dateUse = now)) - metricsService.countCodeUsed(JWT, true) + metricsService.countCodeUsed(true) true } catch (e: DataIntegrityViolationException) { - metricsService.countCodeUsed(JWT, false) + metricsService.countCodeUsed(false) log.info("JWT with jti '$jti' has already been used: $jwt") false } diff --git a/src/main/kotlin/fr/gouv/stopc/submissioncode/service/model/CodeType.kt b/src/main/kotlin/fr/gouv/stopc/submissioncode/service/model/CodeType.kt deleted file mode 100644 index a9d3efcb7d20dc5b83cc8529614b6c3aa9c25295..0000000000000000000000000000000000000000 --- a/src/main/kotlin/fr/gouv/stopc/submissioncode/service/model/CodeType.kt +++ /dev/null @@ -1,19 +0,0 @@ -package fr.gouv.stopc.submissioncode.service.model - -import fr.gouv.stopc.submissioncode.repository.model.SubmissionCode -import kotlin.text.RegexOption.IGNORE_CASE - -enum class CodeType( - private val pattern: Regex, - val databaseRepresentation: SubmissionCode.Type? -) { - LONG("[a-f0-9]{8}(-[a-f0-9]{4}){4}[a-f0-9]{8}".toRegex(IGNORE_CASE), SubmissionCode.Type.LONG), - SHORT("[a-z0-9]{6}".toRegex(IGNORE_CASE), SubmissionCode.Type.SHORT), - TEST("[a-z0-9]{12}".toRegex(IGNORE_CASE), SubmissionCode.Type.TEST), - JWT("^[^.]+\\.[^.]+\\.[^.]+$".toRegex(), null); - - companion object { - - fun ofCode(code: String) = values().find { code.matches(it.pattern) } - } -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index fa1bc4a2d907c5040f0aea87a8d488b5baf8bf37..b322f57882fc29c988851d2973400ba7b6fda638 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,7 +5,4 @@ spring: properties.hibernate.jdbc.time_zone: UTC submission: - long-code-lifetime: 10d - short-code-lifetime: 1h - test-code-lifetime: 15d jwt-code-lifetime: 10d diff --git a/src/main/resources/db/migration/V4__delete_submission_tables.sql b/src/main/resources/db/migration/V4__delete_submission_tables.sql new file mode 100644 index 0000000000000000000000000000000000000000..18794b4217a8c3f8915f4a5b5cea0caa4ae55b99 --- /dev/null +++ b/src/main/resources/db/migration/V4__delete_submission_tables.sql @@ -0,0 +1,6 @@ +-- +-- Now only jwt are used +-- +DROP TABLE submission_code; +DROP TABLE seq_fichier; +DROP TABLE lot_keys; diff --git a/src/main/resources/openapi-submission-v1.yml b/src/main/resources/openapi-submission-v1.yml index 7e4d24ee6e2619135835b99ddd630d2d90495861..bb52271d59811b3398d0ef5735c30adaf7596d02 100644 --- a/src/main/resources/openapi-submission-v1.yml +++ b/src/main/resources/openapi-submission-v1.yml @@ -12,51 +12,6 @@ info: license: name: Custom (see repository's LICENSE.md) paths: - /generate/{codeType}: - get: - operationId: generate - tags: - - Submission Code - summary: Generate a code to declare themself ill - description: One can generate either a short or a test code. - * a short code is a 6 alphanumeric characters code valid 1 hour - * a test code is a 12 alphanumeric characters code valid 3 days - parameters: - - in: path - name: codeType - description: type of the requested code, `long` is unsupported - schema: - $ref: "#/components/schemas/CodeType" - required: true - responses: - 200: - description: The generated code - content: - application/json: - schema: - $ref: "#/components/schemas/SubmissionCodeGenerationResponse" - examples: - short: - value: - code: AS3F5Z - dateGenerate: 2022-01-11T18:28:30Z - validFrom: 2022-01-11T18:28:31Z - validUntil: 2022-01-11T19:28:31Z - test: - value: - code: BxTjcmac07Mg - dateGenerate: 2022-01-11T18:28:30Z - validFrom: 2022-01-11T18:28:31Z - validUntil: 2022-01-14T00:00:00Z - 400: - $ref: "openapi-errors-v1.yml#/components/responses/BadRequest" - 401: - $ref: "openapi-errors-v1.yml#/components/responses/Unauthorized" - 403: - $ref: "openapi-errors-v1.yml#/components/responses/Forbidden" - 500: - $ref: "openapi-errors-v1.yml#/components/responses/InternalServerError" - /verify: get: operationId: verify @@ -75,12 +30,7 @@ paths: in: query required: true description: | - The submission code to validate. A submission code can be either: - * a 6 alphanumeric string `[a-zA-Z0-9]{6}` - * a 12 alphanumeric string `[a-zA-Z0-9]{12}` - * a 36 alphanumeric UUID `[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}` - * a serialized JWT - + The submission code (as a JWT) to validate. : A JWT has three parts : a header, a payload and a signature header { "alg": "ES256", @@ -160,37 +110,6 @@ paths: components: schemas: - CodeType: - type: string - description: A type of submission code - enum: [short, test] - - SubmissionCodeGenerationResponse: - type: object - properties: - code: - type: string - description: The submission code value - example: AS3F5Z - validFrom: - type: string - format: date-time - description: Submission code must not be used before this date - validUntil: - type: string - format: date-time - description: Submission code must not be used after this date - dateGenerate: - type: string - format: date-time - description: Submission code mgeneration date - description: A submission code and its type as well as validity information - required: - - code - - validFrom - - validUntil - - dateGenerate - SubmissionCodeValidationResponse: type: object properties: @@ -211,49 +130,10 @@ components: date: type: string format: date - nbShortCodesUsed: - type: integer - format: int64 - example: 23 - nbLongCodesUsed: - type: integer - format: int64 - example: 55 - nbLongExpiredCodes: - type: integer - format: int64 - example: 2145 - nbShortExpiredCodes: - type: integer - format: int64 - example: 30 - nbShortCodesGenerated: - type: integer - format: int64 - example: 53 - nbTestCodesUsed: - type: integer - format: int64 - example: 52 - nbTestExpiredCodes: - type: integer - format: int64 - example: 40 - nbTestCodesGenerated: - type: integer - format: int64 - example: 60 nbJwtUsed: type: integer format: int64 example: 45 required: - date - - nbShortCodesUsed - - nbLongCodesUsed - - nbLongExpiredCodes - - nbShortExpiredCodes - - nbShortCodesGenerated - - nbTestCodesUsed - - nbTestExpiredCodes - - nbTestCodesGenerated + - nbJwtUsed diff --git a/src/test/kotlin/fr/gouv/stopc/submissioncode/GenerateAndVerifyCodeEnd2endTest.kt b/src/test/kotlin/fr/gouv/stopc/submissioncode/GenerateAndVerifyCodeEnd2endTest.kt deleted file mode 100644 index ce5b7fca77e9219dd58744d24202879735c67611..0000000000000000000000000000000000000000 --- a/src/test/kotlin/fr/gouv/stopc/submissioncode/GenerateAndVerifyCodeEnd2endTest.kt +++ /dev/null @@ -1,36 +0,0 @@ -package fr.gouv.stopc.submissioncode - -import fr.gouv.stopc.submissioncode.test.IntegrationTest -import fr.gouv.stopc.submissioncode.test.When -import io.restassured.RestAssured.given -import org.hamcrest.Matchers.equalTo -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import org.springframework.http.HttpStatus.OK - -@IntegrationTest -class GenerateAndVerifyCodeEnd2endTest { - - @ParameterizedTest - @ValueSource( - strings = [ - "/api/v1/generate/short", - "/api/v1/generate/test" - ] - ) - fun a_code_generated_with_the_api_can_be_verified(generatePath: String) { - val code = given() - .get(generatePath) - .then() - .extract() - .jsonPath() - .getString("code") - - When() - .get("/api/v1/verify?code={code}", code) - - .then() - .statusCode(OK.value()) - .body("valid", equalTo(true)) - } -} diff --git a/src/test/kotlin/fr/gouv/stopc/submissioncode/controller/GenerateControllerDuplicateRandomGenerationTest.kt b/src/test/kotlin/fr/gouv/stopc/submissioncode/controller/GenerateControllerDuplicateRandomGenerationTest.kt deleted file mode 100644 index c5392b897df996fac904617030338f565b36bb3d..0000000000000000000000000000000000000000 --- a/src/test/kotlin/fr/gouv/stopc/submissioncode/controller/GenerateControllerDuplicateRandomGenerationTest.kt +++ /dev/null @@ -1,92 +0,0 @@ -package fr.gouv.stopc.submissioncode.controller - -import fr.gouv.stopc.submissioncode.repository.model.SubmissionCode.Type.SHORT -import fr.gouv.stopc.submissioncode.repository.model.SubmissionCode.Type.TEST -import fr.gouv.stopc.submissioncode.service.RandomGenerator -import fr.gouv.stopc.submissioncode.test.IntegrationTest -import fr.gouv.stopc.submissioncode.test.PostgresqlManager.Companion.givenTableSubmissionCodeContainsCode -import fr.gouv.stopc.submissioncode.test.When -import fr.gouv.stopc.submissioncode.test.isoDateTimeWithin -import org.hamcrest.Matchers.equalTo -import org.junit.jupiter.api.Test -import org.mockito.Mockito.`when` -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.mock.mockito.MockBean -import org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR -import org.springframework.http.HttpStatus.OK -import java.time.Instant -import java.time.temporal.ChronoUnit.DAYS -import java.time.temporal.ChronoUnit.HOURS -import java.time.temporal.ChronoUnit.SECONDS - -@IntegrationTest -class GenerateControllerDuplicateRandomGenerationTest(@Autowired @MockBean private val randomGenerator: RandomGenerator) { - - @Test - fun can_generate_short_code_even_the_2_firsts_already_exists() { - givenTableSubmissionCodeContainsCode(SHORT.dbValue, "AAAAAA") - `when`(randomGenerator.upperCaseString(6)) - .thenReturn("AAAAAA", "AAAAAA", "BBBBBB") - - When() - .get("/api/v1/generate/short") - - .then() - .statusCode(OK.value()) - .body("code", equalTo("BBBBBB")) - .body("validFrom", isoDateTimeWithin(5, SECONDS, Instant.now())) - .body("validUntil", isoDateTimeWithin(5, SECONDS, Instant.now().plus(1, HOURS))) - .body("dateGenerate", isoDateTimeWithin(5, SECONDS, Instant.now())) - } - - @Test - fun can_generate_test_code_even_the_2_firsts_already_exists() { - givenTableSubmissionCodeContainsCode(TEST.dbValue, "AAAAAAAAAAAA") - `when`(randomGenerator.upperCaseString(12)) - .thenReturn("AAAAAAAAAAAA", "AAAAAAAAAAAA", "BBBBBBBBBBBB") - - When() - .get("/api/v1/generate/test") - - .then() - .statusCode(OK.value()) - .body("code", equalTo("BBBBBBBBBBBB")) - .body("validFrom", isoDateTimeWithin(5, SECONDS, Instant.now())) - .body("validUntil", isoDateTimeWithin(5, SECONDS, Instant.now().plus(15, DAYS).truncatedTo(DAYS))) - .body("dateGenerate", isoDateTimeWithin(5, SECONDS, Instant.now())) - } - - @Test - fun returns_server_error_after_10_generations_of_already_existing_short_codes() { - givenTableSubmissionCodeContainsCode(SHORT.dbValue, "AAAAAA") - `when`(randomGenerator.upperCaseString(6)) - .thenReturn("AAAAAA") - - When() - .get("/api/v1/generate/short") - - .then() - .statusCode(INTERNAL_SERVER_ERROR.value()) - .body("status", equalTo(500)) - .body("error", equalTo("Internal Server Error")) - .body("message", equalTo("No unique code could be generated after 10 random attempts")) - .body("path", equalTo("/api/v1/generate/short")) - } - - @Test - fun returns_server_error_after_10_generations_of_already_existing_test_codes() { - givenTableSubmissionCodeContainsCode(SHORT.dbValue, "AAAAAAAAAAAA") - `when`(randomGenerator.upperCaseString(12)) - .thenReturn("AAAAAAAAAAAA") - - When() - .get("/api/v1/generate/test") - - .then() - .statusCode(INTERNAL_SERVER_ERROR.value()) - .body("status", equalTo(500)) - .body("error", equalTo("Internal Server Error")) - .body("message", equalTo("No unique code could be generated after 10 random attempts")) - .body("path", equalTo("/api/v1/generate/test")) - } -} diff --git a/src/test/kotlin/fr/gouv/stopc/submissioncode/controller/GenerateControllerTest.kt b/src/test/kotlin/fr/gouv/stopc/submissioncode/controller/GenerateControllerTest.kt deleted file mode 100644 index fa01cff6c1c6411dad7be778adc9f47ec97fb085..0000000000000000000000000000000000000000 --- a/src/test/kotlin/fr/gouv/stopc/submissioncode/controller/GenerateControllerTest.kt +++ /dev/null @@ -1,74 +0,0 @@ -package fr.gouv.stopc.submissioncode.controller - -import fr.gouv.stopc.submissioncode.repository.SubmissionCodeRepository -import fr.gouv.stopc.submissioncode.test.IntegrationTest -import fr.gouv.stopc.submissioncode.test.When -import fr.gouv.stopc.submissioncode.test.isoDateTimeWithin -import org.exparity.hamcrest.date.InstantMatchers.within -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.hamcrest.Matchers.matchesPattern -import org.hamcrest.Matchers.notNullValue -import org.hamcrest.Matchers.nullValue -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import java.time.Instant.now -import java.time.temporal.ChronoUnit.DAYS -import java.time.temporal.ChronoUnit.HOURS -import java.time.temporal.ChronoUnit.SECONDS - -@IntegrationTest -class GenerateControllerTest(@Autowired val submissionCodeRepository: SubmissionCodeRepository) { - - @Test - fun can_generate_a_short_code() { - val shortCode = When() - .get("/api/v1/generate/short") - - .then() - .body("code", matchesPattern("[A-Z0-9]{6}")) - .body("validFrom", isoDateTimeWithin(5, SECONDS, now())) - .body("validUntil", isoDateTimeWithin(5, SECONDS, now().plus(1, HOURS))) - .body("dateGenerate", isoDateTimeWithin(5, SECONDS, now())) - - .extract() - .jsonPath() - .getString("code") - - val generatedCode = submissionCodeRepository.findByCode(shortCode)!! - assertThat(generatedCode.id, notNullValue()) - assertThat(generatedCode.code, matchesPattern("[A-Z0-9]{6}")) - assertThat(generatedCode.type, equalTo("2")) - assertThat(generatedCode.dateGeneration, within(5, SECONDS, now())) - assertThat(generatedCode.dateAvailable, within(5, SECONDS, now())) - assertThat(generatedCode.dateEndValidity, within(5, SECONDS, now().plus(1, HOURS))) - assertThat(generatedCode.dateUse, nullValue()) - assertThat(generatedCode.used, equalTo(false)) - } - - @Test - fun can_generate_a_test_code() { - val testCode = When() - .get("/api/v1/generate/test") - - .then() - .body("code", matchesPattern("[a-zA-Z0-9]{12}")) - .body("validFrom", isoDateTimeWithin(5, SECONDS, now())) - .body("validUntil", isoDateTimeWithin(5, SECONDS, now().plus(15, DAYS).truncatedTo(DAYS))) - .body("dateGenerate", isoDateTimeWithin(5, SECONDS, now())) - - .extract() - .jsonPath() - .getString("code") - - val generatedCode = submissionCodeRepository.findByCode(testCode)!! - assertThat(generatedCode.id, notNullValue()) - assertThat(generatedCode.code, matchesPattern("[a-zA-Z0-9]{12}")) - assertThat(generatedCode.type, equalTo("3")) - assertThat(generatedCode.dateGeneration, within(5, SECONDS, now())) - assertThat(generatedCode.dateAvailable, within(5, SECONDS, now())) - assertThat(generatedCode.dateEndValidity, within(5, SECONDS, now().plus(15, DAYS).truncatedTo(DAYS))) - assertThat(generatedCode.dateUse, nullValue()) - assertThat(generatedCode.used, equalTo(false)) - } -} diff --git a/src/test/kotlin/fr/gouv/stopc/submissioncode/controller/KpiControllerTest.kt b/src/test/kotlin/fr/gouv/stopc/submissioncode/controller/KpiControllerTest.kt index 9fdd3c1b36f6eaa78ec079598b0a1c55f1885c9e..dd242365f4b8a18766e447d451748c7da25206ac 100644 --- a/src/test/kotlin/fr/gouv/stopc/submissioncode/controller/KpiControllerTest.kt +++ b/src/test/kotlin/fr/gouv/stopc/submissioncode/controller/KpiControllerTest.kt @@ -20,15 +20,7 @@ class KpiControllerTest { .then() .statusCode(OK.value()) .body("[0].date", equalTo("2021-10-19")) - .body("[0].nbShortCodesUsed", equalTo(0)) - .body("[0].nbLongCodesUsed", equalTo(0)) - .body("[0].nbTestCodesUsed", equalTo(0)) .body("[0].nbJwtUsed", equalTo(0)) - .body("[0].nbLongExpiredCodes", equalTo(0)) - .body("[0].nbShortExpiredCodes", equalTo(0)) - .body("[0].nbTestExpiredCodes", equalTo(0)) - .body("[0].nbShortCodesGenerated", equalTo(0)) - .body("[0].nbTestCodesGenerated", equalTo(0)) .body("size()", equalTo(1)) } @@ -36,70 +28,6 @@ class KpiControllerTest { fun can_fetch_kpis(@Autowired jdbcTemplate: JdbcTemplate) { // ok 👌 this is ugly, // focus on describing the actual v1 behavior to have TNR and be able to rewrite the whole application - jdbcTemplate.execute( - """ - insert into submission_code(type_code, code, date_generation, date_available, date_end_validity, date_use, used) values - -- codes statistics for october 31: - ('2', 'AAAAA1', '2021-10-15', '2021-10-15', '2021-11-14', '2021-10-31', true), -- 2 used short codes - ('2', 'AAAAA2', '2021-10-15', '2021-10-15', '2021-11-14', '2021-10-31', true), - ('2', 'AAAAA3', '2021-10-15', '2021-10-15', '2021-10-31', null, false), -- 3 expired short codes - ('2', 'AAAAA4', '2021-10-15', '2021-10-15', '2021-10-31', null, false), - ('2', 'AAAAA5', '2021-10-15', '2021-10-15', '2021-10-31', null, false), - ('2', 'AAAAA6', '2021-10-31', '2021-10-31', '2021-11-14', null, false), -- 4 generated short codes - ('2', 'AAAAA7', '2021-10-31', '2021-10-31', '2021-11-14', null, false), - ('2', 'AAAAA8', '2021-10-31', '2021-10-31', '2021-11-14', null, false), - ('2', 'AAAAA9', '2021-10-31', '2021-10-31', '2021-11-14', null, false), - ('1', '00000000-0000-0000-0000-000000000001', '2021-10-15', '2021-10-15', '2021-11-14', '2021-10-31', true), -- 5 used long codes - ('1', '00000000-0000-0000-0000-000000000002', '2021-10-15', '2021-10-15', '2021-11-14', '2021-10-31', true), - ('1', '00000000-0000-0000-0000-000000000003', '2021-10-15', '2021-10-15', '2021-11-14', '2021-10-31', true), - ('1', '00000000-0000-0000-0000-000000000004', '2021-10-15', '2021-10-15', '2021-11-14', '2021-10-31', true), - ('1', '00000000-0000-0000-0000-000000000005', '2021-10-15', '2021-10-15', '2021-11-14', '2021-10-31', true), - ('1', '00000000-0000-0000-0000-000000000006', '2021-10-15', '2021-10-15', '2021-10-31', null, false), -- 6 expired long codes - ('1', '00000000-0000-0000-0000-000000000007', '2021-10-15', '2021-10-15', '2021-10-31', null, false), - ('1', '00000000-0000-0000-0000-000000000008', '2021-10-15', '2021-10-15', '2021-10-31', null, false), - ('1', '00000000-0000-0000-0000-000000000009', '2021-10-15', '2021-10-15', '2021-10-31', null, false), - ('1', '00000000-0000-0000-0000-000000000010', '2021-10-15', '2021-10-15', '2021-10-31', null, false), - ('1', '00000000-0000-0000-0000-000000000011', '2021-10-15', '2021-10-15', '2021-10-31', null, false), - ('3', 'AAAAAAAAAAA1', '2021-10-15', '2021-10-15', '2021-11-14', '2021-10-31', true), -- 3 used test codes - ('3', 'AAAAAAAAAAA2', '2021-10-15', '2021-10-15', '2021-11-14', '2021-10-31', true), - ('3', 'AAAAAAAAAAA3', '2021-10-15', '2021-10-15', '2021-11-14', '2021-10-31', true), - ('3', 'AAAAAAAAAAA4', '2021-10-15', '2021-10-15', '2021-10-31', null, false), -- 4 expired test codes - ('3', 'AAAAAAAAAAA5', '2021-10-15', '2021-10-15', '2021-10-31', null, false), - ('3', 'AAAAAAAAAAA6', '2021-10-15', '2021-10-15', '2021-10-31', null, false), - ('3', 'AAAAAAAAAAA7', '2021-10-15', '2021-10-15', '2021-10-31', null, false), - ('3', 'AAAAAAAAAAA8', '2021-10-31', '2021-10-31', '2021-11-14', null, false), -- 5 generated test codes - ('3', 'AAAAAAAAAAA9', '2021-10-31', '2021-10-31', '2021-11-14', null, false), - ('3', 'AAAAAAAAAA10', '2021-10-31', '2021-10-31', '2021-11-14', null, false), - ('3', 'AAAAAAAAAA11', '2021-10-31', '2021-10-31', '2021-11-14', null, false), - ('3', 'AAAAAAAAAA12', '2021-10-31', '2021-10-31', '2021-11-14', null, false), - -- codes statistics for november 1: - ('2', 'BBBBB1', '2021-10-15', '2021-10-15', '2021-11-14', '2021-11-01', true), -- 1 used short codes - ('2', 'BBBBB2', '2021-10-15', '2021-10-15', '2021-11-01', null, false), -- 2 expired short codes - ('2', 'BBBBB3', '2021-10-15', '2021-10-15', '2021-11-01', null, false), - ('2', 'BBBBB4', '2021-11-01', '2021-11-01', '2021-11-14', null, false), -- 3 generated short codes - ('2', 'BBBBB5', '2021-11-01', '2021-11-01', '2021-11-14', null, false), - ('2', 'BBBBB6', '2021-11-01', '2021-11-01', '2021-11-14', null, false), - ('1', '00000000-0000-0000-0001-000000000001', '2021-10-15', '2021-10-15', '2021-11-14', '2021-11-01', true), -- 4 used long codes - ('1', '00000000-0000-0000-0001-000000000002', '2021-10-15', '2021-10-15', '2021-11-14', '2021-11-01', true), - ('1', '00000000-0000-0000-0001-000000000003', '2021-10-15', '2021-10-15', '2021-11-14', '2021-11-01', true), - ('1', '00000000-0000-0000-0001-000000000004', '2021-10-15', '2021-10-15', '2021-11-14', '2021-11-01', true), - ('1', '00000000-0000-0000-0001-000000000006', '2021-10-15', '2021-10-15', '2021-11-01', null, false), -- 5 expired long codes - ('1', '00000000-0000-0000-0001-000000000007', '2021-10-15', '2021-10-15', '2021-11-01', null, false), - ('1', '00000000-0000-0000-0001-000000000008', '2021-10-15', '2021-10-15', '2021-11-01', null, false), - ('1', '00000000-0000-0000-0001-000000000009', '2021-10-15', '2021-10-15', '2021-11-01', null, false), - ('1', '00000000-0000-0000-0001-000000000010', '2021-10-15', '2021-10-15', '2021-11-01', null, false), - ('3', 'BBBBBBBBBBB1', '2021-10-15', '2021-10-15', '2021-11-14', '2021-11-01', true), -- 2 used test codes - ('3', 'BBBBBBBBBBB2', '2021-10-15', '2021-10-15', '2021-11-14', '2021-11-01', true), - ('3', 'BBBBBBBBBBB3', '2021-10-15', '2021-10-15', '2021-11-01', null, false), -- 3 expired test codes - ('3', 'BBBBBBBBBBB4', '2021-10-15', '2021-10-15', '2021-11-01', null, false), - ('3', 'BBBBBBBBBBB5', '2021-10-15', '2021-10-15', '2021-11-01', null, false), - ('3', 'BBBBBBBBBBB6', '2021-11-01', '2021-11-01', '2021-11-14', null, false), -- 4 generated test codes - ('3', 'BBBBBBBBBBB7', '2021-11-01', '2021-11-01', '2021-11-14', null, false), - ('3', 'BBBBBBBBBBB8', '2021-11-01', '2021-11-01', '2021-11-14', null, false), - ('3', 'BBBBBBBBBBB9', '2021-11-01', '2021-11-01', '2021-11-14', null, false) - """.trimIndent() - ) - jdbcTemplate.execute( """ insert into jwt_used(jti, date_use) values @@ -117,24 +45,8 @@ class KpiControllerTest { .then() .statusCode(OK.value()) .body("[0].date", equalTo("2021-10-31")) - .body("[0].nbShortCodesUsed", equalTo(2)) - .body("[0].nbShortExpiredCodes", equalTo(3)) - .body("[0].nbShortCodesGenerated", equalTo(4)) - .body("[0].nbLongCodesUsed", equalTo(5)) - .body("[0].nbLongExpiredCodes", equalTo(6)) - .body("[0].nbTestCodesUsed", equalTo(3)) - .body("[0].nbTestExpiredCodes", equalTo(4)) - .body("[0].nbTestCodesGenerated", equalTo(5)) .body("[0].nbJwtUsed", equalTo(1)) .body("[1].date", equalTo("2021-11-01")) - .body("[1].nbShortCodesUsed", equalTo(1)) - .body("[1].nbShortExpiredCodes", equalTo(2)) - .body("[1].nbShortCodesGenerated", equalTo(3)) - .body("[1].nbLongCodesUsed", equalTo(4)) - .body("[1].nbLongExpiredCodes", equalTo(5)) - .body("[1].nbTestCodesUsed", equalTo(2)) - .body("[1].nbTestExpiredCodes", equalTo(3)) - .body("[1].nbTestCodesGenerated", equalTo(4)) .body("[1].nbJwtUsed", equalTo(2)) .body("size()", equalTo(2)) } diff --git a/src/test/kotlin/fr/gouv/stopc/submissioncode/controller/VerifyControllerTest.kt b/src/test/kotlin/fr/gouv/stopc/submissioncode/controller/VerifyControllerTest.kt index ea09eba07bb425fccba8ec117b3e3b59c6082f94..8afc866fa2be34d7e45627a78dc81b074bf32f64 100644 --- a/src/test/kotlin/fr/gouv/stopc/submissioncode/controller/VerifyControllerTest.kt +++ b/src/test/kotlin/fr/gouv/stopc/submissioncode/controller/VerifyControllerTest.kt @@ -2,26 +2,20 @@ package fr.gouv.stopc.submissioncode.controller import com.nimbusds.jose.jwk.Curve import com.nimbusds.jose.jwk.gen.ECKeyGenerator -import fr.gouv.stopc.submissioncode.service.model.CodeType import fr.gouv.stopc.submissioncode.test.IntegrationTest import fr.gouv.stopc.submissioncode.test.JWTManager.Companion.givenJwt -import fr.gouv.stopc.submissioncode.test.PostgresqlManager.Companion.givenTableSubmissionCodeContainsCode import fr.gouv.stopc.submissioncode.test.When import io.restassured.RestAssured.given import org.assertj.core.api.Assertions.assertThat import org.hamcrest.Matchers.equalTo -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.CsvSource import org.junit.jupiter.params.provider.MethodSource -import org.junit.jupiter.params.provider.ValueSource import org.springframework.boot.test.system.CapturedOutput import org.springframework.boot.test.system.OutputCaptureExtension import org.springframework.http.HttpStatus.OK @@ -36,400 +30,207 @@ import java.util.stream.Stream @IntegrationTest @ExtendWith(OutputCaptureExtension::class) +@TestInstance(PER_CLASS) class VerifyControllerTest { - @BeforeEach - fun `given some valid codes exists`() { - givenTableSubmissionCodeContainsCode("1", "0000000a-0000-0000-0000-000000000000") - givenTableSubmissionCodeContainsCode("2", "AAAAAA") - givenTableSubmissionCodeContainsCode("3", "BBBBBBBBBBBB") - val expiredInstant = Instant.now().minusSeconds(1) - givenTableSubmissionCodeContainsCode("1", "00000000-1111-1111-1111-111111111111", expiresOn = expiredInstant) - givenTableSubmissionCodeContainsCode("2", "EXP000", expiresOn = expiredInstant) - givenTableSubmissionCodeContainsCode("3", "EXP000000000", expiresOn = expiredInstant) + private fun generateInvalidJwt(): Stream<Arguments> { + val tenDaysAgo = Instant.now().minus(10, DAYS).truncatedTo(SECONDS) + val oneMinuteAgo = Instant.now().minus(1, MINUTES).truncatedTo(SECONDS) + val oneMinuteLater = Instant.now().plus(1, MINUTES).truncatedTo(SECONDS) + return Stream.of( + Arguments.of( + "the JWT is issued more than 10 days in the past", + givenJwt(iat = tenDaysAgo.epochSecond), + "JWT has expired, it was issued at $tenDaysAgo, so it expired on ${tenDaysAgo.plus(Duration.ofDays(10))}:" + ), + Arguments.of( + "the JWT is issued in the future", + givenJwt(iat = oneMinuteLater.epochSecond), + "JWT is issued at a future time ($oneMinuteLater):" + ), + Arguments.of( + "the iat field is an empty string instead of a numeric Date", + givenJwt(iat = ""), + "JWT claims set could not be parsed: Unexpected type of JSON object member with key iat," + ), + Arguments.of( + "the iat field is a blank string instead of a numeric Date", + givenJwt(iat = " "), + "JWT claims set could not be parsed: Unexpected type of JSON object member with key iat," + ), + Arguments.of( + "the iat field is a string instead of a numeric Date", + givenJwt(iat = "123456"), + "JWT claims set could not be parsed: Unexpected type of JSON object member with key iat," + ), + Arguments.of( + "the iat field is missing", + givenJwt(jti = "some-jwt-identifier", iat = null), + "JWT is missing claim jti (some-jwt-identifier) or iat (null):" + ), + Arguments.of( + "the jti field is empty", + givenJwt(jti = "", iat = oneMinuteAgo.epochSecond), + "JWT is missing claim jti () or iat ($oneMinuteAgo):" + ), + Arguments.of( + "the jti field is blank", + givenJwt(jti = " ", iat = oneMinuteAgo.epochSecond), + "JWT is missing claim jti ( ) or iat ($oneMinuteAgo):" + ), + Arguments.of( + "the jti field is missing", + givenJwt(jti = null, iat = oneMinuteAgo.epochSecond), + "JWT is missing claim jti (null) or iat ($oneMinuteAgo):" + ), + Arguments.of( + "the jti field is a number instead of a string", + givenJwt(jti = 1234), + "JWT claims set could not be parsed: Unexpected type of JSON object member with key jti," + ), + Arguments.of( + "the kid field is empty", + givenJwt(kid = ""), + "No public key found in configuration for kid '':" + ), + Arguments.of( + "the kid field is blank", + givenJwt(kid = " "), + "No public key found in configuration for kid ' ':" + ), + Arguments.of( + "the kid field is missing", + givenJwt(kid = null), + "No public key found in configuration for kid 'null':" + ), + Arguments.of( + "the kid field is an unknown kid", + givenJwt(kid = "Unknown"), + "No public key found in configuration for kid 'Unknown':" + ), + Arguments.of( + "the kid field points to the wrong public key", + givenJwt(kid = "AnotherKID"), + "JWT signature is invalid:" + ), + Arguments.of( + "the alg field is missing", + givenJwt().replaceBefore(".", Base64.getEncoder().encodeToString("""{"kid": "TousAntiCovidKID","typ":"JWT"}""".toByteArray())), + "JWT could not be parsed: Invalid JWS header: Missing \"alg\" in header JSON object," + ), + Arguments.of( + "the JWT header is corrupt", + givenJwt().replaceBefore(".", "Z"), + "JWT could not be parsed: Invalid JWS header: Invalid JSON object," + ), + ) } - @ParameterizedTest - @ValueSource( - strings = [ - "0000000a-0000-0000-0000-000000000000", - "AAAAAA", - "BBBBBBBBBBBB" - ] - ) - fun can_detect_a_code_is_valid(validCode: String) { - When() - .get("/api/v1/verify?code={code}", validCode) + @Test + fun validate_a_valid_JWT() { - .then() - .statusCode(OK.value()) - .body("valid", equalTo(true)) - } + val validJwt = givenJwt() - @ParameterizedTest - @ValueSource( - strings = [ - "0000000A-0000-0000-0000-000000000000, LONG", - "aaaaaa, SHORT", - "bbbbbbbbbbbb, TEST" - ] - ) - fun codes_are_case_sensitive(validCodeButWrongCase: String) { When() - .get("/api/v1/verify?code={code}", validCodeButWrongCase) + .get("/api/v1/verify?code={jwt}", validJwt) .then() .statusCode(OK.value()) - .body("valid", equalTo(false)) + .body("valid", equalTo(true)) } - @ParameterizedTest - @CsvSource( - "00000000-1111-1111-1111-111111111110, LONG", - "AAA000, SHORT", - "BBBBBB000000, TEST" + @Test + fun reject_a_JWT_sign_with_unknown_private_key(output: CapturedOutput) { - ) - fun can_detect_a_code_doesnt_exist(unexistingCode: String, codeType: String, output: CapturedOutput) { - When() - .get("/api/v1/verify?code={code}", unexistingCode) + val unknownKeys = ECKeyGenerator(Curve.P_256) + .keyID("unknownKey") + .generate() - .then() - .statusCode(OK.value()) - .body("valid", equalTo(false)) + val jwtWithUnknownSignature = givenJwt(privateKey = unknownKeys.toECPrivateKey()) - assertThat(output.all) - .contains("Code $unexistingCode doesn't exist") - } - - @ParameterizedTest - @CsvSource( - "LONG,00000000-1111-1111-1111-111111111111", - "SHORT,EXP000", - "TEST,EXP000000000" - ) - fun can_detect_a_code_is_expired(codeType: String, expiredCode: String, output: CapturedOutput) { When() - .get("/api/v1/verify?code={code}", expiredCode) + .get("/api/v1/verify?code={jwt}", jwtWithUnknownSignature) .then() .statusCode(OK.value()) .body("valid", equalTo(false)) assertThat(output.all) - .contains("$codeType code '$expiredCode' has expired on ") + .contains("JWT signature is invalid: $jwtWithUnknownSignature") } - @ParameterizedTest - @CsvSource( - "LONG,f1111111-1111-1111-1111-111111111111", - "SHORT,FUT000", - "TEST,FUT000000000" - ) - fun can_detect_a_code_is_not_yet_available(codeType: CodeType, unavailableCode: String, output: CapturedOutput) { - givenTableSubmissionCodeContainsCode( - codeType.databaseRepresentation!!.dbValue, - unavailableCode, - availableFrom = Instant.now().plus(20, DAYS) - ) + @DisplayName("A valid JWT must have a iat field as a Date, a unique jti field as a string, a kid field as a string which value is associated to a public key corresponding to the private key used to signed the JWT and is valid for 10 days ") + @ParameterizedTest(name = "but {0}, so the JWT is not valid and the response is false") + @MethodSource("generateInvalidJwt") + fun reject_JWT_with_invalid_value_or_structure(title: String, serializedJwt: String, logMessage: String, output: CapturedOutput) { When() - .get("/api/v1/verify?code={code}", unavailableCode) + .get("/api/v1/verify?code={jwt}", serializedJwt) .then() .statusCode(OK.value()) .body("valid", equalTo(false)) assertThat(output.all) - .contains("$codeType code '$unavailableCode' is not yet available (availability date is ") + .contains("$logMessage $serializedJwt") } - @ParameterizedTest - @CsvSource( - "0000000a-0000-0000-0000-000000000000, LONG", - "AAAAAA, SHORT", - "BBBBBBBBBBBB, TEST" + @Test + fun reject_a_JWT_with_jti_already_used(output: CapturedOutput) { + + val jti = UUID.randomUUID().toString() + val validJwt = givenJwt(jti = jti) - ) - fun a_code_can_be_used_only_once(validCode: String, codeType: String, output: CapturedOutput) { - given() // a code has been used - .get("/api/v1/verify?code={code}", validCode) + given() // jwt is used once + .get("/api/v1/verify?code={jwt}", validJwt) - When() // it is used one more time - .get("/api/v1/verify?code={code}", validCode) + When() // jwt is used one more time + .get("/api/v1/verify?code={jwt}", validJwt) .then() // it should be rejected .statusCode(OK.value()) .body("valid", equalTo(false)) assertThat(output.all) - .contains("$codeType code '$validCode' has already been used") + .contains("JWT with jti '$jti' has already been used: $validJwt") } - @ParameterizedTest - @ValueSource( - strings = [ - "", - " ", - "____", - "0000000a0000000000000000000000000000", - "AAAAA@", - "BBBBBBBBBBB+", - "aaaaaaaa.aaaaaaaa", - ] - ) - fun can_reject_codes_not_matching_pattern(codeWithWrongPattern: String, output: CapturedOutput) { + @Test + fun reject_a_code_that_is_not_a_valid_jwt(output: CapturedOutput) { When() - .get("/api/v1/verify?code={code}", codeWithWrongPattern) + .get("/api/v1/verify?code={code}", "aaaaaaa.aaaaaaa.aaaaaaa") .then() .statusCode(OK.value()) .body("valid", equalTo(false)) assertThat(output.all) - .contains("Code $codeWithWrongPattern does not match any code type and can't be validated") + .contains("JWT could not be parsed: Invalid JWS header: Invalid JSON: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path \$, aaaaaaa.aaaaaaa.aaaaaaa") } - @ParameterizedTest - @ValueSource( - strings = [ - "unknow", - "unknownunkno", - "0000000b-0000-0000-0000-000000000000" - ] - ) - fun can_reject_codes_unknown_from_database(unknownCode: String, output: CapturedOutput) { - When() - .get("/api/v1/verify?code={code}", unknownCode) - - .then() - .statusCode(OK.value()) - .body("valid", equalTo(false)) + @Test + fun reject_a_JWT_with_invalid_alg_header_field(output: CapturedOutput) { - assertThat(output.all) - .contains("Code $unknownCode doesn't exist") - } + val invalidHeader = """ + { + "alg": "invalid alg", + "typ": "JWT", + "kid": "TousAntiCovidKID" + } + """.trimIndent() + .toByteArray() + .let { Base64.getEncoder().encodeToString(it) } - @Test - fun can_report_inconsistent_code_in_database(output: CapturedOutput) { - // "2" is SHORT code type - // "AAAAAA123456" is a TEST code value - givenTableSubmissionCodeContainsCode("2", "AAAAAA123456") + val jwtWithInvalidHeader = givenJwt().replaceBefore(".", invalidHeader) When() - .get("/api/v1/verify?code={code}", "AAAAAA123456") + .get("/api/v1/verify?code={jwt}", jwtWithInvalidHeader) .then() .statusCode(OK.value()) .body("valid", equalTo(false)) assertThat(output.all) - .contains("'AAAAAA123456' seems to be a TEST code but database knows it as a SHORT code") - } - - @TestInstance(PER_CLASS) - @Nested - inner class JwtTest { - - private fun generateInvalidJwt(): Stream<Arguments> { - val tenDaysAgo = Instant.now().minus(10, DAYS).truncatedTo(SECONDS) - val oneMinuteAgo = Instant.now().minus(1, MINUTES).truncatedTo(SECONDS) - val oneMinuteLater = Instant.now().plus(1, MINUTES).truncatedTo(SECONDS) - return Stream.of( - Arguments.of( - "the JWT is issued more than 10 days in the past", - givenJwt(iat = tenDaysAgo.epochSecond), - "JWT has expired, it was issued at $tenDaysAgo, so it expired on ${tenDaysAgo.plus(Duration.ofDays(10))}:" - ), - Arguments.of( - "the JWT is issued in the future", - givenJwt(iat = oneMinuteLater.epochSecond), - "JWT is issued at a future time ($oneMinuteLater):" - ), - Arguments.of( - "the iat field is an empty string instead of a numeric Date", - givenJwt(iat = ""), - "JWT claims set could not be parsed: Unexpected type of JSON object member with key iat," - ), - Arguments.of( - "the iat field is a blank string instead of a numeric Date", - givenJwt(iat = " "), - "JWT claims set could not be parsed: Unexpected type of JSON object member with key iat," - ), - Arguments.of( - "the iat field is a string instead of a numeric Date", - givenJwt(iat = "123456"), - "JWT claims set could not be parsed: Unexpected type of JSON object member with key iat," - ), - Arguments.of( - "the iat field is missing", - givenJwt(jti = "some-jwt-identifier", iat = null), - "JWT is missing claim jti (some-jwt-identifier) or iat (null):" - ), - Arguments.of( - "the jti field is empty", - givenJwt(jti = "", iat = oneMinuteAgo.epochSecond), - "JWT is missing claim jti () or iat ($oneMinuteAgo):" - ), - Arguments.of( - "the jti field is blank", - givenJwt(jti = " ", iat = oneMinuteAgo.epochSecond), - "JWT is missing claim jti ( ) or iat ($oneMinuteAgo):" - ), - Arguments.of( - "the jti field is missing", - givenJwt(jti = null, iat = oneMinuteAgo.epochSecond), - "JWT is missing claim jti (null) or iat ($oneMinuteAgo):" - ), - Arguments.of( - "the jti field is a number instead of a string", - givenJwt(jti = 1234), - "JWT claims set could not be parsed: Unexpected type of JSON object member with key jti," - ), - Arguments.of( - "the kid field is empty", - givenJwt(kid = ""), - "No public key found in configuration for kid '':" - ), - Arguments.of( - "the kid field is blank", - givenJwt(kid = " "), - "No public key found in configuration for kid ' ':" - ), - Arguments.of( - "the kid field is missing", - givenJwt(kid = null), - "No public key found in configuration for kid 'null':" - ), - Arguments.of( - "the kid field is an unknown kid", - givenJwt(kid = "Unknown"), - "No public key found in configuration for kid 'Unknown':" - ), - Arguments.of( - "the kid field points to the wrong public key", - givenJwt(kid = "AnotherKID"), - "JWT signature is invalid:" - ), - Arguments.of( - "the alg field is missing", - givenJwt().replaceBefore(".", Base64.getEncoder().encodeToString("""{"kid": "TousAntiCovidKID","typ":"JWT"}""".toByteArray())), - "JWT could not be parsed: Invalid JWS header: Missing \"alg\" in header JSON object," - ), - Arguments.of( - "the JWT header is corrupt", - givenJwt().replaceBefore(".", "Z"), - "JWT could not be parsed: Invalid JWS header: Invalid JSON object," - ), - ) - } - - @Test - fun validate_a_valid_JWT() { - - val validJwt = givenJwt() - - When() - .get("/api/v1/verify?code={jwt}", validJwt) - - .then() - .statusCode(OK.value()) - .body("valid", equalTo(true)) - } - - @Test - fun reject_a_JWT_sign_with_unknown_private_key(output: CapturedOutput) { - - val unknownKeys = ECKeyGenerator(Curve.P_256) - .keyID("unknownKey") - .generate() - - val jwtWithUnknownSignature = givenJwt(privateKey = unknownKeys.toECPrivateKey()) - - When() - .get("/api/v1/verify?code={jwt}", jwtWithUnknownSignature) - - .then() - .statusCode(OK.value()) - .body("valid", equalTo(false)) - - assertThat(output.all) - .contains("JWT signature is invalid: $jwtWithUnknownSignature") - } - - @DisplayName("A valid JWT must have a iat field as a Date, a unique jti field as a string, a kid field as a string which value is associated to a public key corresponding to the private key used to signed the JWT and is valid for 10 days ") - @ParameterizedTest(name = "but {0}, so the JWT is not valid and the response is false") - @MethodSource("generateInvalidJwt") - fun reject_JWT_with_invalid_value_or_structure(title: String, serializedJwt: String, logMessage: String, output: CapturedOutput) { - - When() - .get("/api/v1/verify?code={jwt}", serializedJwt) - - .then() - .statusCode(OK.value()) - .body("valid", equalTo(false)) - - assertThat(output.all) - .contains("$logMessage $serializedJwt") - } - - @Test - fun reject_a_JWT_with_jti_already_used(output: CapturedOutput) { - - val jti = UUID.randomUUID().toString() - val validJwt = givenJwt(jti = jti) - - given() // jwt is used once - .get("/api/v1/verify?code={jwt}", validJwt) - - When() // jwt is used one more time - .get("/api/v1/verify?code={jwt}", validJwt) - - .then() // it should be rejected - .statusCode(OK.value()) - .body("valid", equalTo(false)) - - assertThat(output.all) - .contains("JWT with jti '$jti' has already been used: $validJwt") - } - - @Test - fun reject_a_code_that_is_not_a_valid_jwt(output: CapturedOutput) { - When() - .get("/api/v1/verify?code={code}", "aaaaaaa.aaaaaaa.aaaaaaa") - - .then() - .statusCode(OK.value()) - .body("valid", equalTo(false)) - - assertThat(output.all) - .contains("JWT could not be parsed: Invalid JWS header: Invalid JSON: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path \$, aaaaaaa.aaaaaaa.aaaaaaa") - } - - @Test - fun reject_a_JWT_with_invalid_alg_header_field(output: CapturedOutput) { - - val invalidHeader = """ - { - "alg": "invalid alg", - "typ": "JWT", - "kid": "TousAntiCovidKID" - } - """.trimIndent() - .toByteArray() - .let { Base64.getEncoder().encodeToString(it) } - - val jwtWithInvalidHeader = givenJwt().replaceBefore(".", invalidHeader) - - When() - .get("/api/v1/verify?code={jwt}", jwtWithInvalidHeader) - - .then() - .statusCode(OK.value()) - .body("valid", equalTo(false)) - - assertThat(output.all) - .contains("JWT signature can't be verified: Unsupported JWS algorithm invalid alg, must be ES256, $jwtWithInvalidHeader") - } + .contains("JWT signature can't be verified: Unsupported JWS algorithm invalid alg, must be ES256, $jwtWithInvalidHeader") } } diff --git a/src/test/kotlin/fr/gouv/stopc/submissioncode/service/MetricsServiceTest.kt b/src/test/kotlin/fr/gouv/stopc/submissioncode/service/MetricsServiceTest.kt index 38ddeeda6d117260659bf83b49bc946bcece68f6..61ae8bb864f38c1381e42491b2fd8373f0870684 100644 --- a/src/test/kotlin/fr/gouv/stopc/submissioncode/service/MetricsServiceTest.kt +++ b/src/test/kotlin/fr/gouv/stopc/submissioncode/service/MetricsServiceTest.kt @@ -3,48 +3,12 @@ package fr.gouv.stopc.submissioncode.service import fr.gouv.stopc.submissioncode.test.IntegrationTest import fr.gouv.stopc.submissioncode.test.JWTManager.Companion.givenJwt import fr.gouv.stopc.submissioncode.test.MetricsManager.Companion.assertThatMetricCounterIncrement -import fr.gouv.stopc.submissioncode.test.PostgresqlManager.Companion.givenTableSubmissionCodeContainsCode import fr.gouv.stopc.submissioncode.test.When -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.CsvSource -import java.time.Instant @IntegrationTest class MetricsServiceTest { - @BeforeEach - fun `given some valid codes exists`() { - givenTableSubmissionCodeContainsCode("1", "0000000a-0000-0000-0000-000000000000") - givenTableSubmissionCodeContainsCode("3", "BBBBBBBBBBBB") - givenTableSubmissionCodeContainsCode("2", "AAAAAA") - val expiredInstant = Instant.now().minusSeconds(1) - givenTableSubmissionCodeContainsCode("1", "0000000a-0000-0000-0000-000000000001", expiresOn = expiredInstant) - givenTableSubmissionCodeContainsCode("3", "BBBBBBBBBBBA", expiresOn = expiredInstant) - givenTableSubmissionCodeContainsCode("2", "AAAAAB", expiresOn = expiredInstant) - } - - @ParameterizedTest - @CsvSource( - "AAAAAA, AAAAAB, SHORT", - "BBBBBBBBBBBB, BBBBBBBBBBBA, TEST", - "0000000a-0000-0000-0000-000000000000, 0000000a-0000-0000-0000-000000000001, LONG" - ) - fun can_increment_codes_counters(validCode: String, invalidCode: String, codeType: String) { - - When() - .get("/api/v1/verify?code={code}", validCode) - - When() - .get("/api/v1/verify?code={code}", invalidCode) - - assertThatMetricCounterIncrement("submission.verify.code", "valid", "true", "code type", codeType) - .isEqualTo(1.0) - assertThatMetricCounterIncrement("submission.verify.code", "valid", "false", "code type", codeType) - .isEqualTo(1.0) - } - @Test fun can_increment_jwt_counters() { @@ -61,9 +25,9 @@ class MetricsServiceTest { When() .get("/api/v1/verify?code={jwt}", invalidJwt2) - assertThatMetricCounterIncrement("submission.verify.code", "valid", "true", "code type", "JWT") + assertThatMetricCounterIncrement("submission.verify.code", "valid", "true") .isEqualTo(1.0) - assertThatMetricCounterIncrement("submission.verify.code", "valid", "false", "code type", "JWT") + assertThatMetricCounterIncrement("submission.verify.code", "valid", "false") .isEqualTo(2.0) } } diff --git a/src/test/kotlin/fr/gouv/stopc/submissioncode/test/JWTManager.kt b/src/test/kotlin/fr/gouv/stopc/submissioncode/test/JWTManager.kt index 30a640effe9c6ad0be5bf7a5fbad77a0ba6146f9..50b464c83cdc0a4ad4cb77e75e3a3285235db708 100644 --- a/src/test/kotlin/fr/gouv/stopc/submissioncode/test/JWTManager.kt +++ b/src/test/kotlin/fr/gouv/stopc/submissioncode/test/JWTManager.kt @@ -9,7 +9,6 @@ import com.nimbusds.jose.jwk.ECKey import com.nimbusds.jose.jwk.gen.ECKeyGenerator import com.nimbusds.jwt.JWTClaimsSet import com.nimbusds.jwt.SignedJWT -import groovy.util.logging.Slf4j import org.springframework.test.context.TestExecutionListener import java.security.NoSuchAlgorithmException import java.security.interfaces.ECPrivateKey @@ -17,7 +16,6 @@ import java.time.Instant import java.util.Base64 import java.util.UUID -@Slf4j class JWTManager : TestExecutionListener { companion object { diff --git a/src/test/kotlin/fr/gouv/stopc/submissioncode/test/PostgresqlManager.kt b/src/test/kotlin/fr/gouv/stopc/submissioncode/test/PostgresqlManager.kt index 45ed222bc1fdce643f97a5bf0c7ac2444cb7aa40..421a36e96acbcd57ca8117e906cdc9bef2cbfd01 100644 --- a/src/test/kotlin/fr/gouv/stopc/submissioncode/test/PostgresqlManager.kt +++ b/src/test/kotlin/fr/gouv/stopc/submissioncode/test/PostgresqlManager.kt @@ -5,8 +5,6 @@ import org.springframework.jdbc.core.JdbcTemplate import org.springframework.test.context.TestContext import org.springframework.test.context.TestExecutionListener import org.testcontainers.containers.PostgreSQLContainer -import java.time.Instant -import java.time.temporal.ChronoUnit.MINUTES class PostgresqlManager : TestExecutionListener { @@ -22,21 +20,6 @@ class PostgresqlManager : TestExecutionListener { System.setProperty("spring.datasource.password", password) } } - - fun givenTableSubmissionCodeContainsCode( - type: String, - code: String, - generatedOn: Instant = Instant.now(), - availableFrom: Instant = Instant.now(), - expiresOn: Instant = Instant.now().plus(5, MINUTES) - ) { - JDBC_TEMPLATE.execute( - """ - insert into submission_code(type_code, code, date_generation, date_available, date_end_validity, used) values - ('$type', '$code', '$generatedOn', '$availableFrom', '$expiresOn', false) - """.trimIndent() - ) - } } override fun beforeTestMethod(testContext: TestContext) { diff --git a/src/test/kotlin/fr/gouv/stopc/submissioncode/test/SubmissionCodeFiles.kt b/src/test/kotlin/fr/gouv/stopc/submissioncode/test/SubmissionCodeFiles.kt deleted file mode 100644 index dc3721bd11d240812a96547db3e417c4458783b1..0000000000000000000000000000000000000000 --- a/src/test/kotlin/fr/gouv/stopc/submissioncode/test/SubmissionCodeFiles.kt +++ /dev/null @@ -1,45 +0,0 @@ -package fr.gouv.stopc.submissioncode.test - -import fr.gouv.stopc.submissioncode.test.LongCodesCsvFile.LongCodeLine -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream -import java.io.ByteArrayInputStream -import java.util.zip.GZIPInputStream - -data class NamedContentFile(val name: String, val content: ByteArray) { - override fun toString(): String { - return "NamedContentFile(name=$name, content=${content.size} bytes)" - } -} - -fun readTarGzEntries(tarGzArchive: ByteArray): List<NamedContentFile> { - return TarArchiveInputStream(GZIPInputStream(ByteArrayInputStream(tarGzArchive))) - .use { tar -> - generateSequence { tar.nextTarEntry } - .map { entry -> NamedContentFile(entry.name, tar.readAllBytes()) } - .toList() - } -} - -class LongCodesCsvFile(csvFileContent: String) : Iterable<LongCodeLine> { - val header: String - private val codes: List<LongCodeLine> - - init { - header = csvFileContent.lines().first() - codes = csvFileContent - .removeSuffix("\n") - .lines() - .drop(1) - .map { it.removePrefix("\"").removeSuffix("\"").split("\",\"") } - .map { LongCodeLine(link = it[0], code = it[1], start = it[2], end = it[3]) } - } - - data class LongCodeLine( - val link: String, - val code: String, - val start: String, - val end: String - ) - - override fun iterator() = codes.iterator() -}