Michael Hoennig
d06512f0a0
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: #127 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
455 lines
15 KiB
Groovy
455 lines
15 KiB
Groovy
plugins {
|
|
id 'java'
|
|
id 'org.springframework.boot' version '3.3.4'
|
|
id 'io.spring.dependency-management' version '1.1.6'
|
|
id 'io.openapiprocessor.openapi-processor' version '2023.2'
|
|
id 'com.github.jk1.dependency-license-report' version '2.9'
|
|
id "org.owasp.dependencycheck" version "10.0.4"
|
|
id "com.diffplug.spotless" version "6.25.0"
|
|
id 'jacoco'
|
|
id 'info.solidsoft.pitest' version '1.15.0'
|
|
id 'se.patrikerdes.use-latest-versions' version '0.2.18'
|
|
id 'com.github.ben-manes.versions' version '0.51.0'
|
|
}
|
|
|
|
group = 'net.hostsharing'
|
|
version = '0.0.1-SNAPSHOT'
|
|
|
|
wrapper {
|
|
distributionType = Wrapper.DistributionType.BIN
|
|
gradleVersion = '8.5'
|
|
}
|
|
|
|
configurations {
|
|
compileOnly {
|
|
extendsFrom annotationProcessor
|
|
}
|
|
testCompile {
|
|
extendsFrom testAnnotationProcessor
|
|
|
|
// Only JUNit 5 (Jupiter) should be used at compile time.
|
|
// For runtime it's still needed by testcontainers, though.
|
|
exclude group: 'junit', module: 'junit'
|
|
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
|
|
}
|
|
}
|
|
|
|
repositories {
|
|
mavenCentral()
|
|
maven { url 'https://repo.spring.io/milestone' }
|
|
maven { url 'https://repo.spring.io/snapshot' }
|
|
}
|
|
|
|
java {
|
|
toolchain {
|
|
languageVersion = JavaLanguageVersion.of(21)
|
|
vendor = JvmVendorSpec.ADOPTIUM
|
|
implementation = JvmImplementation.VENDOR_SPECIFIC
|
|
}
|
|
}
|
|
|
|
ext {
|
|
set('testcontainersVersion', "1.17.3")
|
|
}
|
|
|
|
dependencies {
|
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
|
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
|
|
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
|
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
|
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
|
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
|
implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.9.2'
|
|
implementation 'org.springdoc:springdoc-openapi:2.6.0'
|
|
implementation 'org.postgresql:postgresql:42.7.4'
|
|
implementation 'org.liquibase:liquibase-core:4.29.2'
|
|
implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.8.3'
|
|
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.0'
|
|
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
|
|
implementation 'org.apache.commons:commons-text:1.12.0'
|
|
implementation 'net.java.dev.jna:jna:5.15.0'
|
|
implementation 'org.modelmapper:modelmapper:3.2.1'
|
|
implementation 'org.iban4j:iban4j:3.2.10-RELEASE'
|
|
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'
|
|
implementation 'org.webjars:swagger-ui:5.17.14'
|
|
implementation 'org.reflections:reflections:0.10.2'
|
|
|
|
compileOnly 'org.projectlombok:lombok'
|
|
testCompileOnly 'org.projectlombok:lombok'
|
|
|
|
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
|
|
|
annotationProcessor 'org.projectlombok:lombok'
|
|
testAnnotationProcessor 'org.projectlombok:lombok'
|
|
|
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
|
testImplementation 'org.testcontainers:testcontainers'
|
|
testImplementation 'org.testcontainers:junit-jupiter'
|
|
testImplementation 'org.junit.jupiter:junit-jupiter'
|
|
testImplementation 'org.testcontainers:postgresql'
|
|
testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
|
|
testImplementation 'io.rest-assured:spring-mock-mvc'
|
|
testImplementation 'org.hamcrest:hamcrest-core:3.0'
|
|
testImplementation 'org.pitest:pitest-junit5-plugin:1.2.1'
|
|
testImplementation 'org.junit.jupiter:junit-jupiter-api'
|
|
}
|
|
|
|
dependencyManagement {
|
|
imports {
|
|
mavenBom "org.testcontainers:testcontainers-bom:${testcontainersVersion}"
|
|
}
|
|
}
|
|
|
|
// Java Compiler Options
|
|
tasks.withType(JavaCompile) {
|
|
options.compilerArgs += [
|
|
"-parameters" // keep parameter names => no need for @Param for SpringData
|
|
]
|
|
}
|
|
|
|
// Configure tests
|
|
tasks.named('test') {
|
|
useJUnitPlatform()
|
|
jvmArgs '-Duser.language=en'
|
|
jvmArgs '-Duser.country=US'
|
|
}
|
|
|
|
// OpenAPI Source Code Generation
|
|
openapiProcessor {
|
|
springRoot {
|
|
processorName 'spring'
|
|
processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
|
|
apiPath "$projectDir/src/main/resources/api-definition/api-definition.yaml"
|
|
mapping "$projectDir/src/main/resources/api-definition/api-mappings.yaml"
|
|
targetDir "$buildDir/generated/sources/openapi-javax"
|
|
showWarnings true
|
|
openApiNullable true
|
|
}
|
|
springRbac {
|
|
processorName 'spring'
|
|
processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
|
|
apiPath "$projectDir/src/main/resources/api-definition/rbac/rbac.yaml"
|
|
mapping "$projectDir/src/main/resources/api-definition/rbac/api-mappings.yaml"
|
|
targetDir "$buildDir/generated/sources/openapi-javax"
|
|
showWarnings true
|
|
openApiNullable true
|
|
}
|
|
springTest {
|
|
processorName 'spring'
|
|
processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
|
|
apiPath "$projectDir/src/main/resources/api-definition/test/test.yaml"
|
|
mapping "$projectDir/src/main/resources/api-definition/test/api-mappings.yaml"
|
|
targetDir "$buildDir/generated/sources/openapi-javax"
|
|
showWarnings true
|
|
openApiNullable true
|
|
}
|
|
springHsOffice {
|
|
processorName 'spring'
|
|
processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
|
|
apiPath "$projectDir/src/main/resources/api-definition/hs-office/hs-office.yaml"
|
|
mapping "$projectDir/src/main/resources/api-definition/hs-office/api-mappings.yaml"
|
|
targetDir "$buildDir/generated/sources/openapi-javax"
|
|
showWarnings true
|
|
openApiNullable true
|
|
}
|
|
springHsBooking {
|
|
processorName 'spring'
|
|
processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
|
|
apiPath "$projectDir/src/main/resources/api-definition/hs-booking/hs-booking.yaml"
|
|
mapping "$projectDir/src/main/resources/api-definition/hs-booking/api-mappings.yaml"
|
|
targetDir "$buildDir/generated/sources/openapi-javax"
|
|
showWarnings true
|
|
openApiNullable true
|
|
}
|
|
springHsHosting {
|
|
processorName 'spring'
|
|
processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
|
|
apiPath "$projectDir/src/main/resources/api-definition/hs-hosting/hs-hosting.yaml"
|
|
mapping "$projectDir/src/main/resources/api-definition/hs-hosting/api-mappings.yaml"
|
|
targetDir "$buildDir/generated/sources/openapi-javax"
|
|
showWarnings true
|
|
openApiNullable true
|
|
}
|
|
}
|
|
sourceSets.main.java.srcDir 'build/generated/sources/openapi'
|
|
abstract class ProcessSpring extends DefaultTask {}
|
|
tasks.register('processSpring', ProcessSpring)
|
|
['processSpringRoot',
|
|
'processSpringRbac',
|
|
'processSpringTest',
|
|
'processSpringHsOffice',
|
|
'processSpringHsBooking',
|
|
'processSpringHsHosting'
|
|
].each {
|
|
project.tasks.processSpring.dependsOn it
|
|
}
|
|
project.tasks.processResources.dependsOn processSpring
|
|
project.tasks.compileJava.dependsOn processSpring
|
|
|
|
// Rename javax to jakarta in OpenApi generated java files because
|
|
// io.openapiprocessor.openapi-processor 2022.5 does not yet support the openapiprocessor useSpringBoot3 config option.
|
|
// TODO.impl: Upgrade to io.openapiprocessor.openapi-processor >= 2024.2
|
|
// and use either `bean-validation: true` in api-mapping.yaml or `useSpringBoot3 true` (not sure where exactly).
|
|
task openApiGenerate(type: Copy) {
|
|
from "$buildDir/generated/sources/openapi-javax"
|
|
into "$buildDir/generated/sources/openapi"
|
|
filter { line -> line.replaceAll('javax', 'jakarta') }
|
|
}
|
|
compileJava.source "$buildDir/generated/sources/openapi"
|
|
compileJava.dependsOn openApiGenerate
|
|
openApiGenerate.dependsOn processSpring
|
|
|
|
// Spotless Code Formatting
|
|
spotless {
|
|
java {
|
|
removeUnusedImports()
|
|
indentWithSpaces(4)
|
|
endWithNewline()
|
|
toggleOffOn()
|
|
|
|
target fileTree(rootDir) {
|
|
include '**/*.java'
|
|
exclude '**/generated/**/*.java'
|
|
}
|
|
}
|
|
}
|
|
project.tasks.check.dependsOn(spotlessCheck)
|
|
// HACK: no idea why spotless uses the output of these tasks, but we get warnings without those
|
|
project.tasks.spotlessJava.dependsOn(
|
|
tasks.generateLicenseReport,
|
|
tasks.pitest,
|
|
tasks.jacocoTestReport,
|
|
tasks.processResources,
|
|
tasks.processTestResources)
|
|
|
|
// OWASP Dependency Security Test
|
|
dependencyCheck {
|
|
nvd {
|
|
apiKey = project.properties['OWASP_API_KEY'] // set it in ~/.gradle/gradle.properties
|
|
delay = 16000
|
|
}
|
|
format = 'ALL'
|
|
suppressionFile = 'etc/owasp-dependency-check-suppression.xml'
|
|
failOnError = true
|
|
failBuildOnCVSS = 5
|
|
}
|
|
project.tasks.check.dependsOn(dependencyCheckAnalyze)
|
|
project.tasks.dependencyCheckAnalyze.doFirst { // Why not doLast? See README.md!
|
|
println "OWASP Dependency Security Report: file:///${project.rootDir}/build/reports/dependency-check-report.html"
|
|
}
|
|
|
|
|
|
// License Check
|
|
licenseReport {
|
|
excludeBoms = true
|
|
allowedLicensesFile = new File("$projectDir/etc/allowed-licenses.json")
|
|
}
|
|
project.tasks.check.dependsOn(checkLicense)
|
|
|
|
// JaCoCo Test Code Coverage
|
|
jacoco {
|
|
toolVersion = "0.8.10"
|
|
}
|
|
test {
|
|
finalizedBy jacocoTestReport // generate report after tests
|
|
excludes = [
|
|
'net.hostsharing.hsadminng.**.generated.**',
|
|
]
|
|
useJUnitPlatform {
|
|
excludeTags 'importOfficeData', 'importHostingData', 'scenarioTest'
|
|
}
|
|
}
|
|
jacocoTestReport {
|
|
dependsOn test
|
|
afterEvaluate {
|
|
classDirectories.setFrom(files(classDirectories.files.collect {
|
|
fileTree(dir: it, exclude: [
|
|
"net/hostsharing/hsadminng/**/generated/**/*.class",
|
|
"net/hostsharing/hsadminng/hs/HsadminNgApplication.class"
|
|
])
|
|
}))
|
|
}
|
|
doFirst { // Why not doLast? See README.md!
|
|
println "HTML Jacoco Test Code Coverage Report: file://${reports.html.outputLocation.get()}/index.html"
|
|
}
|
|
}
|
|
project.tasks.check.dependsOn(jacocoTestCoverageVerification)
|
|
jacocoTestCoverageVerification {
|
|
violationRules {
|
|
rule {
|
|
limit {
|
|
minimum = 0.80 // TODO.test: improve instruction coverage
|
|
}
|
|
}
|
|
|
|
// element: PACKAGE, BUNDLE, CLASS, SOURCEFILE or METHOD
|
|
// counter: INSTRUCTION, BRANCH, LINE, COMPLEXITY, METHOD, or CLASS
|
|
// value: TOTALCOUNT, COVEREDCOUNT, MISSEDCOUNT, COVEREDRATIO or MISSEDRATIO
|
|
|
|
rule {
|
|
element = 'CLASS'
|
|
excludes = [
|
|
'net.hostsharing.hsadminng.**.generated.**',
|
|
'net.hostsharing.hsadminng.rbac.test.dom.TestDomainEntity',
|
|
'net.hostsharing.hsadminng.HsadminNgApplication',
|
|
'net.hostsharing.hsadminng.ping.PingController',
|
|
'net.hostsharing.hsadminng.rbac.generator.*',
|
|
'net.hostsharing.hsadminng.rbac.grant.RbacGrantsDiagramService',
|
|
'net.hostsharing.hsadminng.rbac.grant.RbacGrantsDiagramService.Node',
|
|
'net.hostsharing.hsadminng.**.*Repository',
|
|
'net.hostsharing.hsadminng.mapper.Mapper'
|
|
]
|
|
|
|
limit {
|
|
counter = 'LINE'
|
|
value = 'COVEREDRATIO'
|
|
minimum = 0.75 // TODO.test: improve line coverage
|
|
}
|
|
}
|
|
rule {
|
|
element = 'METHOD'
|
|
excludes = [
|
|
'net.hostsharing.hsadminng.**.generated.**',
|
|
'net.hostsharing.hsadminng.HsadminNgApplication.main',
|
|
'net.hostsharing.hsadminng.ping.PingController.*'
|
|
]
|
|
|
|
limit {
|
|
counter = 'BRANCH'
|
|
value = 'COVEREDRATIO'
|
|
minimum = 0.00 // TODO.test: improve branch coverage
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
tasks.register('importOfficeData', Test) {
|
|
useJUnitPlatform {
|
|
includeTags 'importOfficeData'
|
|
}
|
|
|
|
group 'verification'
|
|
description 'run the import jobs as tests'
|
|
|
|
mustRunAfter spotlessJava
|
|
}
|
|
|
|
tasks.register('importHostingAssets', Test) {
|
|
useJUnitPlatform {
|
|
includeTags 'importHostingAssets'
|
|
}
|
|
|
|
group 'verification'
|
|
description 'run the import jobs as tests'
|
|
|
|
mustRunAfter spotlessJava
|
|
}
|
|
|
|
tasks.register('scenarioTests', Test) {
|
|
useJUnitPlatform {
|
|
includeTags 'scenarioTest'
|
|
}
|
|
|
|
group 'verification'
|
|
description 'run the import jobs as tests'
|
|
|
|
mustRunAfter spotlessJava
|
|
}
|
|
|
|
// pitest mutation testing
|
|
pitest {
|
|
targetClasses = ['net.hostsharing.hsadminng.**']
|
|
excludedClasses = [
|
|
'net.hostsharing.hsadminng.config.**',
|
|
// 'net.hostsharing.hsadminng.**.*Controller',
|
|
'net.hostsharing.hsadminng.**.generated.**'
|
|
]
|
|
|
|
targetTests = ['net.hostsharing.hsadminng.**.*UnitTest', 'net.hostsharing.hsadminng.**.*RestTest']
|
|
excludedTestClasses = ['**AcceptanceTest*', '**IntegrationTest*']
|
|
|
|
pitestVersion = '1.17.0'
|
|
junit5PluginVersion = '1.1.0'
|
|
|
|
threads = 4
|
|
|
|
// As Java unit tests are pretty pointless in our case, this maybe makes not much sense.
|
|
mutationThreshold = 71
|
|
coverageThreshold = 57
|
|
testStrengthThreshold = 87
|
|
|
|
outputFormats = ['XML', 'HTML']
|
|
timestampedReports = false
|
|
}
|
|
project.tasks.check.dependsOn(project.tasks.pitest)
|
|
project.tasks.pitest.doFirst { // Why not doLast? See README.md!
|
|
println "PiTest Mutation Report: file:///${project.rootDir}/build/reports/pitest/index.html"
|
|
}
|
|
|
|
|
|
// Dependency Versions Upgrade
|
|
useLatestVersions {
|
|
finalizedBy check
|
|
}
|
|
|
|
def isNonStable = { String version ->
|
|
def stableKeyword = ['RELEASE', 'FINAL', 'GA'].any { it -> version.toUpperCase().contains(it) }
|
|
def regex = /^[0-9,.v-]+(-r)?$/
|
|
return !stableKeyword && !(version ==~ regex)
|
|
}
|
|
|
|
tasks.named("dependencyUpdates").configure {
|
|
rejectVersionIf {
|
|
isNonStable(it.candidate.version)
|
|
}
|
|
}
|
|
|
|
|
|
// Generate HTML from Markdown scenario-test-reports using Pandoc:
|
|
tasks.register('convertMarkdownToHtml') {
|
|
description = 'Generates HTML from Markdown scenario-test-reports using Pandoc.'
|
|
group = 'Conversion'
|
|
|
|
// Define the template file and input directory
|
|
def templateFile = file('doc/scenarios/.template.html')
|
|
|
|
// Task configuration and execution
|
|
doFirst {
|
|
// Check if pandoc is installed
|
|
try {
|
|
exec {
|
|
commandLine 'pandoc', '--version'
|
|
}
|
|
} catch (Exception) {
|
|
throw new GradleException("Pandoc is not installed or not found in the system path.")
|
|
}
|
|
|
|
// Check if the template file exists
|
|
if (!templateFile.exists()) {
|
|
throw new GradleException("Template file 'doc/scenarios/.template.html' not found.")
|
|
}
|
|
}
|
|
|
|
doLast {
|
|
// Gather all Markdown files in the current directory
|
|
fileTree(dir: '.', include: 'build/doc/scenarios/*.md').each { file ->
|
|
// Corrected way to create the output file path
|
|
def outputFile = new File(file.parent, file.name.replaceAll(/\.md$/, '.html'))
|
|
|
|
// Execute pandoc for each markdown file
|
|
exec {
|
|
commandLine 'pandoc', file.absolutePath, '--template', templateFile.absolutePath, '-o', outputFile.absolutePath
|
|
}
|
|
|
|
println "Converted ${file.name} to ${outputFile.name}"
|
|
}
|
|
}
|
|
}
|
|
convertMarkdownToHtml.dependsOn scenarioTests
|
|
|
|
// shortcut for compiling all files
|
|
tasks.register('compile') {
|
|
dependsOn 'compileJava', 'compileTestJava'
|
|
}
|