diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1b1e336b8..04d757722 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -19,6 +19,16 @@ plugins { group = "pro.qyoga" version = "0.0.1-SNAPSHOT" +configurations.matching { it.name == "compileClasspath" }.all { + exclude(group = "com.fasterxml.jackson.core", module = "jackson-core") + exclude(group = "com.fasterxml.jackson.core", module = "jackson-databind") +} + +configurations.matching { it.name in setOf("testCompileClasspath", "testFixturesCompileClasspath") }.all { + exclude(group = "com.fasterxml.jackson") + exclude(group = "com.fasterxml.jackson.core") +} + dependencies { implementation(kotlin("reflect")) implementation("org.springframework.boot:spring-boot-starter-data-jdbc") @@ -29,11 +39,12 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-thymeleaf") implementation("org.springframework.boot:spring-boot-starter-cache") + implementation("org.springframework.boot:spring-boot-starter-flyway") implementation(libs.caffeine) - implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("tools.jackson.module:jackson-module-kotlin") implementation("org.flywaydb:flyway-database-postgresql") - implementation(libs.jackarta.validation) + implementation("org.springframework.boot:spring-boot-starter-validation") implementation(libs.thymeleaf.extras.java8time) implementation(libs.postgres) implementation(libs.minio) @@ -49,6 +60,9 @@ dependencies { implementation(libs.bouncycastle) developmentOnly("org.springframework.boot:spring-boot-docker-compose") + runtimeOnly(platform("com.fasterxml.jackson:jackson-bom:2.20.2")) + runtimeOnly("com.fasterxml.jackson.core:jackson-core") + runtimeOnly("com.fasterxml.jackson.core:jackson-databind") testFixturesApi("org.springframework.boot:spring-boot-testcontainers") testFixturesApi(testLibs.kotest.assertions) @@ -71,18 +85,22 @@ dependencies { testFixturesImplementation("org.springframework.boot:spring-boot-starter-web") testFixturesImplementation("org.springframework.boot:spring-boot-starter-security") testFixturesImplementation("org.springframework.boot:spring-boot-starter-oauth2-client") - testFixturesImplementation("com.fasterxml.jackson.core:jackson-databind") + testFixturesImplementation(enforcedPlatform(testLibs.jetty)) + testFixturesImplementation(enforcedPlatform(testLibs.jetty.ee10)) + testFixturesImplementation("tools.jackson.core:jackson-databind") testFixturesImplementation(libs.minio) testFixturesImplementation(libs.ical4j) testFixturesImplementation("org.springframework.boot:spring-boot-starter-test") testFixturesImplementation("org.springframework.security:spring-security-test") testFixturesImplementation("org.springframework.boot:spring-boot-starter-webflux") - testFixturesImplementation("org.testcontainers:junit-jupiter") - testFixturesImplementation("org.testcontainers:postgresql") + testFixturesImplementation("org.testcontainers:testcontainers-junit-jupiter") + testFixturesImplementation("org.testcontainers:testcontainers-postgresql") testFixturesImplementation(testLibs.testcontainers.minio) testImplementation(testFixtures(project(":app"))) + testImplementation(enforcedPlatform(testLibs.jetty)) + testImplementation(enforcedPlatform(testLibs.jetty.ee10)) testImplementation(testLibs.bundles.restassured) testImplementation(testLibs.archunit) testImplementation(testLibs.mockito.kotlin) @@ -220,7 +238,7 @@ configurations.matching { it.name == "detekt" }.all { resolutionStrategy.eachDependency { if (requested.group == "org.jetbrains.kotlin") { @Suppress("UnstableApiUsage") - useVersion(io.gitlab.arturbosch.detekt.getSupportedKotlinVersion()) + useVersion(dev.detekt.gradle.plugin.getSupportedKotlinVersion()) } } } diff --git a/app/src/main/kotlin/pro/azhidkov/platform/file_storage/internal/FilesMetaDataRepo.kt b/app/src/main/kotlin/pro/azhidkov/platform/file_storage/internal/FilesMetaDataRepo.kt index 6f0b14da9..c59856c2b 100644 --- a/app/src/main/kotlin/pro/azhidkov/platform/file_storage/internal/FilesMetaDataRepo.kt +++ b/app/src/main/kotlin/pro/azhidkov/platform/file_storage/internal/FilesMetaDataRepo.kt @@ -1,10 +1,10 @@ package pro.azhidkov.platform.file_storage.internal +import org.springframework.data.core.TypeInformation import org.springframework.data.jdbc.core.JdbcAggregateOperations import org.springframework.data.jdbc.core.convert.JdbcConverter import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository import org.springframework.data.mapping.model.BasicPersistentEntity -import org.springframework.data.util.TypeInformation import org.springframework.stereotype.Repository import pro.azhidkov.platform.file_storage.api.FileMetaData diff --git a/app/src/main/kotlin/pro/azhidkov/platform/spring/jdbc/RowMapperExt.kt b/app/src/main/kotlin/pro/azhidkov/platform/spring/jdbc/RowMapperExt.kt index 8bf5831ad..18af3090c 100644 --- a/app/src/main/kotlin/pro/azhidkov/platform/spring/jdbc/RowMapperExt.kt +++ b/app/src/main/kotlin/pro/azhidkov/platform/spring/jdbc/RowMapperExt.kt @@ -1,15 +1,15 @@ package pro.azhidkov.platform.spring.jdbc -import com.fasterxml.jackson.databind.ObjectMapper import org.springframework.core.convert.support.DefaultConversionService import org.springframework.jdbc.core.DataClassRowMapper import org.springframework.jdbc.core.RowMapper import pro.azhidkov.platform.spring.sdj.converters.PGIntervalToDurationConverter import pro.azhidkov.platform.spring.sdj.converters.StringToSecretChars import pro.azhidkov.platform.spring.sdj.converters.UuidToAggregateReferenceConverter +import tools.jackson.databind.ObjectMapper -inline fun rowMapperFor(objectMapper: ObjectMapper, columnName: String? = null) = RowMapper { rs, _ -> +inline fun rowMapperFor(objectMapper: ObjectMapper, columnName: String? = null) = RowMapper { rs, _ -> val json: String? = if (columnName != null) { rs.getString(columnName) } else { @@ -18,10 +18,11 @@ inline fun rowMapperFor(objectMapper: ObjectMapper, columnName: Stri json?.let { objectMapper.readValue(it, T::class.java) } } -inline fun taDataClassRowMapper() = DataClassRowMapper.newInstance(T::class.java).apply { - conversionService = DefaultConversionService().apply { +inline fun taDataClassRowMapper(): DataClassRowMapper = + DataClassRowMapper.newInstance(T::class.java).apply { + conversionService = DefaultConversionService().apply { addConverter(PGIntervalToDurationConverter()) addConverter(UuidToAggregateReferenceConverter) addConverter(StringToSecretChars()) + } } -} diff --git a/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/AggregateReferenceDeserializer.kt b/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/AggregateReferenceDeserializer.kt index 7fc076e60..e3786d571 100644 --- a/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/AggregateReferenceDeserializer.kt +++ b/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/AggregateReferenceDeserializer.kt @@ -1,22 +1,21 @@ package pro.azhidkov.platform.spring.sdj -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.* -import com.fasterxml.jackson.databind.deser.ContextualDeserializer -import com.fasterxml.jackson.databind.type.TypeFactory import org.springframework.data.jdbc.core.mapping.AggregateReference import pro.azhidkov.platform.spring.sdj.ergo.hydration.AggregateReferenceTarget import pro.azhidkov.platform.spring.sdj.ergo.hydration.Identifiable +import tools.jackson.core.JsonParser +import tools.jackson.databind.* +import tools.jackson.databind.type.TypeFactory import kotlin.reflect.full.memberProperties import kotlin.reflect.jvm.jvmErasure class AggregateReferenceDeserializer( private var type: JavaType = TypeFactory.unknownType() -) : JsonDeserializer>(), ContextualDeserializer { +) : ValueDeserializer>() { override fun deserialize(parser: JsonParser, context: DeserializationContext): AggregateReference<*, *>? { - val node = parser.codec.readTree(parser) + val node = parser.readValueAsTree() val propertyNames = node.properties().map { it.key }.toSet() return if (propertyNames == setOf(ID_FIELD_NAME)) { val idType = getIdType(type) @@ -28,7 +27,7 @@ class AggregateReferenceDeserializer( } } - override fun createContextual(ctx: DeserializationContext, property: BeanProperty): JsonDeserializer<*> { + override fun createContextual(ctx: DeserializationContext, property: BeanProperty): ValueDeserializer<*> { return AggregateReferenceDeserializer(property.type.containedType(0)) } @@ -44,4 +43,4 @@ private fun getIdType(type: JavaType): Class = .first { it.name == AggregateReferenceDeserializer.ID_FIELD_NAME } .returnType .jvmErasure - .java \ No newline at end of file + .java diff --git a/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/ErgoSdjConfig.kt b/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/ErgoSdjConfig.kt index f28d1ddbf..33a01b78e 100644 --- a/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/ErgoSdjConfig.kt +++ b/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/ErgoSdjConfig.kt @@ -1,11 +1,10 @@ package pro.azhidkov.platform.spring.sdj -import com.fasterxml.jackson.databind.module.SimpleModule import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.data.jdbc.core.mapping.AggregateReference -import pro.azhidkov.platform.spring.sdj.ergo.ErgoPersistenceExceptionTranslator -import com.fasterxml.jackson.databind.Module as JacksonModule +import tools.jackson.databind.JacksonModule +import tools.jackson.databind.module.SimpleModule @Configuration @@ -21,8 +20,4 @@ class ErgoSdjConfig { fun aggregateReferenceBindingAdvice() = AggregateReferenceBindingAdvice() - @Bean - fun persistenceExceptionTranslator() = - ErgoPersistenceExceptionTranslator() - -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/PageExt.kt b/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/PageExt.kt index 4b213dc97..65b991539 100644 --- a/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/PageExt.kt +++ b/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/PageExt.kt @@ -4,6 +4,6 @@ import org.springframework.data.domain.Page import org.springframework.data.domain.PageImpl import org.springframework.data.domain.PageRequest -fun Page.mapContent(f: (MutableList) -> List): Page { +fun Page.mapContent(f: (List) -> List): Page { return PageImpl(f(this.content), PageRequest.of(this.number, this.size), this.totalElements) } diff --git a/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/converters/ObjectToJsonbConverters.kt b/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/converters/ObjectToJsonbConverters.kt index d8a7210ec..9dd02bf15 100644 --- a/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/converters/ObjectToJsonbConverters.kt +++ b/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/converters/ObjectToJsonbConverters.kt @@ -1,10 +1,10 @@ package pro.azhidkov.platform.spring.sdj.converters -import com.fasterxml.jackson.databind.ObjectMapper import org.postgresql.util.PGobject import org.springframework.core.convert.converter.Converter import org.springframework.data.convert.ReadingConverter import org.springframework.data.convert.WritingConverter +import tools.jackson.databind.ObjectMapper import kotlin.reflect.KClass @@ -12,7 +12,7 @@ import kotlin.reflect.KClass fun interface ObjectToJsonbWriter : Converter @ReadingConverter -fun interface JsonbToObjectReader : Converter +fun interface JsonbToObjectReader : Converter abstract class JacksonObjectToJsonbWriter( private val objectMapper: ObjectMapper diff --git a/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/ergo/ErgoPersistenceExceptionTranslator.kt b/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/ergo/ErgoPersistenceExceptionTranslator.kt deleted file mode 100644 index 09828d986..000000000 --- a/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/ergo/ErgoPersistenceExceptionTranslator.kt +++ /dev/null @@ -1,15 +0,0 @@ -package pro.azhidkov.platform.spring.sdj.ergo - -import org.springframework.dao.DataAccessException -import org.springframework.dao.support.PersistenceExceptionTranslator -import org.springframework.data.relational.core.conversion.DbActionExecutionException -import org.springframework.stereotype.Component - - -@Component -class ErgoPersistenceExceptionTranslator : PersistenceExceptionTranslator { - - override fun translateExceptionIfPossible(ex: RuntimeException): DataAccessException? = - (ex as? DbActionExecutionException)?.cause as? DataAccessException - -} \ No newline at end of file diff --git a/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/ergo/ErgoRepository.kt b/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/ergo/ErgoRepository.kt index adc50d1fc..cca05b6c6 100644 --- a/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/ergo/ErgoRepository.kt +++ b/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/ergo/ErgoRepository.kt @@ -6,7 +6,6 @@ import org.springframework.data.jdbc.core.JdbcAggregateOperations import org.springframework.data.jdbc.core.convert.EntityRowMapper import org.springframework.data.jdbc.core.convert.JdbcConverter import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository -import org.springframework.data.relational.core.conversion.DbActionExecutionException import org.springframework.data.relational.core.mapping.RelationalMappingContext import org.springframework.data.relational.core.mapping.RelationalPersistentEntity import org.springframework.data.relational.core.query.Query @@ -48,7 +47,7 @@ class ErgoRepository( protected fun saveAndMapDuplicatedKeyException(aggregate: T, map: (DuplicateKeyException) -> Throwable): S { val res = runCatching { super.save(aggregate) } - when (val ex = (res.exceptionOrNull() as? DbActionExecutionException)?.cause as? DuplicateKeyException) { + when (val ex = res.exceptionOrNull() as? DuplicateKeyException) { is DuplicateKeyException -> throw map(ex) } @@ -162,13 +161,19 @@ class ErgoRepository( queryBuilder: QueryBuilder.() -> Unit = {} ): Page { val query = query(queryBuilder) - val res = jdbcAggregateTemplate.findAll(query, relationalPersistentEntity.type, pageRequest) - return res.mapContent { - jdbcAggregateTemplate.hydrate( - res, - FetchSpec(fetch) - ) - } + + val content = jdbcAggregateTemplate.findAll(query.with(pageRequest), relationalPersistentEntity.type) + .let { jdbcAggregateTemplate.hydrate(it, FetchSpec(fetch)) } + + val total = jdbcAggregateTemplate.count(query, relationalPersistentEntity.type) + + val page = PageImpl( + content, + pageRequest, + total + ) + + return page } fun findSlice( @@ -263,4 +268,4 @@ class ErgoRepository( @Suppress("UNCHECKED_CAST") private fun RelationalMappingContext.getRelationPersistentEntity( type: KClass -): RelationalPersistentEntity = getRequiredPersistentEntity(type.java) as RelationalPersistentEntity \ No newline at end of file +): RelationalPersistentEntity = getRequiredPersistentEntity(type.java) as RelationalPersistentEntity diff --git a/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/ergo/hydration/Hydration.kt b/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/ergo/hydration/Hydration.kt index 225587f1e..148332f8b 100644 --- a/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/ergo/hydration/Hydration.kt +++ b/app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/ergo/hydration/Hydration.kt @@ -11,7 +11,7 @@ import kotlin.reflect.jvm.jvmErasure data class PropertyFetchSpec( val property: KProperty1, - val fetchSpec: FetchSpec<*> = FetchSpec( + val fetchSpec: FetchSpec = FetchSpec( emptyList>() ) ) @@ -42,15 +42,14 @@ fun JdbcAggregateOperations.hydrate( return (entities as? List) ?: entities.toList() } - val refs: Map?>, Map> = + val refs: Map, Map> = fetchSpec.propertyFetchSpecs.filter { detectRefType( it.property ) != null } .associate { it: PropertyFetchSpec -> - val property = it.property as KProperty1<*, AggregateReference<*, *>?> - property to fetchPropertyRefs(entities, it) + it.property to fetchPropertyRefs(entities, it) } if (refs.isEmpty()) { @@ -69,11 +68,19 @@ private fun JdbcAggregateOperations.fetchPropertyRefs( val property = propertyFetchSpec.property val ids = fetchIds(entities, property) val targetType = (property.returnType.arguments[0].type!!.classifier!! as KClass<*>).java - val refs = hydrate(this.findAllById(ids, targetType), propertyFetchSpec.fetchSpec as FetchSpec) + val refs = hydrateAny(this.findAllById(ids, targetType), propertyFetchSpec.fetchSpec) .associateBy { (it as Identifiable<*>).id } return refs } +@Suppress("UNCHECKED_CAST") +private fun JdbcAggregateOperations.hydrateAny( + entities: Iterable<*>, + fetchSpec: FetchSpec +): List { + return hydrate(entities as Iterable, fetchSpec as FetchSpec) +} + private fun fetchIds( entities: Iterable, property: KProperty1 @@ -87,12 +94,12 @@ private fun fetchIds( else -> error("Unsupported property type: $property") } -private fun hydrateEntity(entity: T, refs: Map?>, Map>): T { +private fun hydrateEntity(entity: T, refs: Map, Map>): T { val constructorParams = entity::class.primaryConstructor!!.parameters val paramValues = constructorParams.associateWith { param -> val prop = - entity::class.memberProperties.find { prop -> param.name == prop.name }!! as KProperty1?> - val currentValue: Any? = prop.invoke(entity) + entity::class.memberProperties.find { prop -> param.name == prop.name }!! + val currentValue = prop.getter.call(entity) val newValue = if (prop in refs) { val id = (currentValue as AggregateReference<*, *>?)?.id if (id != null) { diff --git a/app/src/main/kotlin/pro/azhidkov/platform/spring/tx/TransactionTemplateExt.kt b/app/src/main/kotlin/pro/azhidkov/platform/spring/tx/TransactionTemplateExt.kt index 27568727a..bdec826bf 100644 --- a/app/src/main/kotlin/pro/azhidkov/platform/spring/tx/TransactionTemplateExt.kt +++ b/app/src/main/kotlin/pro/azhidkov/platform/spring/tx/TransactionTemplateExt.kt @@ -7,11 +7,9 @@ interface TransactionalService { val transactionTemplate: TransactionTemplate fun transaction(body: () -> T): T { - val res = transactionTemplate.execute { + return transactionTemplate.execute { body() } - @Suppress("UNCHECKED_CAST") - return res as T } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/pro/qyoga/app/infra/MultipartSizeLimitExceptionFilter.kt b/app/src/main/kotlin/pro/qyoga/app/infra/MultipartSizeLimitExceptionFilter.kt new file mode 100644 index 000000000..d5de60014 --- /dev/null +++ b/app/src/main/kotlin/pro/qyoga/app/infra/MultipartSizeLimitExceptionFilter.kt @@ -0,0 +1,57 @@ +package pro.qyoga.app.infra + +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException +import org.slf4j.LoggerFactory +import org.springframework.core.Ordered +import org.springframework.core.annotation.Order +import org.springframework.http.HttpStatus +import org.springframework.stereotype.Component +import org.springframework.web.filter.OncePerRequestFilter +import org.springframework.web.multipart.MaxUploadSizeExceededException + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +class MultipartSizeLimitExceptionFilter : OncePerRequestFilter() { + + private val log = LoggerFactory.getLogger(javaClass) + + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + try { + filterChain.doFilter(request, response) + } catch (ex: Exception) { + if (!isMultipartSizeLimitExceeded(ex)) { + throw ex + } + + log.info("Rejected oversized multipart request: {} {}", request.method, request.requestURI, ex) + + if (response.isCommitted) { + throw ex + } + + response.resetBuffer() + response.status = HttpStatus.PAYLOAD_TOO_LARGE.value() + } + } + + private fun isMultipartSizeLimitExceeded(ex: Throwable): Boolean { + var current: Throwable? = ex + + while (current != null) { + if (current is MaxUploadSizeExceededException || current is SizeLimitExceededException) { + current.printStackTrace() + return true + } + current = current.cause + } + + return false + } +} diff --git a/app/src/main/kotlin/pro/qyoga/app/infra/WebSecurityConfig.kt b/app/src/main/kotlin/pro/qyoga/app/infra/WebSecurityConfig.kt index 8cff83cd8..206e4fca2 100644 --- a/app/src/main/kotlin/pro/qyoga/app/infra/WebSecurityConfig.kt +++ b/app/src/main/kotlin/pro/qyoga/app/infra/WebSecurityConfig.kt @@ -116,7 +116,10 @@ class WebSecurityConfig( @Bean fun tokenRepository(): PersistentTokenRepository { val jdbcTokenRepositoryImpl = JdbcTokenRepositoryImpl() - jdbcTokenRepositoryImpl.dataSource = dataSource + // JdbcDaoSupport хоть и помечен для удаления, но иного способа датасорс сейчас нет + // property access syntax почему-то перестал комплироваться после переезда на Spring Boot 4 + @Suppress("removal", "UsePropertyAccessSyntax") + jdbcTokenRepositoryImpl.setDataSource(dataSource) return jdbcTokenRepositoryImpl } diff --git a/app/src/main/kotlin/pro/qyoga/app/therapist/appointments/core/edit/ops/UpdateAppointmentOp.kt b/app/src/main/kotlin/pro/qyoga/app/therapist/appointments/core/edit/ops/UpdateAppointmentOp.kt index b747b68d6..590d51013 100644 --- a/app/src/main/kotlin/pro/qyoga/app/therapist/appointments/core/edit/ops/UpdateAppointmentOp.kt +++ b/app/src/main/kotlin/pro/qyoga/app/therapist/appointments/core/edit/ops/UpdateAppointmentOp.kt @@ -38,9 +38,9 @@ class UpdateAppointmentOp( val typeRef = appointmentTypesRepo.createTypeIfNew(therapistRef, editAppointmentRequest) - return appointmentsRepo.updateById(appointmentRef.id!!) { appointment -> + return appointmentsRepo.updateById(appointmentRef.id) { appointment -> appointment.patchBy(editAppointmentRequest, typeRef) } } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/pro/qyoga/core/appointments/core/Transformations.kt b/app/src/main/kotlin/pro/qyoga/core/appointments/core/Transformations.kt index 7d6d32df7..7b32add84 100644 --- a/app/src/main/kotlin/pro/qyoga/core/appointments/core/Transformations.kt +++ b/app/src/main/kotlin/pro/qyoga/core/appointments/core/Transformations.kt @@ -38,7 +38,7 @@ fun Appointment.toEditRequest(resolveTimeZone: (ZoneId) -> LocalizedTimeZone?) = clientRef, clientRef.resolveOrNull()?.fullName() ?: clientRef.id.toString(), typeRef, - typeRef.resolveOrNull()?.name ?: typeRef.id?.toString() ?: "", + typeRef.resolveOrNull()?.name ?: typeRef.id.toString(), therapeuticTaskRef, therapeuticTaskRef?.resolveOrNull()?.name ?: therapeuticTaskRef?.id?.toString() ?: "", wallClockDateTime, @@ -50,4 +50,4 @@ fun Appointment.toEditRequest(resolveTimeZone: (ZoneId) -> LocalizedTimeZone?) = payed, status, comment -) \ No newline at end of file +) diff --git a/app/src/main/kotlin/pro/qyoga/core/calendar/api/CalendarItem.kt b/app/src/main/kotlin/pro/qyoga/core/calendar/api/CalendarItem.kt index ee0cf0d19..0a3ea9f41 100644 --- a/app/src/main/kotlin/pro/qyoga/core/calendar/api/CalendarItem.kt +++ b/app/src/main/kotlin/pro/qyoga/core/calendar/api/CalendarItem.kt @@ -55,7 +55,7 @@ interface CalendarItemId { .buildAndExpand(type.name) .toUri() - fun toMap(): Map + fun toMap(): Map } diff --git a/app/src/main/kotlin/pro/qyoga/core/clients/cards/ClientsRepo.kt b/app/src/main/kotlin/pro/qyoga/core/clients/cards/ClientsRepo.kt index 80d57b02c..ab18e2458 100644 --- a/app/src/main/kotlin/pro/qyoga/core/clients/cards/ClientsRepo.kt +++ b/app/src/main/kotlin/pro/qyoga/core/clients/cards/ClientsRepo.kt @@ -38,7 +38,7 @@ class ClientsRepo( val topFiveByLastName = PageRequest.of(0, 5, sortBy(Client::lastName)) } - override fun save(instance: S & Any): S & Any { + override fun save(instance: S): S { return saveAndMapDuplicatedKeyException(instance) { ex -> DuplicatedPhoneException(instance, ex) } diff --git a/app/src/main/kotlin/pro/qyoga/core/clients/files/ClientFilesService.kt b/app/src/main/kotlin/pro/qyoga/core/clients/files/ClientFilesService.kt index 4c3c38514..d213b948d 100644 --- a/app/src/main/kotlin/pro/qyoga/core/clients/files/ClientFilesService.kt +++ b/app/src/main/kotlin/pro/qyoga/core/clients/files/ClientFilesService.kt @@ -40,7 +40,7 @@ class ClientFilesService( val clientFile = clientFilesRepo.findFile(clientId, fileId) ?: return null - return clientFilesStorage.findByIdOrNull(clientFile.fileRef.id!!) + return clientFilesStorage.findByIdOrNull(clientFile.fileRef.id) } fun deleteFile(clientId: UUID, fileId: Long): ClientFile? { @@ -50,7 +50,7 @@ class ClientFilesService( clientFilesRepo.deleteById(clientFile.id) try { - clientFilesStorage.deleteById(clientFile.fileRef.id!!) + clientFilesStorage.deleteById(clientFile.fileRef.id) } catch (ex: Exception) { log.warn("Client file deletion failed", ex) } @@ -58,4 +58,4 @@ class ClientFilesService( return clientFile } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/pro/qyoga/core/clients/journals/JournalEntriesRepo.kt b/app/src/main/kotlin/pro/qyoga/core/clients/journals/JournalEntriesRepo.kt index 09ab57da6..d4bd47050 100644 --- a/app/src/main/kotlin/pro/qyoga/core/clients/journals/JournalEntriesRepo.kt +++ b/app/src/main/kotlin/pro/qyoga/core/clients/journals/JournalEntriesRepo.kt @@ -33,7 +33,7 @@ class JournalEntriesRepo( ) { @Transactional - override fun save(instance: S & Any): S & Any { + override fun save(instance: S): S { return saveAndMapDuplicatedKeyException(instance) { ex -> DuplicatedDate(instance, ex) } @@ -60,4 +60,4 @@ class JournalEntriesRepo( } } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/pro/qyoga/core/survey_forms/settings/model/SurveyFormsSettings.kt b/app/src/main/kotlin/pro/qyoga/core/survey_forms/settings/model/SurveyFormsSettings.kt index 64b1ba4a0..bcf979c96 100644 --- a/app/src/main/kotlin/pro/qyoga/core/survey_forms/settings/model/SurveyFormsSettings.kt +++ b/app/src/main/kotlin/pro/qyoga/core/survey_forms/settings/model/SurveyFormsSettings.kt @@ -18,7 +18,7 @@ data class SurveyFormsSettings( ) : Identifiable { @Transient - override val id: UUID = therapistRef.id!! + override val id: UUID = therapistRef.id companion object { @@ -30,4 +30,4 @@ data class SurveyFormsSettings( } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/pro/qyoga/core/therapy/exercises/ExercisesServiceImpl.kt b/app/src/main/kotlin/pro/qyoga/core/therapy/exercises/ExercisesServiceImpl.kt index ef7b0e273..6cbe3d489 100644 --- a/app/src/main/kotlin/pro/qyoga/core/therapy/exercises/ExercisesServiceImpl.kt +++ b/app/src/main/kotlin/pro/qyoga/core/therapy/exercises/ExercisesServiceImpl.kt @@ -40,7 +40,7 @@ class ExercisesService( val stepIdxToStepImageId = exerciseStepsImagesStorage.uploadAllStepImages(stepImages) .mapValues { it.value.id } - val exercise = Exercise.of(createExerciseRequest, stepIdxToStepImageId, therapistRef.id!!) + val exercise = Exercise.of(createExerciseRequest, stepIdxToStepImageId, therapistRef.id) return exercisesRepo.save(exercise) } @@ -86,7 +86,7 @@ class ExercisesService( val imageId = exercise.steps[stepIdx].imageId ?: return null - return exerciseStepsImagesStorage.findByIdOrNull(imageId.id!!) + return exerciseStepsImagesStorage.findByIdOrNull(imageId.id) } fun deleteById(exerciseId: Long) { @@ -121,4 +121,4 @@ fun FilesStorage.uploadAllStepImages(stepImages: Map): Map stepIdx to persistedImage } return stepIdxToStepImage -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/pro/qyoga/core/therapy/exercises/model/ExerciseType.kt b/app/src/main/kotlin/pro/qyoga/core/therapy/exercises/model/ExerciseType.kt index 1b6f029c7..0f67391a5 100644 --- a/app/src/main/kotlin/pro/qyoga/core/therapy/exercises/model/ExerciseType.kt +++ b/app/src/main/kotlin/pro/qyoga/core/therapy/exercises/model/ExerciseType.kt @@ -2,9 +2,8 @@ package pro.qyoga.core.therapy.exercises.model import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonFormat -import com.fasterxml.jackson.databind.JsonNode import pro.azhidkov.platform.kotlin.LabeledEnum -import pro.qyoga.core.therapy.exercises.model.ExerciseType.entries +import tools.jackson.databind.JsonNode @JsonFormat(shape = JsonFormat.Shape.OBJECT) @@ -22,11 +21,11 @@ enum class ExerciseType(override val label: String) : LabeledEnum { @JsonCreator @JvmStatic fun ofName(node: JsonNode): ExerciseType? { - require(node.isTextual || node.isObject) { "Создать экземпляр ExerciseType можно только из строки или объекта" } - val name = if (node.isTextual) node.textValue() else node["name"].asText() + require(node.isString || node.isObject) { "Создать экземпляр ExerciseType можно только из строки или объекта" } + val name = if (node.isString) node.stringValue() else node["name"].stringValue() return entries.find { it.name == name } } } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/pro/qyoga/core/therapy/programs/ProgramsRepo.kt b/app/src/main/kotlin/pro/qyoga/core/therapy/programs/ProgramsRepo.kt index 9a29ae7a9..fc4965a32 100644 --- a/app/src/main/kotlin/pro/qyoga/core/therapy/programs/ProgramsRepo.kt +++ b/app/src/main/kotlin/pro/qyoga/core/therapy/programs/ProgramsRepo.kt @@ -1,6 +1,5 @@ package pro.qyoga.core.therapy.programs -import com.fasterxml.jackson.databind.ObjectMapper import org.intellij.lang.annotations.Language import org.springframework.data.domain.Page import org.springframework.data.domain.PageRequest @@ -16,6 +15,7 @@ import pro.qyoga.core.therapy.programs.dtos.ProgramsSearchFilter import pro.qyoga.core.therapy.programs.model.DocxProgram import pro.qyoga.core.therapy.programs.model.Program import pro.qyoga.core.therapy.programs.views.ProgramSummaryView +import tools.jackson.databind.ObjectMapper import kotlin.reflect.KProperty1 @Repository @@ -112,4 +112,4 @@ fun ProgramsRepo.getSummaryById(programId: Long): ProgramSummaryView? { """.trimIndent() return findOne(query, mapOf("id" to programId), programSummaryViewRowMapper) -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/pro/qyoga/core/therapy/therapeutic_tasks/TherapeuticTasksRepo.kt b/app/src/main/kotlin/pro/qyoga/core/therapy/therapeutic_tasks/TherapeuticTasksRepo.kt index add1f3b53..95466547a 100644 --- a/app/src/main/kotlin/pro/qyoga/core/therapy/therapeutic_tasks/TherapeuticTasksRepo.kt +++ b/app/src/main/kotlin/pro/qyoga/core/therapy/therapeutic_tasks/TherapeuticTasksRepo.kt @@ -1,5 +1,6 @@ package pro.qyoga.core.therapy.therapeutic_tasks +import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable import org.springframework.data.domain.Slice import org.springframework.data.jdbc.core.JdbcAggregateOperations @@ -31,12 +32,12 @@ class TherapeuticTasksRepo( ) { object Page { - val topFiveByName = Pageable.ofSize(5).withSortBy(TherapeuticTask::name) - val topTenByName = Pageable.ofSize(10).withSortBy(TherapeuticTask::name) + val topFiveByName: PageRequest = Pageable.ofSize(5).withSortBy(TherapeuticTask::name) + val topTenByName: PageRequest = Pageable.ofSize(10).withSortBy(TherapeuticTask::name) } @Transactional - override fun save(instance: S & Any): S & Any { + override fun save(instance: S): S { return saveAndMapDuplicatedKeyException(instance) { ex -> DuplicatedTherapeuticTaskName(instance, ex) } diff --git a/app/src/main/kotlin/pro/qyoga/core/users/auth/UsersFactory.kt b/app/src/main/kotlin/pro/qyoga/core/users/auth/UsersFactory.kt index 7b1225f90..7dd33a098 100644 --- a/app/src/main/kotlin/pro/qyoga/core/users/auth/UsersFactory.kt +++ b/app/src/main/kotlin/pro/qyoga/core/users/auth/UsersFactory.kt @@ -12,8 +12,8 @@ class UsersFactory( ) { fun createUser(email: String, plainPassword: CharSequence, roles: Set): User { - val passwordHash = passwordEncoder.encode(plainPassword) + val passwordHash = passwordEncoder.encode(plainPassword)!! return User(email, passwordHash, roles.toTypedArray(), enabled = true) } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/pro/qyoga/core/users/auth/UsersRepo.kt b/app/src/main/kotlin/pro/qyoga/core/users/auth/UsersRepo.kt index 85b032827..bbc3cc41a 100644 --- a/app/src/main/kotlin/pro/qyoga/core/users/auth/UsersRepo.kt +++ b/app/src/main/kotlin/pro/qyoga/core/users/auth/UsersRepo.kt @@ -26,7 +26,7 @@ class UsersRepo( ) { @Transactional - override fun save(instance: S & Any): S & Any { + override fun save(instance: S): S { return saveAndMapDuplicatedKeyException(instance) { ex -> DuplicatedEmailException(instance, ex) } @@ -38,4 +38,4 @@ class UsersRepo( } } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/pro/qyoga/core/users/auth/model/User.kt b/app/src/main/kotlin/pro/qyoga/core/users/auth/model/User.kt index a9a4eafda..f2124e4cf 100644 --- a/app/src/main/kotlin/pro/qyoga/core/users/auth/model/User.kt +++ b/app/src/main/kotlin/pro/qyoga/core/users/auth/model/User.kt @@ -13,7 +13,7 @@ import java.util.* typealias UserRef = AggregateReference -fun UserRef(therapistRef: TherapistRef): UserRef = AggregateReference.to(therapistRef.id!!) +fun UserRef(therapistRef: TherapistRef): UserRef = AggregateReference.to(therapistRef.id) @Table("users") data class User( @@ -66,4 +66,4 @@ data class User( return result } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/pro/qyoga/core/users/therapists/TherapistsRepo.kt b/app/src/main/kotlin/pro/qyoga/core/users/therapists/TherapistsRepo.kt index d8a74eb2a..c3fec988a 100644 --- a/app/src/main/kotlin/pro/qyoga/core/users/therapists/TherapistsRepo.kt +++ b/app/src/main/kotlin/pro/qyoga/core/users/therapists/TherapistsRepo.kt @@ -1,10 +1,10 @@ package pro.qyoga.core.users.therapists +import org.springframework.data.core.TypeInformation import org.springframework.data.jdbc.core.JdbcAggregateTemplate import org.springframework.data.jdbc.core.convert.JdbcConverter import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository import org.springframework.data.mapping.model.BasicPersistentEntity -import org.springframework.data.util.TypeInformation import org.springframework.stereotype.Repository @@ -16,4 +16,4 @@ class TherapistsRepo( jdbcAggregateTemplate, BasicPersistentEntity(TypeInformation.of(Therapist::class.java)), jdbcConverter -) \ No newline at end of file +) diff --git a/app/src/main/kotlin/pro/qyoga/i9ns/calendars/google/client/GoogleCalendarsClient.kt b/app/src/main/kotlin/pro/qyoga/i9ns/calendars/google/client/GoogleCalendarsClient.kt index e8edd1969..678f937fe 100644 --- a/app/src/main/kotlin/pro/qyoga/i9ns/calendars/google/client/GoogleCalendarsClient.kt +++ b/app/src/main/kotlin/pro/qyoga/i9ns/calendars/google/client/GoogleCalendarsClient.kt @@ -11,7 +11,7 @@ import com.google.auth.http.HttpCredentialsAdapter import com.google.auth.oauth2.UserCredentials import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Value -import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties +import org.springframework.boot.security.oauth2.client.autoconfigure.OAuth2ClientProperties import org.springframework.cache.annotation.Cacheable import org.springframework.http.HttpStatus import org.springframework.stereotype.Component diff --git a/app/src/main/kotlin/pro/qyoga/i9ns/calendars/ical/ICalCalendarsRepo.kt b/app/src/main/kotlin/pro/qyoga/i9ns/calendars/ical/ICalCalendarsRepo.kt index 02242cb5a..8e904a77c 100644 --- a/app/src/main/kotlin/pro/qyoga/i9ns/calendars/ical/ICalCalendarsRepo.kt +++ b/app/src/main/kotlin/pro/qyoga/i9ns/calendars/ical/ICalCalendarsRepo.kt @@ -50,9 +50,7 @@ class ICalCalendarsRepo( eventId: ICalEventId ): CalendarItem? { return iCalCalendarsDao.findAllByOwner(therapistRef) - .asSequence() - .mapNotNull { it.findById(eventId) } - .firstOrNull() + .firstNotNullOfOrNull { it.findById(eventId) } ?.toICalCalendarItem() } diff --git a/app/src/main/kotlin/pro/qyoga/i9ns/calendars/ical/model/ICalEventId.kt b/app/src/main/kotlin/pro/qyoga/i9ns/calendars/ical/model/ICalEventId.kt index c844724ab..08a851393 100644 --- a/app/src/main/kotlin/pro/qyoga/i9ns/calendars/ical/model/ICalEventId.kt +++ b/app/src/main/kotlin/pro/qyoga/i9ns/calendars/ical/model/ICalEventId.kt @@ -1,5 +1,6 @@ package pro.qyoga.i9ns.calendars.ical.model +import pro.azhidkov.platform.kotlin.mapOfNotNull import pro.qyoga.core.calendar.api.CalendarItemId import pro.qyoga.core.calendar.api.CalendarType @@ -11,9 +12,9 @@ data class ICalEventId( override val type: CalendarType = ICalCalendar.Type - override fun toMap(): Map = mapOf( + override fun toMap(): Map = mapOfNotNull( "uid" to uid, - "rid" to recurrenceId + recurrenceId?.let { "rid" to recurrenceId } ) } diff --git a/app/src/main/kotlin/pro/qyoga/i9ns/pushes/web/WebPushSubscriptionsRepo.kt b/app/src/main/kotlin/pro/qyoga/i9ns/pushes/web/WebPushSubscriptionsRepo.kt index a591748d3..560dedd2b 100644 --- a/app/src/main/kotlin/pro/qyoga/i9ns/pushes/web/WebPushSubscriptionsRepo.kt +++ b/app/src/main/kotlin/pro/qyoga/i9ns/pushes/web/WebPushSubscriptionsRepo.kt @@ -1,12 +1,12 @@ package pro.qyoga.i9ns.pushes.web -import com.fasterxml.jackson.databind.ObjectMapper import org.springframework.data.jdbc.core.JdbcAggregateTemplate import org.springframework.jdbc.core.simple.JdbcClient import org.springframework.stereotype.Repository import pro.azhidkov.platform.spring.sdj.query.query import pro.qyoga.core.users.therapists.TherapistRef import pro.qyoga.i9ns.pushes.web.model.TherapistWebPushSubscription +import tools.jackson.databind.ObjectMapper @Repository diff --git a/app/src/main/kotlin/pro/qyoga/i9ns/pushes/web/WebPushesConf.kt b/app/src/main/kotlin/pro/qyoga/i9ns/pushes/web/WebPushesConf.kt index cc762ac58..693136393 100644 --- a/app/src/main/kotlin/pro/qyoga/i9ns/pushes/web/WebPushesConf.kt +++ b/app/src/main/kotlin/pro/qyoga/i9ns/pushes/web/WebPushesConf.kt @@ -1,6 +1,5 @@ package pro.qyoga.i9ns.pushes.web -import com.fasterxml.jackson.databind.ObjectMapper import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean import org.springframework.context.annotation.ComponentScan @@ -8,6 +7,7 @@ import org.springframework.context.annotation.Configuration import pro.azhidkov.platform.spring.sdj.converters.ModuleConverters import pro.azhidkov.platform.spring.sdj.converters.ObjectToJsonbConverters.convertersFor import pro.qyoga.i9ns.pushes.web.model.WebPushSubscription +import tools.jackson.databind.ObjectMapper @Configuration diff --git a/app/src/main/kotlin/pro/qyoga/infra/web/ThymeleafConfig.kt b/app/src/main/kotlin/pro/qyoga/infra/web/ThymeleafConfig.kt index 4a165a844..7b7f5c5b3 100644 --- a/app/src/main/kotlin/pro/qyoga/infra/web/ThymeleafConfig.kt +++ b/app/src/main/kotlin/pro/qyoga/infra/web/ThymeleafConfig.kt @@ -1,12 +1,12 @@ package pro.qyoga.infra.web -import com.fasterxml.jackson.databind.ObjectMapper import jakarta.annotation.PostConstruct import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Lazy import org.thymeleaf.spring6.SpringTemplateEngine import org.thymeleaf.standard.StandardDialect import org.thymeleaf.standard.serializer.IStandardJavaScriptSerializer +import tools.jackson.databind.ObjectMapper @Configuration @@ -29,4 +29,4 @@ class ThymeleafConfig( } } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/pro/qyoga/infra/web/WebConfig.kt b/app/src/main/kotlin/pro/qyoga/infra/web/WebConfig.kt index 14238c6e7..a7fc12e00 100644 --- a/app/src/main/kotlin/pro/qyoga/infra/web/WebConfig.kt +++ b/app/src/main/kotlin/pro/qyoga/infra/web/WebConfig.kt @@ -10,10 +10,10 @@ class WebConfig( private val handlerMethodArgumentResolvers: List ) : WebMvcConfigurer { - override fun addArgumentResolvers(argumentResolvers: MutableList) { + override fun addArgumentResolvers(argumentResolvers: MutableList) { handlerMethodArgumentResolvers.forEach { argumentResolvers.add(it) } } -} \ No newline at end of file +} diff --git a/app/src/test/kotlin/pro/qyoga/tests/cases/app/auth/AuthTests.kt b/app/src/test/kotlin/pro/qyoga/tests/cases/app/auth/AuthTests.kt index 46fd6246d..2032c3455 100644 --- a/app/src/test/kotlin/pro/qyoga/tests/cases/app/auth/AuthTests.kt +++ b/app/src/test/kotlin/pro/qyoga/tests/cases/app/auth/AuthTests.kt @@ -1,6 +1,5 @@ package pro.qyoga.tests.cases.app.auth -import com.fasterxml.jackson.databind.JsonNode import io.kotest.matchers.shouldNotBe import io.kotest.matchers.types.shouldBeInstanceOf import io.restassured.http.ContentType @@ -27,6 +26,7 @@ import pro.qyoga.tests.fixture.object_mothers.therapists.THE_THERAPIST_PASSWORD import pro.qyoga.tests.infra.web.QYogaAppIntegrationBaseTest import pro.qyoga.tests.pages.publc.LoginPage import pro.qyoga.tests.pages.therapist.clients.ClientsListPage +import tools.jackson.databind.JsonNode import java.time.Instant import java.time.temporal.ChronoUnit import java.util.* diff --git a/app/src/test/kotlin/pro/qyoga/tests/cases/app/publc/surveys/SubmitSurveyTest.kt b/app/src/test/kotlin/pro/qyoga/tests/cases/app/publc/surveys/SubmitSurveyTest.kt index f042764cd..5076bfc4f 100644 --- a/app/src/test/kotlin/pro/qyoga/tests/cases/app/publc/surveys/SubmitSurveyTest.kt +++ b/app/src/test/kotlin/pro/qyoga/tests/cases/app/publc/surveys/SubmitSurveyTest.kt @@ -1,8 +1,5 @@ package pro.qyoga.tests.cases.app.publc.surveys -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.node.ObjectNode -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import io.kotest.core.annotation.DisplayName import io.kotest.inspectors.forAll import io.kotest.matchers.shouldBe @@ -22,19 +19,22 @@ import pro.qyoga.tests.fixture.object_mothers.survey_forms.SurveyFormsSettingsOb import pro.qyoga.tests.fixture.object_mothers.therapists.THE_ADMIN_LOGIN import pro.qyoga.tests.fixture.object_mothers.therapists.THE_THERAPIST_REF import pro.qyoga.tests.infra.web.QYogaAppIntegrationBaseKoTest -import pro.qyoga.tests.infra.web.mainWebTestClient +import pro.qyoga.tests.infra.web.mainRestTestClient +import tools.jackson.databind.JsonNode +import tools.jackson.databind.node.ObjectNode +import tools.jackson.module.kotlin.jacksonObjectMapper @DisplayName("Операция создания анкеты") class SubmitSurveyTest : QYogaAppIntegrationBaseKoTest({ - val yandexFormsClient by lazy { YandexFormsClient(mainWebTestClient) } + val yandexFormsClient by lazy { YandexFormsClient(mainRestTestClient) } "при отправке новым клиентом корректного запроса со всеми значениями карточки должна" - { // Сетап val entranceSurveyJson = jacksonObjectMapper().readTree(entranceSurveyJsonStr) - val yandexAdminEmail = entranceSurveyJson["yandexAdminEmail"].textValue() + val yandexAdminEmail = entranceSurveyJson["yandexAdminEmail"].stringValue() backgrounds.settingsBackgrounds.createSurveyFormsSettings( THE_THERAPIST_REF, SurveyFormsSettingsObjectMother.aSurveyFromsSettingsFrom(yandexAdminEmail) @@ -55,7 +55,7 @@ class SubmitSurveyTest : QYogaAppIntegrationBaseKoTest({ } "заполнять поле 'Жалобы' ответом из соответствующего поля" { - client?.complaints shouldContain entranceSurveyJson["survey"]["answer"]["data"][Survey.COMPLAINTS_FIELD]["value"].textValue() + client?.complaints shouldContain entranceSurveyJson["survey"]["answer"]["data"][Survey.COMPLAINTS_FIELD]["value"].stringValue() } "заполнять поле 'Анамнез' карточки клиента корректным строковым представлением ответов на пользовательские вопросы" { @@ -68,7 +68,7 @@ class SubmitSurveyTest : QYogaAppIntegrationBaseKoTest({ val phoneAndNameOnlySurveyJsonStr = phoneAndNameOnlySurveyJsonStr(THE_ADMIN_LOGIN) val phoneAndNameOnlyEntranceSurveyJson = jacksonObjectMapper().readTree(phoneAndNameOnlySurveyJsonStr) - val yandexAdminEmail = phoneAndNameOnlyEntranceSurveyJson["yandexAdminEmail"].textValue() + val yandexAdminEmail = phoneAndNameOnlyEntranceSurveyJson["yandexAdminEmail"].stringValue() backgrounds.settingsBackgrounds.createSurveyFormsSettings( THE_THERAPIST_REF, SurveyFormsSettingsObjectMother.aSurveyFromsSettingsFrom(yandexAdminEmail) @@ -217,7 +217,7 @@ class SubmitSurveyTest : QYogaAppIntegrationBaseKoTest({ // Проверка errorResponse.status shouldBe HttpStatus.CONFLICT.value() - errorResponse.type.path shouldBe InvalidSurveyException.SURVEY_SETTINGS_NOT_FOUND_FOR_ADMIN_EMAIL + errorResponse.type?.path shouldBe InvalidSurveyException.SURVEY_SETTINGS_NOT_FOUND_FOR_ADMIN_EMAIL } }) @@ -226,12 +226,12 @@ private infix fun Client?.shouldMatch(surveyRqJson: JsonNode) { this shouldNotBe null this!! val answerData = surveyRqJson["survey"]["answer"]["data"] - phoneNumber shouldBe PhoneNumber.of(answerData[Survey.PHONE_NUMBER_FIELD]["value"].textValue()) - firstName shouldBe answerData[Survey.FIRST_NAME_FIELD]["value"].textValue() - lastName shouldBe answerData[Survey.LAST_NAME_FIELD]["value"].textValue() - middleName shouldBe answerData[Survey.MIDDLE_NAME_FIELD]?.get("value")?.textValue() - birthDate?.toString() shouldBe answerData[Survey.BIRTH_DATE_FIELD]?.get("value")?.textValue() - address shouldBe answerData[Survey.LOCATION_FIELD]?.get("value")?.get(0)?.get("text")?.textValue() + phoneNumber shouldBe PhoneNumber.of(answerData[Survey.PHONE_NUMBER_FIELD]["value"].stringValue()) + firstName shouldBe answerData[Survey.FIRST_NAME_FIELD]["value"].stringValue() + lastName shouldBe answerData[Survey.LAST_NAME_FIELD]["value"].stringValue() + middleName shouldBe answerData[Survey.MIDDLE_NAME_FIELD]?.get("value")?.stringValue() + birthDate?.toString() shouldBe answerData[Survey.BIRTH_DATE_FIELD]?.get("value")?.stringValue() + address shouldBe answerData[Survey.LOCATION_FIELD]?.get("value")?.get(0)?.get("text")?.stringValue() } private infix fun String?.shouldContainAllCustomAnswersFrom(surveyRqJson: JsonNode) { @@ -241,6 +241,7 @@ private infix fun String?.shouldContainAllCustomAnswersFrom(surveyRqJson: JsonNo answerData.properties() .filter { it.key !in Survey.standardFields } .forAll { - this shouldContain it.value.asText() + val answerValue = it.value["value"].stringValue() + this shouldContain answerValue } -} \ No newline at end of file +} diff --git a/app/src/test/kotlin/pro/qyoga/tests/cases/app/therapist/appointments/core/SchedulePageTest.kt b/app/src/test/kotlin/pro/qyoga/tests/cases/app/therapist/appointments/core/SchedulePageTest.kt index 1e43dbeec..5ee78ab9d 100644 --- a/app/src/test/kotlin/pro/qyoga/tests/cases/app/therapist/appointments/core/SchedulePageTest.kt +++ b/app/src/test/kotlin/pro/qyoga/tests/cases/app/therapist/appointments/core/SchedulePageTest.kt @@ -1,6 +1,5 @@ package pro.qyoga.tests.cases.app.therapist.appointments.core -import com.fasterxml.jackson.core.type.TypeReference import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe import org.junit.jupiter.api.DisplayName @@ -24,6 +23,7 @@ import pro.qyoga.tests.pages.therapist.appointments.CalendarPage import pro.qyoga.tests.pages.therapist.appointments.appointmentCards import pro.qyoga.tests.pages.therapist.appointments.shouldMatch import pro.qyoga.tests.platform.instancio.KSelect.Companion.field +import tools.jackson.core.type.TypeReference import java.time.LocalDate diff --git a/app/src/test/kotlin/pro/qyoga/tests/cases/app/therapist/calendars/google/GoogleAuthorizationIntegrationTest.kt b/app/src/test/kotlin/pro/qyoga/tests/cases/app/therapist/calendars/google/GoogleAuthorizationIntegrationTest.kt index 55e6fa193..bafe85fab 100644 --- a/app/src/test/kotlin/pro/qyoga/tests/cases/app/therapist/calendars/google/GoogleAuthorizationIntegrationTest.kt +++ b/app/src/test/kotlin/pro/qyoga/tests/cases/app/therapist/calendars/google/GoogleAuthorizationIntegrationTest.kt @@ -20,7 +20,7 @@ import pro.qyoga.tests.fixture.wiremocks.MockGoogleOAuthServer import pro.qyoga.tests.infra.test_config.spring.context import pro.qyoga.tests.infra.web.QYogaAppIntegrationBaseKoTest import pro.qyoga.tests.infra.wiremock.WireMock -import pro.qyoga.tests.platform.spring.web_test_client.redirectLocation +import pro.qyoga.tests.platform.spring.rest_test_client.redirectLocation @DisplayName("Интеграция с Google OAuth") diff --git a/app/src/test/kotlin/pro/qyoga/tests/cases/app/therapist/therapy/programs/EditProgramPageTest.kt b/app/src/test/kotlin/pro/qyoga/tests/cases/app/therapist/therapy/programs/EditProgramPageTest.kt index 0cb74707a..42196222c 100644 --- a/app/src/test/kotlin/pro/qyoga/tests/cases/app/therapist/therapy/programs/EditProgramPageTest.kt +++ b/app/src/test/kotlin/pro/qyoga/tests/cases/app/therapist/therapy/programs/EditProgramPageTest.kt @@ -1,6 +1,5 @@ package pro.qyoga.tests.cases.app.therapist.therapy.programs -import com.fasterxml.jackson.core.type.TypeReference import io.kotest.inspectors.forAll import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe @@ -24,6 +23,7 @@ import pro.qyoga.tests.pages.therapist.therapy.programs.EditProgramPage import pro.qyoga.tests.pages.therapist.therapy.programs.PROGRAM_FORM_SCRIPT import pro.qyoga.tests.pages.therapist.therapy.programs.ProgramPage.ProgramFormScript import pro.qyoga.tests.pages.therapist.therapy.programs.ProgramsListPage +import tools.jackson.core.type.TypeReference class EditProgramPageTest : QYogaAppIntegrationBaseTest() { @@ -99,7 +99,7 @@ class EditProgramPageTest : QYogaAppIntegrationBaseTest() { // When val document = therapist.programs.updateProgramForError( program.id, - CreateProgramRequest(program.title, program.exercises.map { it.exerciseRef.id!! }), + CreateProgramRequest(program.title, program.exercises.map { it.exerciseRef.id }), notExistingTherapeuticTask ) @@ -125,4 +125,4 @@ class EditProgramPageTest : QYogaAppIntegrationBaseTest() { document shouldBePage GenericErrorPage } -} \ No newline at end of file +} diff --git a/app/src/test/kotlin/pro/qyoga/tests/cases/i9ns/calendars/ical/ICalCalendarTest.kt b/app/src/test/kotlin/pro/qyoga/tests/cases/i9ns/calendars/ical/ICalCalendarTest.kt index b5a653bcb..e711327ae 100644 --- a/app/src/test/kotlin/pro/qyoga/tests/cases/i9ns/calendars/ical/ICalCalendarTest.kt +++ b/app/src/test/kotlin/pro/qyoga/tests/cases/i9ns/calendars/ical/ICalCalendarTest.kt @@ -14,6 +14,7 @@ import pro.qyoga.i9ns.calendars.ical.model.findById import pro.qyoga.i9ns.calendars.ical.model.vEvents import pro.qyoga.tests.fixture.object_mothers.calendars.ical.ICalCalendarsObjectMother.aICalCalendar import java.time.Duration +import java.time.ZoneId import java.time.ZonedDateTime @@ -25,7 +26,10 @@ class ICalCalendarTest : FreeSpec({ val ical = aICalCalendar(singleWeeklyEvent) "при запросе событий за период включающим это событие" - { - val interval = Interval.of(ZonedDateTime.now(), Duration.ofDays(7)) + val interval = Interval.of( + ZonedDateTime.of(2025, 3, 26, 0, 0, 0, 0, ZoneId.of("Asia/Novosibirsk")), + Duration.ofDays(7) + ) val events = ical.calendarItemsIn(interval)!! "должен вернуть одно событие" { diff --git a/app/src/test/kotlin/pro/qyoga/tests/clients/OpsClient.kt b/app/src/test/kotlin/pro/qyoga/tests/clients/OpsClient.kt index d6228c977..0e1e8d571 100644 --- a/app/src/test/kotlin/pro/qyoga/tests/clients/OpsClient.kt +++ b/app/src/test/kotlin/pro/qyoga/tests/clients/OpsClient.kt @@ -1,12 +1,12 @@ package pro.qyoga.tests.clients -import com.fasterxml.jackson.databind.JsonNode import io.restassured.http.ContentType import io.restassured.module.kotlin.extensions.Extract import io.restassured.module.kotlin.extensions.Given import io.restassured.module.kotlin.extensions.Then import io.restassured.module.kotlin.extensions.When import org.springframework.http.HttpStatus +import tools.jackson.databind.JsonNode val actuatorPath = "ops/actuator" @@ -32,4 +32,4 @@ class OpsClient( } } -} \ No newline at end of file +} diff --git a/app/src/test/kotlin/pro/qyoga/tests/clients/TherapistClient.kt b/app/src/test/kotlin/pro/qyoga/tests/clients/TherapistClient.kt index dfb6a1f2d..e1c3a18c8 100644 --- a/app/src/test/kotlin/pro/qyoga/tests/clients/TherapistClient.kt +++ b/app/src/test/kotlin/pro/qyoga/tests/clients/TherapistClient.kt @@ -8,21 +8,21 @@ import io.restassured.module.kotlin.extensions.When import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.springframework.http.HttpStatus -import org.springframework.test.web.reactive.server.WebTestClient +import org.springframework.test.web.servlet.client.RestTestClient import pro.qyoga.tests.clients.api.* import pro.qyoga.tests.fixture.object_mothers.therapists.THE_THERAPIST_LOGIN import pro.qyoga.tests.fixture.object_mothers.therapists.THE_THERAPIST_PASSWORD -import pro.qyoga.tests.infra.web.mainWebTestClient +import pro.qyoga.tests.infra.web.mainRestTestClient class TherapistClient( val authCookie: Cookie, - webTestClient: WebTestClient = mainWebTestClient + restTestClient: RestTestClient = mainRestTestClient ) { // Work val appointments = TherapistAppointmentsApi(authCookie) - val googleCalendarIntegration = TherapistGoogleCalendarIntegrationApi(authCookie, webTestClient) + val googleCalendarIntegration = TherapistGoogleCalendarIntegrationApi(authCookie, restTestClient) val clients = TherapistClientsApi(authCookie) val clientJournal = TherapistClientJournalApi(authCookie) val clientFiles = TherapistClientFilesApi(authCookie) diff --git a/app/src/test/kotlin/pro/qyoga/tests/clients/YandexFormsClient.kt b/app/src/test/kotlin/pro/qyoga/tests/clients/YandexFormsClient.kt index d76a7e340..3c2539331 100644 --- a/app/src/test/kotlin/pro/qyoga/tests/clients/YandexFormsClient.kt +++ b/app/src/test/kotlin/pro/qyoga/tests/clients/YandexFormsClient.kt @@ -3,14 +3,14 @@ package pro.qyoga.tests.clients import org.springframework.http.HttpStatus import org.springframework.http.HttpStatusCode import org.springframework.http.ResponseEntity -import org.springframework.test.web.reactive.server.WebTestClient -import org.springframework.test.web.reactive.server.returnResult +import org.springframework.test.web.servlet.client.RestTestClient +import org.springframework.test.web.servlet.client.returnResult import pro.azhidkov.platform.spring.http.ErrorResponse import pro.qyoga.app.publc.surverys.SurveysController class YandexFormsClient( - private val client: WebTestClient + private val client: RestTestClient ) { fun createSurvey(surveyJsonStr: String): HttpStatusCode { @@ -26,16 +26,15 @@ class YandexFormsClient( return createSurveyForResponse(entranceSurveyJsonStr) .expectStatus().isEqualTo(expectedStatus) .returnResult(ErrorResponse::class.java) - .responseBody - .blockFirst()!! + .responseBody!! } - fun createSurveyForResponse(entranceSurveyJsonStr: String): WebTestClient.ResponseSpec { + fun createSurveyForResponse(entranceSurveyJsonStr: String): RestTestClient.ResponseSpec { return client .post() .uri(SurveysController.PATH) - .bodyValue(entranceSurveyJsonStr) + .body(entranceSurveyJsonStr) .exchange() } -} \ No newline at end of file +} diff --git a/app/src/test/kotlin/pro/qyoga/tests/clients/api/AuthorizedApi.kt b/app/src/test/kotlin/pro/qyoga/tests/clients/api/AuthorizedApi.kt index 2e491dd3b..ec3a4288f 100644 --- a/app/src/test/kotlin/pro/qyoga/tests/clients/api/AuthorizedApi.kt +++ b/app/src/test/kotlin/pro/qyoga/tests/clients/api/AuthorizedApi.kt @@ -2,7 +2,7 @@ package pro.qyoga.tests.clients.api import io.restassured.http.Cookie import io.restassured.specification.RequestSpecification -import org.springframework.test.web.reactive.server.WebTestClient +import org.springframework.test.web.servlet.client.RestTestClient interface AuthorizedApi { @@ -13,8 +13,8 @@ interface AuthorizedApi { return cookie(authCookie) } - fun WebTestClient.RequestHeadersSpec<*>.authorized(): WebTestClient.RequestHeadersSpec<*> { + fun RestTestClient.RequestHeadersSpec<*>.authorized(): RestTestClient.RequestHeadersSpec<*> { return cookie(authCookie.name, authCookie.value) } -} \ No newline at end of file +} diff --git a/app/src/test/kotlin/pro/qyoga/tests/clients/api/NotificationsApi.kt b/app/src/test/kotlin/pro/qyoga/tests/clients/api/NotificationsApi.kt index 2e7c76087..10f69dab4 100644 --- a/app/src/test/kotlin/pro/qyoga/tests/clients/api/NotificationsApi.kt +++ b/app/src/test/kotlin/pro/qyoga/tests/clients/api/NotificationsApi.kt @@ -3,16 +3,16 @@ package pro.qyoga.tests.clients.api import io.restassured.http.Cookie import org.jsoup.Jsoup import org.jsoup.nodes.Document -import org.springframework.test.web.reactive.server.WebTestClient +import org.springframework.test.web.servlet.client.RestTestClient import pro.qyoga.app.therapist.appointments.core.schedule.settings.NotificationsSettingsController -import pro.qyoga.tests.infra.web.mainWebTestClient -import pro.qyoga.tests.platform.spring.web_test_client.getBodyAsString +import pro.qyoga.tests.infra.web.mainRestTestClient +import pro.qyoga.tests.platform.spring.rest_test_client.getBodyAsString object NotificationsApiFactory { fun therapistApi( principal: Cookie, - ) = NotificationsTherapistApi(principal, mainWebTestClient) + ) = NotificationsTherapistApi(principal, mainRestTestClient) } @@ -22,11 +22,11 @@ val TrainerAdvisorApis.Notifications class NotificationsTherapistApi( override val authCookie: Cookie, - private val webTestClient: WebTestClient + private val restTestClient: RestTestClient ) : AuthorizedApi { fun getNotificationsSettings(): Document { - return webTestClient.get() + return restTestClient.get() .uri(NotificationsSettingsController.PATH) .authorized() .exchange() diff --git a/app/src/test/kotlin/pro/qyoga/tests/clients/api/TherapistGoogleCalendarIntegrationApi.kt b/app/src/test/kotlin/pro/qyoga/tests/clients/api/TherapistGoogleCalendarIntegrationApi.kt index f8ade129b..afd2803bd 100644 --- a/app/src/test/kotlin/pro/qyoga/tests/clients/api/TherapistGoogleCalendarIntegrationApi.kt +++ b/app/src/test/kotlin/pro/qyoga/tests/clients/api/TherapistGoogleCalendarIntegrationApi.kt @@ -4,21 +4,20 @@ import io.restassured.http.Cookie import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse -import org.springframework.test.web.reactive.server.WebTestClient -import org.springframework.web.reactive.function.BodyInserters.fromValue +import org.springframework.test.web.servlet.client.RestTestClient import pro.qyoga.app.therapist.appointments.core.schedule.settings.GoogleCalendarSettingsController import pro.qyoga.i9ns.calendars.google.model.GoogleAccountRef -import pro.qyoga.tests.platform.spring.web_test_client.getBodyAsString -import pro.qyoga.tests.platform.spring.web_test_client.redirectLocation +import pro.qyoga.tests.platform.spring.rest_test_client.getBodyAsString +import pro.qyoga.tests.platform.spring.rest_test_client.redirectLocation import java.net.URI class TherapistGoogleCalendarIntegrationApi( override val authCookie: Cookie, - private val webTestClient: WebTestClient + private val restTestClient: RestTestClient ) : AuthorizedApi { fun authorizeInGoogle(): URI { - val response = webTestClient.get() + val response = restTestClient.get() .uri("/oauth2/authorization/google") .authorized() .exchange() @@ -33,8 +32,8 @@ class TherapistGoogleCalendarIntegrationApi( // scope=https://www.googleapis.com/auth/calendar.readonly' \ fun handleOAuthCallbackForResponse( authResponse: OAuth2AuthorizationResponse - ): WebTestClient.ResponseSpec { - return webTestClient.get() + ): RestTestClient.ResponseSpec { + return restTestClient.get() .uri { it.path("/therapist/oauth2/google/callback") .queryParam("state", authResponse.state) @@ -45,15 +44,15 @@ class TherapistGoogleCalendarIntegrationApi( .exchange() } - fun finalizeOAuthCallbackForResponse(): WebTestClient.ResponseSpec { - return webTestClient.get() + fun finalizeOAuthCallbackForResponse(): RestTestClient.ResponseSpec { + return restTestClient.get() .uri("/therapist/oauth2/google/callback") .authorized() .exchange() } fun getGoogleCalendarComponent(): Document { - return webTestClient.get() + return restTestClient.get() .uri(GoogleCalendarSettingsController.PATH) .authorized() .exchange() @@ -67,9 +66,9 @@ class TherapistGoogleCalendarIntegrationApi( "shouldBeShown" to shouldBeShown ) - webTestClient.patch() + restTestClient.patch() .uri(GoogleCalendarSettingsController.updateCalendarSettingsPath(googleAccount, calendarId)) - .body(fromValue(body)) + .body(body) .authorized() .exchange() .expectStatus().isNoContent diff --git a/app/src/test/kotlin/pro/qyoga/tests/clients/api/TherapistProgramsApi.kt b/app/src/test/kotlin/pro/qyoga/tests/clients/api/TherapistProgramsApi.kt index 28136c8be..235d9f69c 100644 --- a/app/src/test/kotlin/pro/qyoga/tests/clients/api/TherapistProgramsApi.kt +++ b/app/src/test/kotlin/pro/qyoga/tests/clients/api/TherapistProgramsApi.kt @@ -1,8 +1,5 @@ package pro.qyoga.tests.clients.api -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.node.ObjectNode -import com.fasterxml.jackson.module.kotlin.convertValue import io.restassured.http.ContentType import io.restassured.http.Cookie import io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath @@ -24,6 +21,9 @@ import pro.qyoga.tests.pages.therapist.therapy.programs.CreateProgramForm import pro.qyoga.tests.pages.therapist.therapy.programs.CreateProgramPage import pro.qyoga.tests.pages.therapist.therapy.programs.EditProgramPage import pro.qyoga.tests.pages.therapist.therapy.programs.ProgramsListPage +import tools.jackson.databind.ObjectMapper +import tools.jackson.databind.node.ObjectNode +import tools.jackson.module.kotlin.convertValue class TherapistProgramsApi(override val authCookie: Cookie) : AuthorizedApi { diff --git a/app/src/test/kotlin/pro/qyoga/tests/clients/api/WebPushesPublicApi.kt b/app/src/test/kotlin/pro/qyoga/tests/clients/api/WebPushesPublicApi.kt index 0477555f5..7eb6f1ca1 100644 --- a/app/src/test/kotlin/pro/qyoga/tests/clients/api/WebPushesPublicApi.kt +++ b/app/src/test/kotlin/pro/qyoga/tests/clients/api/WebPushesPublicApi.kt @@ -1,19 +1,19 @@ package pro.qyoga.tests.clients.api import io.restassured.http.Cookie -import org.springframework.test.web.reactive.server.WebTestClient -import org.springframework.test.web.reactive.server.returnResult +import org.springframework.test.web.servlet.client.RestTestClient +import org.springframework.test.web.servlet.client.returnResult import pro.qyoga.app.publc.pushes.web.PushesPublicKeyController -import pro.qyoga.tests.infra.web.mainWebTestClient +import pro.qyoga.tests.infra.web.mainRestTestClient object WebPushesApiFactory { - val publicApi = WebPushesPublicApi(mainWebTestClient) + val publicApi = WebPushesPublicApi(mainRestTestClient) fun therapistApi( principal: Cookie, - ) = WebPushesTherapistApi(principal, mainWebTestClient) + ) = WebPushesTherapistApi(principal, mainRestTestClient) } @@ -22,7 +22,7 @@ val TrainerAdvisorApis.WebPushes get() = WebPushesApiFactory class WebPushesPublicApi( - private val client: WebTestClient + private val client: RestTestClient ) { fun getPublicKey(): String { @@ -31,8 +31,7 @@ class WebPushesPublicApi( .exchange() .expectStatus().isOk .returnResult() - .responseBody - .blockFirst() ?: "" + .responseBody ?: "" } } diff --git a/app/src/test/kotlin/pro/qyoga/tests/clients/api/WebPushesTherapistApi.kt b/app/src/test/kotlin/pro/qyoga/tests/clients/api/WebPushesTherapistApi.kt index 9622ac5ec..b795b9e51 100644 --- a/app/src/test/kotlin/pro/qyoga/tests/clients/api/WebPushesTherapistApi.kt +++ b/app/src/test/kotlin/pro/qyoga/tests/clients/api/WebPushesTherapistApi.kt @@ -1,27 +1,26 @@ package pro.qyoga.tests.clients.api import io.restassured.http.Cookie -import org.springframework.test.web.reactive.server.WebTestClient -import org.springframework.web.reactive.function.BodyInserters.fromValue +import org.springframework.test.web.servlet.client.RestTestClient import pro.qyoga.app.pushes.web.WebPushesController import pro.qyoga.i9ns.pushes.web.model.WebPushSubscription class WebPushesTherapistApi( override val authCookie: Cookie, - private val webTestClient: WebTestClient + private val restTestClient: RestTestClient ) : AuthorizedApi { fun createSubscription(subscription: WebPushSubscription) { - webTestClient.post() + restTestClient.post() .uri(WebPushesController.PATH) - .body(fromValue(subscription)) + .body(subscription) .authorized() .exchange() .expectStatus().isNoContent } fun deleteSubscription(p256dh: String) { - webTestClient.delete() + restTestClient.delete() .uri(WebPushesController.DELETE_SUBSCRIPTION_PATH, p256dh) .authorized() .exchange() diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/assertions/MultiValueMapAssertions.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/assertions/MultiValueMapAssertions.kt index 44c952c74..30e7ecb1e 100644 --- a/app/src/testFixtures/kotlin/pro/qyoga/tests/assertions/MultiValueMapAssertions.kt +++ b/app/src/testFixtures/kotlin/pro/qyoga/tests/assertions/MultiValueMapAssertions.kt @@ -5,7 +5,7 @@ import io.kotest.matchers.maps.shouldContainKey import org.springframework.util.MultiValueMap -fun MultiValueMap.shouldContainValue(key: K, value: V) { +fun MultiValueMap.shouldContainValue(key: K, value: V) { this shouldContainKey key this[key]!! shouldContain value -} \ No newline at end of file +} diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/fixture/backgrounds/AppointmentsBackgrounds.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/fixture/backgrounds/AppointmentsBackgrounds.kt index d18c63fea..ccf6d376a 100644 --- a/app/src/testFixtures/kotlin/pro/qyoga/tests/fixture/backgrounds/AppointmentsBackgrounds.kt +++ b/app/src/testFixtures/kotlin/pro/qyoga/tests/fixture/backgrounds/AppointmentsBackgrounds.kt @@ -59,7 +59,7 @@ class AppointmentsBackgrounds( appointmentStatus: AppointmentStatus = AppointmentStatus.entries.randomElement(), comment: String? = randomSentence(), therapist: TherapistRef = THE_THERAPIST_REF, - therapeuticTaskRef: TherapeuticTaskRef? = therapeuticTasksBackgrounds.createTherapeuticTask(therapist.id!!) + therapeuticTaskRef: TherapeuticTaskRef? = therapeuticTasksBackgrounds.createTherapeuticTask(therapist.id) .ref(), ): Appointment { return create(dateTime, timeZone, duration, place, cost, payed, appointmentStatus, comment, therapist, therapeuticTaskRef) @@ -77,7 +77,7 @@ class AppointmentsBackgrounds( therapist: TherapistRef = THE_THERAPIST_REF, therapeuticTaskRef: TherapeuticTaskRef? = null, ): Appointment { - val clientRef = clientsBackgrounds.createClients(1, therapist.id!!).single().ref() + val clientRef = clientsBackgrounds.createClients(1, therapist.id).single().ref() val appointment = createAppointment( therapist, AppointmentsObjectMother.randomEditAppointmentRequest( diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/fixture/object_mothers/appointments/AppointmentsObjectMother.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/fixture/object_mothers/appointments/AppointmentsObjectMother.kt index 243f523e2..d09fccc84 100644 --- a/app/src/testFixtures/kotlin/pro/qyoga/tests/fixture/object_mothers/appointments/AppointmentsObjectMother.kt +++ b/app/src/testFixtures/kotlin/pro/qyoga/tests/fixture/object_mothers/appointments/AppointmentsObjectMother.kt @@ -28,7 +28,7 @@ object AppointmentsObjectMother { appointmentStatus: AppointmentStatus = AppointmentStatus.entries.randomElement(), ): LocalizedAppointmentSummary { return LocalizedAppointmentSummary( - aAppointmentId().id!!, + aAppointmentId().id, client.resolveOrThrow().fullName(), typeTitle, dateTime, diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/fixture/object_mothers/calendars/ical/ICalCalendarsObjectMother.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/fixture/object_mothers/calendars/ical/ICalCalendarsObjectMother.kt index 3d4b6e5f0..708bb9a02 100644 --- a/app/src/testFixtures/kotlin/pro/qyoga/tests/fixture/object_mothers/calendars/ical/ICalCalendarsObjectMother.kt +++ b/app/src/testFixtures/kotlin/pro/qyoga/tests/fixture/object_mothers/calendars/ical/ICalCalendarsObjectMother.kt @@ -3,11 +3,10 @@ package pro.qyoga.tests.fixture.object_mothers.calendars.ical import net.fortuna.ical4j.data.CalendarOutputter import net.fortuna.ical4j.model.Calendar import net.fortuna.ical4j.model.component.CalendarComponent +import net.fortuna.ical4j.model.component.Standard import net.fortuna.ical4j.model.component.VEvent -import net.fortuna.ical4j.model.property.Description -import net.fortuna.ical4j.model.property.Location -import net.fortuna.ical4j.model.property.RecurrenceId -import net.fortuna.ical4j.model.property.Uid +import net.fortuna.ical4j.model.component.VTimeZone +import net.fortuna.ical4j.model.property.* import pro.azhidkov.platform.kotlin.ifNotNull import pro.qyoga.i9ns.calendars.ical.model.ICalCalendar import pro.qyoga.i9ns.calendars.ical.model.ICalZonedCalendarItem @@ -16,7 +15,10 @@ import pro.qyoga.tests.fixture.object_mothers.calendars.CalendarsObjectMother import pro.qyoga.tests.fixture.object_mothers.therapists.THE_THERAPIST_REF import java.io.StringWriter import java.net.URI +import java.time.ZoneId +import java.time.ZonedDateTime import java.time.temporal.Temporal +import net.fortuna.ical4j.model.parameter.TzId as TzIdParam object ICalCalendarsObjectMother { @@ -43,16 +45,40 @@ object ICalCalendarsObjectMother { .withDefaults() .fluentTarget - calendar.withComponent( - VEvent(event.dateTime, event.duration, event.title) - .withProperty(Uid(event.id.uid)) - .ifNotNull(event.id.recurrenceId) { withProperty(RecurrenceId(it)) } - .withProperty(Description(event.description)) - .withProperty(Location(event.location)) - as CalendarComponent + val vEvent = VEvent() + .withProperty(Uid(event.id.uid)) + .withProperty(DtStart(event.dateTime.toLocalDateTime()).withParameter(TzIdParam(event.dateTime.zone.id)).fluentTarget) + .withProperty(Duration(event.duration)) + .withProperty(Summary(event.title)) + .ifNotNull(event.id.recurrenceId) { withProperty(RecurrenceId(it)) } + .withProperty(Description(event.description)) + .withProperty(Location(event.location)) + as CalendarComponent + + calendar + .withComponent(vTimeZoneFrom(event.dateTime.zone, event.dateTime)) + .withComponent(vEvent) - ) return calendar } + private fun vTimeZoneFrom(zone: ZoneId, reference: ZonedDateTime): VTimeZone { + val offset = zone.rules.getOffset(reference.toInstant()) + val offsetString = offset.id.replace(":", "") + + val standard = Standard().apply { + add(TzName(offset.id)) + add(TzOffsetFrom(offsetString)) + add(TzOffsetTo(offsetString)) + + val dtStart = DtStart(reference.withYear(1970).toLocalDateTime()) + add(dtStart) + } + + return VTimeZone().apply { + add(TzId(zone.id)) + add(standard) + } + } + } diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/db/Containers.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/db/Containers.kt index 0fd22c12b..5da233329 100644 --- a/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/db/Containers.kt +++ b/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/db/Containers.kt @@ -1,10 +1,10 @@ package pro.qyoga.tests.infra.db import org.testcontainers.containers.MinIOContainer -import org.testcontainers.containers.PostgreSQLContainer +import org.testcontainers.postgresql.PostgreSQLContainer import org.testcontainers.utility.MountableFile -val pgContainer: PostgreSQLContainer<*> by lazy { +val pgContainer: PostgreSQLContainer by lazy { PostgreSQLContainer("postgres:15.2") .withExposedPorts(5432) .withUsername("postgres") diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/test_config/spring/TestsConfig.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/test_config/spring/TestsConfig.kt index 4a25b877e..8c110d2bb 100644 --- a/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/test_config/spring/TestsConfig.kt +++ b/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/test_config/spring/TestsConfig.kt @@ -1,7 +1,7 @@ package pro.qyoga.tests.infra.test_config.spring -import org.springframework.boot.autoconfigure.web.ServerProperties import org.springframework.boot.builder.SpringApplicationBuilder +import org.springframework.boot.web.server.autoconfigure.ServerProperties import org.springframework.context.ApplicationContext import org.springframework.context.ConfigurableApplicationContext import org.springframework.context.annotation.AnnotationConfigApplicationContext @@ -53,4 +53,4 @@ val sdjContext by lazy { class TestsConfig @Import(SdjConfig::class, TestDataSourceConfig::class) -class SdjTestsConfig \ No newline at end of file +class SdjTestsConfig diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/test_config/spring/auth/TestPasswordEncoderConfig.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/test_config/spring/auth/TestPasswordEncoderConfig.kt index 29142f985..289390619 100644 --- a/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/test_config/spring/auth/TestPasswordEncoderConfig.kt +++ b/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/test_config/spring/auth/TestPasswordEncoderConfig.kt @@ -5,16 +5,12 @@ package pro.qyoga.tests.infra.test_config.spring.auth import org.springframework.boot.test.context.TestConfiguration import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Primary -import org.springframework.security.authentication.dao.DaoAuthenticationProvider -import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.crypto.password.NoOpPasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder @TestConfiguration -class TestPasswordEncoderConfig( - private val userDetailsService: UserDetailsService -) { +class TestPasswordEncoderConfig { // Стандартный BCryptPasswordEncoder кодирует пароли по 300мс, что слишком расточительно для тестов @Suppress("DEPRECATION") @@ -22,11 +18,4 @@ class TestPasswordEncoderConfig( @Bean fun fastPasswordEncoder(): PasswordEncoder = NoOpPasswordEncoder.getInstance() - @Bean - fun daoAuthenticationProvider(): DaoAuthenticationProvider { - val daoAuthenticationProvider = DaoAuthenticationProvider(fastPasswordEncoder()) - daoAuthenticationProvider.setUserDetailsService(userDetailsService) - return daoAuthenticationProvider - } - -} \ No newline at end of file +} diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/web/QYogaAppBaseKoTest.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/web/QYogaAppBaseKoTest.kt index c7c59570b..7256fe674 100644 --- a/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/web/QYogaAppBaseKoTest.kt +++ b/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/web/QYogaAppBaseKoTest.kt @@ -18,10 +18,10 @@ abstract class QYogaAppBaseKoTest(body: QYogaAppBaseKoTest.() -> Unit = {}) : Fr val presets: PresetsConf = context.getBean(PresetsConf::class.java) - inline fun getBean(): T = + inline fun getBean(): T = context.getBean(T::class.java) - inline fun getBean(name: String): T = + inline fun getBean(name: String): T = context.getBean(name, T::class.java) init { @@ -35,4 +35,4 @@ abstract class QYogaAppBaseKoTest(body: QYogaAppBaseKoTest.() -> Unit = {}) : Fr body() } -} \ No newline at end of file +} diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/web/QYogaAppBaseTest.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/web/QYogaAppBaseTest.kt index 2fa36d038..e84374c57 100644 --- a/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/web/QYogaAppBaseTest.kt +++ b/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/web/QYogaAppBaseTest.kt @@ -1,7 +1,7 @@ package pro.qyoga.tests.infra.web import org.junit.jupiter.api.BeforeEach -import org.springframework.boot.autoconfigure.web.ServerProperties +import org.springframework.boot.web.server.autoconfigure.ServerProperties import pro.qyoga.tests.fixture.backgrounds.Backgrounds import pro.qyoga.tests.fixture.data.resetFaker import pro.qyoga.tests.fixture.presets.PresetsConf @@ -15,13 +15,13 @@ open class QYogaAppBaseTest { private val dataSource: DataSource = context.getBean(DataSource::class.java) - protected val port: Int = context.getBean(ServerProperties::class.java).port + protected val port: Int = requireNotNull(context.getBean(ServerProperties::class.java).port) protected val backgrounds: Backgrounds = context.getBean(Backgrounds::class.java) protected val presets: PresetsConf = context.getBean(PresetsConf::class.java) - inline fun getBean(): T = + inline fun getBean(): T = context.getBean(T::class.java) @BeforeEach @@ -31,4 +31,4 @@ open class QYogaAppBaseTest { WireMock.reset() } -} \ No newline at end of file +} diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/web/RestTestClient.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/web/RestTestClient.kt new file mode 100644 index 000000000..c1a29edb9 --- /dev/null +++ b/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/web/RestTestClient.kt @@ -0,0 +1,22 @@ +package pro.qyoga.tests.infra.web + +import org.apache.hc.client5.http.impl.classic.HttpClients +import org.springframework.context.ConfigurableApplicationContext +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory +import org.springframework.test.web.servlet.client.RestTestClient +import pro.qyoga.tests.infra.test_config.spring.baseUrl + + +val mainRestTestClient: RestTestClient by lazy { createRestTestClient() } + +fun createRestTestClient(context: ConfigurableApplicationContext = pro.qyoga.tests.infra.test_config.spring.context): RestTestClient = + RestTestClient.bindToServer( + HttpComponentsClientHttpRequestFactory( + HttpClients.custom() + .disableRedirectHandling() + .build() + ) + ) + .baseUrl(context.baseUrl) + .defaultHeader("Content-Type", "application/json;charset=UTF-8") + .build() diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/web/WebTestClient.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/web/WebTestClient.kt deleted file mode 100644 index 1a185a427..000000000 --- a/app/src/testFixtures/kotlin/pro/qyoga/tests/infra/web/WebTestClient.kt +++ /dev/null @@ -1,14 +0,0 @@ -package pro.qyoga.tests.infra.web - -import org.springframework.context.ConfigurableApplicationContext -import org.springframework.test.web.reactive.server.WebTestClient -import pro.qyoga.tests.infra.test_config.spring.baseUrl - - -val mainWebTestClient: WebTestClient by lazy { createWebTestClient() } - -fun createWebTestClient(context: ConfigurableApplicationContext = pro.qyoga.tests.infra.test_config.spring.context): WebTestClient = - WebTestClient.bindToServer() - .baseUrl(context.baseUrl) - .defaultHeader("Content-Type", "application/json;charset=UTF-8") - .build() \ No newline at end of file diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/pages/therapist/appointments/AppointmentForm.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/pages/therapist/appointments/AppointmentForm.kt index b00bd7294..afca45ee8 100644 --- a/app/src/testFixtures/kotlin/pro/qyoga/tests/pages/therapist/appointments/AppointmentForm.kt +++ b/app/src/testFixtures/kotlin/pro/qyoga/tests/pages/therapist/appointments/AppointmentForm.kt @@ -88,7 +88,7 @@ object EditAppointmentForm : AppointmentForm(FormAction.hxPut("$PATH/{appointmen fun statusFromXData(element: Element): String? { val formElement = element.select("form[x-data]").single() - val xDataValue = formElement?.attr("x-data") ?: return null + val xDataValue = formElement.attr("x-data") val regex = """'status':\s'(.*)'""".toRegex() val matchResult = regex.find(xDataValue) diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/pages/therapist/survey_forms/SurveyFormsSettingsComponent.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/pages/therapist/survey_forms/SurveyFormsSettingsComponent.kt index f1f0275f4..6ea0c3f16 100644 --- a/app/src/testFixtures/kotlin/pro/qyoga/tests/pages/therapist/survey_forms/SurveyFormsSettingsComponent.kt +++ b/app/src/testFixtures/kotlin/pro/qyoga/tests/pages/therapist/survey_forms/SurveyFormsSettingsComponent.kt @@ -1,7 +1,5 @@ package pro.qyoga.tests.pages.therapist.survey_forms -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.ObjectMapper import io.kotest.matchers.Matcher import org.jsoup.nodes.Element import pro.qyoga.app.therapist.survey_forms.settings.SurveyFormsSettingsComponentController @@ -10,6 +8,8 @@ import pro.qyoga.tests.assertions.isTag import pro.qyoga.tests.infra.test_config.spring.context import pro.qyoga.tests.platform.html.* import pro.qyoga.tests.platform.kotest.all +import tools.jackson.databind.JsonNode +import tools.jackson.databind.ObjectMapper object SurveyFormsSettingsComponent : Component { @@ -35,7 +35,7 @@ object SurveyFormsSettingsComponent : Component { val formEl = element.select("#$id").single() val formModelJson = formEl.attr("x-data").replace("'", "\"") val formModel = mapper.readValue(formModelJson, JsonNode::class.java) - return formModel.get(yandexAdminEmail.name).asText() + return formModel.get(yandexAdminEmail.name).asString() } } diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/platform/html/Script.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/platform/html/Script.kt index f9bbd1c74..110997cf1 100644 --- a/app/src/testFixtures/kotlin/pro/qyoga/tests/platform/html/Script.kt +++ b/app/src/testFixtures/kotlin/pro/qyoga/tests/platform/html/Script.kt @@ -1,12 +1,12 @@ package pro.qyoga.tests.platform.html -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.databind.ObjectMapper import io.kotest.matchers.Matcher import io.kotest.matchers.compose.all import org.jsoup.nodes.Element import pro.qyoga.tests.assertions.htmlMatch import pro.qyoga.tests.infra.test_config.spring.context +import tools.jackson.core.type.TypeReference +import tools.jackson.databind.ObjectMapper import kotlin.reflect.KClass @@ -45,4 +45,4 @@ abstract class Script( return Matcher.all(*varMatchers.toTypedArray()) } -} \ No newline at end of file +} diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/platform/spring/context/ConfigurableApplicationContextExt.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/platform/spring/context/ConfigurableApplicationContextExt.kt index 1c7857abe..73dbbd82a 100644 --- a/app/src/testFixtures/kotlin/pro/qyoga/tests/platform/spring/context/ConfigurableApplicationContextExt.kt +++ b/app/src/testFixtures/kotlin/pro/qyoga/tests/platform/spring/context/ConfigurableApplicationContextExt.kt @@ -3,5 +3,5 @@ package pro.qyoga.tests.platform.spring.context import org.springframework.beans.factory.BeanFactory -inline fun BeanFactory.getBean(): T = +inline fun BeanFactory.getBean(): T = this.getBean(T::class.java) diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/platform/spring/rest_test_client/ResponseSpecExt.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/platform/spring/rest_test_client/ResponseSpecExt.kt new file mode 100644 index 000000000..76f68e46c --- /dev/null +++ b/app/src/testFixtures/kotlin/pro/qyoga/tests/platform/spring/rest_test_client/ResponseSpecExt.kt @@ -0,0 +1,14 @@ +package pro.qyoga.tests.platform.spring.rest_test_client + +import org.springframework.test.web.servlet.client.RestTestClient +import java.net.URI + + +fun RestTestClient.ResponseSpec.getBodyAsString(): String = + String(this.returnResult().responseBodyContent) + +fun RestTestClient.ResponseSpec.redirectLocation(): URI = + this + .expectStatus().isFound.returnResult() + .responseHeaders + .location!! diff --git a/app/src/testFixtures/kotlin/pro/qyoga/tests/platform/spring/web_test_client/ResponseSpecExt.kt b/app/src/testFixtures/kotlin/pro/qyoga/tests/platform/spring/web_test_client/ResponseSpecExt.kt deleted file mode 100644 index 0aa228511..000000000 --- a/app/src/testFixtures/kotlin/pro/qyoga/tests/platform/spring/web_test_client/ResponseSpecExt.kt +++ /dev/null @@ -1,19 +0,0 @@ -package pro.qyoga.tests.platform.spring.web_test_client - -import org.springframework.test.web.reactive.server.WebTestClient -import org.springframework.test.web.reactive.server.returnResult -import java.net.URI - - -fun WebTestClient.ResponseSpec.getBodyAsString(): String = - this.returnResult(String::class.java) - .responseBody - .collectList() - .block()!! - .joinToString("\n") - -fun WebTestClient.ResponseSpec.redirectLocation(): URI = - this - .expectStatus().isFound.returnResult() - .responseHeaders - .location!! \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index d7f5862a0..182e8cc77 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,4 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -26,7 +27,7 @@ subprojects { } } - configure { + configure { toolVersion = detektPlugin.version.requiredVersion config.setFrom(file(rootProject.rootDir.resolve("config/detekt/detekt.yml"))) @@ -35,8 +36,10 @@ subprojects { tasks.withType { compilerOptions { - freeCompilerArgs = listOf("-Xjsr305=strict", "-Xjvm-default=all", "-Xwhen-guards") + freeCompilerArgs = listOf("-Xjsr305=strict", "-Xwhen-guards") jvmTarget.set(JvmTarget.JVM_21) + jvmDefault.set(JvmDefaultMode.NO_COMPATIBILITY) + allWarningsAsErrors = true } } @@ -45,4 +48,4 @@ subprojects { useJUnitPlatform() } -} \ No newline at end of file +} diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index 1ae7b19ac..48f5af4a1 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -1,21 +1,14 @@ -build: - maxIssues: 0 - excludeCorrectable: false - weights: - # complexity: 2 - # LongParameterList: 1 - # style: 1 - # comments: 1 - style: - UnusedImports: + UnusedImport: active: true UnusedPrivateClass: active: true - UnusedPrivateMember: + UnusedPrivateFunction: + active: true + UnusedPrivateProperty: active: true complexity: CognitiveComplexMethod: active: true - threshold: 15 \ No newline at end of file + allowedComplexity: 15 diff --git a/e2e-tests/build.gradle.kts b/e2e-tests/build.gradle.kts index 613769fa5..11af63d6b 100644 --- a/e2e-tests/build.gradle.kts +++ b/e2e-tests/build.gradle.kts @@ -1,6 +1,12 @@ group = "pro.qyoga.e2e-tests" version = "0.0.1-SNAPSHOT" +configurations.matching { it.name in setOf("compileClasspath", "testCompileClasspath", "testFixturesCompileClasspath") } + .all { + exclude(group = "com.fasterxml.jackson") + exclude(group = "com.fasterxml.jackson.core") + } + dependencies { implementation(platform("org.springframework.boot:spring-boot-dependencies:${libs.versions.springBoot.get()}")) @@ -16,4 +22,4 @@ tasks { named("test", Test::class) { systemProperties["selenide.browser"] = "chrome" } -} \ No newline at end of file +} diff --git a/e2e-tests/src/test/kotlin/pro/qyoga/tests/infra/SelenideContainer.kt b/e2e-tests/src/test/kotlin/pro/qyoga/tests/infra/SelenideContainer.kt index 46e8449b5..a098e3832 100644 --- a/e2e-tests/src/test/kotlin/pro/qyoga/tests/infra/SelenideContainer.kt +++ b/e2e-tests/src/test/kotlin/pro/qyoga/tests/infra/SelenideContainer.kt @@ -1,13 +1,11 @@ package pro.qyoga.tests.infra -import org.openqa.selenium.chrome.ChromeOptions -import org.testcontainers.containers.BrowserWebDriverContainer +import org.testcontainers.selenium.BrowserWebDriverContainer import org.testcontainers.utility.DockerImageName -val container: BrowserWebDriverContainer<*> by lazy { +val container: BrowserWebDriverContainer by lazy { BrowserWebDriverContainer(chromeImage()) - .withCapabilities(ChromeOptions()) .withAccessToHost(true) .apply { start() diff --git a/e2e-tests/src/test/kotlin/pro/qyoga/tests/platform/selenide/SelenideExt.kt b/e2e-tests/src/test/kotlin/pro/qyoga/tests/platform/selenide/SelenideExt.kt index e1752a228..85e330cf7 100644 --- a/e2e-tests/src/test/kotlin/pro/qyoga/tests/platform/selenide/SelenideExt.kt +++ b/e2e-tests/src/test/kotlin/pro/qyoga/tests/platform/selenide/SelenideExt.kt @@ -4,6 +4,7 @@ import com.codeborne.selenide.Condition.attribute import com.codeborne.selenide.Condition.visible import com.codeborne.selenide.Selenide import com.codeborne.selenide.Selenide.`$` +import com.codeborne.selenide.Selenide.executeJavaScript import com.codeborne.selenide.SelenideElement import com.codeborne.selenide.TypeOptions import pro.qyoga.tests.platform.html.ComboBox @@ -63,16 +64,24 @@ fun open(page: HtmlPageCompat) { fun click(component: Component) { `$`(component.selector()) - .scrollIntoView("{behavior: \"instant\", block: \"center\", inline: \"center\"}") + .scrollToCenter() .click() } fun SelenideElement.click(selector: String) { `$`(selector) - .scrollIntoView("{behavior: \"instant\", block: \"center\", inline: \"center\"}") + .scrollToCenter() .click() } +private fun SelenideElement.scrollToCenter(): SelenideElement { + executeJavaScript( + "arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'})", + this + ) + return this +} + fun await(page: HtmlPageCompat) { requireNotNull(page.title) { "Невозможно дождаться страницы без названия" } `$`("title").shouldHave(attribute("text", page.title!!)) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55ba..8bdaf60c7 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e1113280..37f78a6af 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 23d15a936..adff685a0 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index db3a6ac20..c4bdd3ab8 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/settings.gradle.kts b/settings.gradle.kts index b316362b8..738ce6cd7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,12 +6,12 @@ dependencyResolutionManagement { versionCatalogs { create("libs") { // plugin versions - val kotlinVersion = version("kotlin", "2.2.10") - val springBootVersion = version("springBoot", "3.5.6") + val kotlinVersion = version("kotlin", "2.3.10") + val springBootVersion = version("springBoot", "4.0.3") val springDependencyManagementVersion = version("springDependencyManagement", "1.1.7") val koverVersion = version("kover", "0.9.2") val gitPropertiesVersion = version("gitProperties", "2.5.3") - val detektVersion = version("dekekt", "1.23.8") + val detektVersion = version("dekekt", "2.0.0-alpha.1") // lib versions val poiVersion = version("poi", "5.4.1") @@ -29,10 +29,9 @@ dependencyResolutionManagement { plugin("kover", "org.jetbrains.kotlinx.kover").versionRef(koverVersion) plugin("gitProperties", "com.gorylenko.gradle-git-properties").versionRef(gitPropertiesVersion) - plugin("detekt", "io.gitlab.arturbosch.detekt").versionRef(detektVersion) + plugin("detekt", "dev.detekt").versionRef(detektVersion) // libs - library("jackarta-validation", "jakarta.validation", "jakarta.validation-api").version("3.1.1") library( "thymeleaf-extras-java8time", "org.thymeleaf.extras", @@ -70,13 +69,16 @@ dependencyResolutionManagement { create("testLibs") { val selenideVersion = version("selenide", "7.10.1") - val testContainersVersion = version("testcontainers", "1.21.3") - val restAssuredVersion = version("restAssured", "5.5.6") + val testContainersVersion = version("testcontainers", "2.0.3") + val restAssuredVersion = version("restAssured", "6.0.0") val kotestVersion = version("kotest", "5.9.1") val wiremockVersion = version("wiremock", "3.13.1") + val jettyVersion = version("jetty", "12.1.0") library("selenide-proxy", "com.codeborne", "selenide-proxy").versionRef(selenideVersion) - library("testcontainers-selenium", "org.testcontainers", "selenium").versionRef(testContainersVersion) + library("testcontainers-selenium", "org.testcontainers", "testcontainers-selenium").versionRef( + testContainersVersion + ) library("kotest-assertions", "io.kotest", "kotest-assertions-core").versionRef(kotestVersion) library("kotest-runner", "io.kotest", "kotest-runner-junit5").versionRef(kotestVersion) @@ -95,7 +97,9 @@ dependencyResolutionManagement { library("datafaker", "net.datafaker", "datafaker").version("2.5.1") library("greenmail", "com.icegreen", "greenmail-junit5").version("2.1.6") - library("testcontainers-minio", "org.testcontainers", "minio").versionRef(testContainersVersion) + library("testcontainers-minio", "org.testcontainers", "testcontainers-minio").versionRef( + testContainersVersion + ) library("mockito-kotlin", "org.mockito.kotlin", "mockito-kotlin").version("6.1.0") library("archunit", "com.tngtech.archunit", "archunit").version("1.4.1") @@ -103,6 +107,8 @@ dependencyResolutionManagement { library("wiremock", "org.wiremock", "wiremock").versionRef(wiremockVersion) library("wiremock-jetty12", "org.wiremock", "wiremock-jetty12").versionRef(wiremockVersion) + library("jetty", "org.eclipse.jetty", "jetty-bom").versionRef(jettyVersion) + library("jetty-ee10", "org.eclipse.jetty.ee10", "jetty-ee10-bom").versionRef(jettyVersion) } } }