Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TECH] Ajout de tests unitaires pour vérifier la clean architecture avec ArchUnit #1527

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ dev-run-front:
dev-lint-frontend:
cd frontend && npm run test:lint

test-back: check-clean-archi
test-back:
cd backend && ./gradlew clean test

test-front:
Expand Down Expand Up @@ -74,9 +74,7 @@ dev-erase-db:
dev-clean-target-env:
rm -rf $(shell pwd)/backend/target

.PHONY: clean lint-back test check-clean-archi
check-clean-archi:
cd backend/tools && ./check-clean-architecture.sh
.PHONY: clean lint-back test

lint-back:
cd ./backend && ./gradlew ktlintFormat | grep -v \
Expand Down
1 change: 1 addition & 0 deletions backend/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ dependencies {
testImplementation("org.springframework.restdocs:spring-restdocs-mockmvc:3.0.1")
testImplementation("org.testcontainers:junit-jupiter:1.19.8")
testImplementation("net.java.dev.jna:jna:5.14.0")
testImplementation("com.tngtech.archunit:archunit-junit5:1.3.0")
// TODO: move in tests only
api("net.ttddyy:datasource-proxy:1.10")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package fr.gouv.cacem.monitorenv.config

import org.springframework.scheduling.annotation.Scheduled

@Scheduled
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Scheduled(
val cron: String = "",
val zone: String = "",
val fixedDelay: Long = -1,
val fixedRate: Long = -1,
val initialDelay: Long = -1,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package fr.gouv.cacem.monitorenv.domain.event

interface EventPublisher<T> {
fun publish(event: T)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionContr
import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.EnvActionControlProperties
import fr.gouv.cacem.monitorenv.domain.exceptions.EntityConversionException
import org.locationtech.jts.geom.Geometry
import org.springframework.stereotype.Component
import java.time.ZonedDateTime
import java.util.UUID
import java.util.*

@Component
object EnvActionMapper {
private const val JSONB_NULL_STRING = "null"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ package fr.gouv.cacem.monitorenv.domain.use_cases.missions

import fr.gouv.cacem.monitorenv.config.UseCase
import fr.gouv.cacem.monitorenv.domain.entities.mission.*
import fr.gouv.cacem.monitorenv.domain.event.EventPublisher
import fr.gouv.cacem.monitorenv.domain.repositories.IFacadeAreasRepository
import fr.gouv.cacem.monitorenv.domain.repositories.IMissionRepository
import fr.gouv.cacem.monitorenv.domain.repositories.IPostgisFunctionRepository
import fr.gouv.cacem.monitorenv.domain.use_cases.missions.events.UpdateMissionEvent
import org.slf4j.LoggerFactory
import org.springframework.context.ApplicationEventPublisher

@UseCase
class CreateOrUpdateMission(
private val facadeRepository: IFacadeAreasRepository,
private val missionRepository: IMissionRepository,
private val postgisFunctionRepository: IPostgisFunctionRepository,
private val eventPublisher: ApplicationEventPublisher,
private val eventPublisher: EventPublisher<UpdateMissionEvent>,
) {
private val logger = LoggerFactory.getLogger(CreateOrUpdateMission::class.java)

Expand Down Expand Up @@ -46,7 +46,7 @@ class CreateOrUpdateMission(
}

logger.info("Sending CREATE/UPDATE event for mission id ${savedMission.mission.id}.")
eventPublisher.publishEvent(
eventPublisher.publish(
UpdateMissionEvent(savedMission.mission),
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package fr.gouv.cacem.monitorenv.domain.use_cases.reportings

import fr.gouv.cacem.monitorenv.config.Scheduled
import fr.gouv.cacem.monitorenv.config.UseCase
import fr.gouv.cacem.monitorenv.domain.repositories.IReportingRepository
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.scheduling.annotation.Scheduled

@UseCase
class ArchiveOutdatedReportings(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ package fr.gouv.cacem.monitorenv.domain.use_cases.reportings

import fr.gouv.cacem.monitorenv.config.UseCase
import fr.gouv.cacem.monitorenv.domain.entities.reporting.ReportingEntity
import fr.gouv.cacem.monitorenv.domain.event.EventPublisher
import fr.gouv.cacem.monitorenv.domain.exceptions.ReportingAlreadyAttachedException
import fr.gouv.cacem.monitorenv.domain.repositories.*
import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.dtos.ReportingDTO
import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.events.UpdateReportingEvent
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.context.ApplicationEventPublisher

@UseCase
class CreateOrUpdateReporting(
private val reportingRepository: IReportingRepository,
private val facadeRepository: IFacadeAreasRepository,
private val postgisFunctionRepository: IPostgisFunctionRepository,
private val eventPublisher: ApplicationEventPublisher,
private val eventPublisher: EventPublisher<UpdateReportingEvent>,
) {
private val logger: Logger = LoggerFactory.getLogger(CreateOrUpdateReporting::class.java)

Expand Down Expand Up @@ -61,7 +61,7 @@ class CreateOrUpdateReporting(
)

logger.info("Sending CREATE/UPDATE event for reporting id ${savedReporting.reporting.id}.")
eventPublisher.publishEvent(
eventPublisher.publish(
UpdateReportingEvent(savedReporting),
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package fr.gouv.cacem.monitorenv.infrastructure.event

import fr.gouv.cacem.monitorenv.domain.event.EventPublisher
import fr.gouv.cacem.monitorenv.domain.use_cases.missions.events.UpdateMissionEvent
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Component

@Component
class MissionPublisher(private val eventPublisher: ApplicationEventPublisher) : EventPublisher<UpdateMissionEvent> {
override fun publish(event: UpdateMissionEvent) {
eventPublisher.publishEvent(event)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package fr.gouv.cacem.monitorenv.infrastructure.event

import fr.gouv.cacem.monitorenv.domain.event.EventPublisher
import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.events.UpdateReportingEvent
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Component

@Component
class ReportingPublisher(private val eventPublisher: ApplicationEventPublisher) : EventPublisher<UpdateReportingEvent> {
override fun publish(event: UpdateReportingEvent) {
eventPublisher.publishEvent(event)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package fr.gouv.cacem.monitorenv

import com.tngtech.archunit.core.domain.JavaClasses
import com.tngtech.archunit.core.importer.ImportOption
import com.tngtech.archunit.junit.AnalyzeClasses
import com.tngtech.archunit.junit.ArchTest
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses

private const val DOMAIN_PACKAGE = "..domain.."
private const val DOMAIN_ENTITIES_PACKAGE = "..domain.entities.."
private const val DOMAIN_REPOSITORIES_PACKAGE = "..domain.repositories.."
private const val DOMAIN_USE_CASES_PACKAGE = "..domain.use_cases.."

private val UNAUTHORIZED_PACKAGES_IN_DOMAIN = listOf(
"infrastructure",
"org.springframework",
"jakarta",
)

@AnalyzeClasses(packages = ["fr.gouv.cacem.monitorenv"], importOptions = [ImportOption.DoNotIncludeTests::class])
class LayeredArchitectureTest {

@ArchTest
fun `domain entities and repositories should not use unauthorized packages`(importedClasses: JavaClasses) {
UNAUTHORIZED_PACKAGES_IN_DOMAIN.forEach { packageName ->
noClasses()
.that().resideInAnyPackage(DOMAIN_ENTITIES_PACKAGE, DOMAIN_REPOSITORIES_PACKAGE)
.should().dependOnClassesThat().resideInAPackage("..$packageName..")
.check(importedClasses)
}
}

@ArchTest
fun `domain use cases should not use unauthorized packages`(importedClasses: JavaClasses) {
UNAUTHORIZED_PACKAGES_IN_DOMAIN.forEach { packageName ->
noClasses()
.that().resideInAnyPackage(DOMAIN_USE_CASES_PACKAGE)
.should().dependOnClassesThat().resideInAPackage("..$packageName..")
.check(importedClasses)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,13 @@ import fr.gouv.cacem.monitorenv.domain.entities.reporting.ReportingTypeEnum
import fr.gouv.cacem.monitorenv.domain.entities.reporting.SourceTypeEnum
import fr.gouv.cacem.monitorenv.domain.entities.reporting.TargetTypeEnum
import fr.gouv.cacem.monitorenv.domain.entities.semaphore.SemaphoreEntity
import fr.gouv.cacem.monitorenv.domain.event.EventPublisher
import fr.gouv.cacem.monitorenv.domain.exceptions.ReportingAlreadyAttachedException
import fr.gouv.cacem.monitorenv.domain.repositories.IControlUnitRepository
import fr.gouv.cacem.monitorenv.domain.repositories.IFacadeAreasRepository
import fr.gouv.cacem.monitorenv.domain.repositories.IMissionRepository
import fr.gouv.cacem.monitorenv.domain.repositories.IPostgisFunctionRepository
import fr.gouv.cacem.monitorenv.domain.repositories.IReportingRepository
import fr.gouv.cacem.monitorenv.domain.repositories.ISemaphoreRepository
import fr.gouv.cacem.monitorenv.domain.repositories.*
import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.dtos.FullControlUnitDTO
import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.CreateOrUpdateReporting
import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.dtos.ReportingDTO
import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.events.UpdateReportingEvent
import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
Expand All @@ -30,25 +27,31 @@ import org.locationtech.jts.geom.MultiPolygon
import org.locationtech.jts.geom.Point
import org.locationtech.jts.io.WKTReader
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.context.ApplicationEventPublisher
import org.springframework.test.context.junit.jupiter.SpringExtension
import java.time.ZonedDateTime

@ExtendWith(SpringExtension::class)
class CreateOrUpdateReportingUTests {
@MockBean private lateinit var reportingRepository: IReportingRepository
@MockBean
private lateinit var reportingRepository: IReportingRepository

@MockBean private lateinit var controlUnitRepository: IControlUnitRepository
@MockBean
private lateinit var controlUnitRepository: IControlUnitRepository

@MockBean private lateinit var semaphoreRepository: ISemaphoreRepository
@MockBean
private lateinit var semaphoreRepository: ISemaphoreRepository

@MockBean private lateinit var facadeRepository: IFacadeAreasRepository
@MockBean
private lateinit var facadeRepository: IFacadeAreasRepository

@MockBean private lateinit var missionRepository: IMissionRepository
@MockBean
private lateinit var missionRepository: IMissionRepository

@MockBean private lateinit var postgisFunctionRepository: IPostgisFunctionRepository
@MockBean
private lateinit var postgisFunctionRepository: IPostgisFunctionRepository

@MockBean private lateinit var applicationEventPublisher: ApplicationEventPublisher
@MockBean
private lateinit var eventPublisher: EventPublisher<UpdateReportingEvent>

@Test
fun `Should throw an exception when input is null`() {
Expand All @@ -59,7 +62,7 @@ class CreateOrUpdateReportingUTests {
reportingRepository = reportingRepository,
facadeRepository = facadeRepository,
postgisFunctionRepository = postgisFunctionRepository,
eventPublisher = applicationEventPublisher,
eventPublisher = eventPublisher,
)
.execute(null)
}
Expand Down Expand Up @@ -221,7 +224,7 @@ class CreateOrUpdateReportingUTests {
reportingRepository = reportingRepository,
facadeRepository = facadeRepository,
postgisFunctionRepository = postgisFunctionRepository,
eventPublisher = applicationEventPublisher,
eventPublisher = eventPublisher,
)
.execute(reportingWithSemaphore)

Expand All @@ -236,7 +239,7 @@ class CreateOrUpdateReportingUTests {
reportingRepository = reportingRepository,
facadeRepository = facadeRepository,
postgisFunctionRepository = postgisFunctionRepository,
eventPublisher = applicationEventPublisher,
eventPublisher = eventPublisher,
)
.execute(reportingWithControlUnit)

Expand Down Expand Up @@ -284,7 +287,7 @@ class CreateOrUpdateReportingUTests {
reportingRepository = reportingRepository,
facadeRepository = facadeRepository,
postgisFunctionRepository = postgisFunctionRepository,
eventPublisher = applicationEventPublisher,
eventPublisher = eventPublisher,
)
.execute(reporting)
}
Expand Down Expand Up @@ -334,7 +337,7 @@ class CreateOrUpdateReportingUTests {
reportingRepository = reportingRepository,
facadeRepository = facadeRepository,
postgisFunctionRepository = postgisFunctionRepository,
eventPublisher = applicationEventPublisher,
eventPublisher = eventPublisher,
)
.execute(reporting)
}
Expand Down Expand Up @@ -428,7 +431,7 @@ class CreateOrUpdateReportingUTests {
reportingRepository = reportingRepository,
facadeRepository = facadeRepository,
postgisFunctionRepository = postgisFunctionRepository,
eventPublisher = applicationEventPublisher,
eventPublisher = eventPublisher,
)
.execute(reportingWithControlUnitId)
}
Expand All @@ -445,7 +448,7 @@ class CreateOrUpdateReportingUTests {
reportingRepository = reportingRepository,
facadeRepository = facadeRepository,
postgisFunctionRepository = postgisFunctionRepository,
eventPublisher = applicationEventPublisher,
eventPublisher = eventPublisher,
)
.execute(reportingWithSemaphoreId)
}
Expand All @@ -462,7 +465,7 @@ class CreateOrUpdateReportingUTests {
reportingRepository = reportingRepository,
facadeRepository = facadeRepository,
postgisFunctionRepository = postgisFunctionRepository,
eventPublisher = applicationEventPublisher,
eventPublisher = eventPublisher,
)
.execute(reportingWithoutSourceName)
}
Expand Down Expand Up @@ -549,7 +552,7 @@ class CreateOrUpdateReportingUTests {
reportingRepository = reportingRepository,
facadeRepository = facadeRepository,
postgisFunctionRepository = postgisFunctionRepository,
eventPublisher = applicationEventPublisher,
eventPublisher = eventPublisher,
)
.execute(reportingWithNewAttachedMission)
}
Expand Down
Loading
Loading