// Application entry point. Wires the default runtime module set and runs Spring Boot. // Optional leaves require an explicit registry allowance plus a composition-root dependency. apply plugin: 'org.springframework.boot' apply from: "${rootProject.projectDir}/gradle/strict-qualification-test.gradle" sourceSets { sampleOffTest { java.srcDirs = sourceSets.test.java.srcDirs java.srcDir 'src/sampleOffTest/java' resources.srcDirs = sourceSets.test.resources.srcDirs compileClasspath += sourceSets.main.output runtimeClasspath += sourceSets.main.output } functionalTest { java.srcDir 'src/functionalTest/java' resources.srcDir 'src/functionalTest/resources' // feature-techlog-studio-backend Task 10 — StudioContractDriftTest reuses // RepositoryContractResources (test-sourceSet-owned, see // dev.caskeleton.bootstrap.contract.support) for fail-closed repo-root resolution instead // of a hand-rolled relative Path.of(..), matching the sibling contract tests' convention. compileClasspath += sourceSets.test.output runtimeClasspath += sourceSets.test.output } conditionalTransportTest { java.srcDir 'src/conditionalTransportTest/java' resources.srcDir 'src/conditionalTransportTest/resources' compileClasspath += sourceSets.main.output runtimeClasspath += sourceSets.main.output } } // objectstorage 모듈은 AWS SDK BOM 을 자기 스코프로만 import 한다(그 모듈 주석 참고 — // strict locking 의 영향 범위를 좁히려는 의도다). Spring 의 dependency-management 는 // 소비자에게 전파되지 않으므로, 런타임을 조립하는 이쪽에서 같은 SSOT 로 한 번 더 선언한다. // 이것이 없으면 s3/netty-nio-client 가 버전 없이 남아 bootJar 가 해석에 실패한다. dependencyManagement { imports { mavenBom "software.amazon.awssdk:bom:${awsSdkVersion}" } } configurations { sampleOffTestImplementation.extendsFrom testImplementation sampleOffTestCompileOnly.extendsFrom testCompileOnly sampleOffTestRuntimeOnly.extendsFrom testRuntimeOnly sampleOffTestAnnotationProcessor.extendsFrom testAnnotationProcessor } dependencies { implementation project(':domain-core') implementation project(':application-core') implementation project(':adapter:outbound:persistence-jpa') implementation project(':adapter:outbound:support') implementation project(':adapter:outbound:messaging') implementation project(':adapter:outbound:cache-redis') implementation project(':adapter:outbound:notification') implementation project(':adapter:outbound:fileserver') implementation project(':adapter:outbound:httpclient') implementation project(':adapter:outbound:identifier') // Studio Asset 바이너리의 실제 저장 구현. 이것이 없으면 ObjectStorageAssetBinaryAdapter 의 // ObjectProvider 가 항상 비어서 업로드·삭제가 STUDIO_UNAVAILABLE 로 // 거절된다 — 컴파일은 통과하므로 빌드로는 드러나지 않고 런타임에만 나타난다. implementation project(':adapter:outbound:objectstorage') implementation project(':adapter:inbound:web') implementation project(':shared-contract') implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-validation' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' implementation 'me.paulschwarz:spring-dotenv:4.0.0' // Boot 4 Flyway API/autoconfiguration: the composition root drives startup migration // (MigrationStartupConfig). See README. implementation 'org.springframework.boot:spring-boot-flyway' // Flyway API: MigrationStartupRunner directly invokes Flyway. Kept explicit for readability. implementation 'org.flywaydb:flyway-core' // Micrometer core for OutboxMetrics meters (no-op without a MeterRegistry). See README. implementation 'io.micrometer:micrometer-core' // OTel/Micrometer tracer runtime (exporter stays off until an OTLP endpoint is set). See README. implementation 'io.micrometer:micrometer-tracing-bridge-otel' implementation 'io.opentelemetry:opentelemetry-exporter-otlp' // Actuator + Prometheus registry (health/info/prometheus/loggers endpoints). implementation 'org.springframework.boot:spring-boot-starter-actuator' // The composition root wires the OAuth2 credential provider for the HTTP Client Platform, // so it must see OAuth2AuthorizedClientManager. The adapter keeps the dependency internal. implementation 'org.springframework.security:spring-security-oauth2-client' implementation 'io.micrometer:micrometer-registry-prometheus' // Security types for ManagementSecurityConfig (not reachable via adapter-web's implementation dep). See README. implementation 'org.springframework.boot:spring-boot-starter-security' // Redis-backed HTTP session for auth-mode=redis-session (the BFF surface the Studio contract // declares: sessionCookie TECHLOG_SESSION + X-CSRF-TOKEN). AuthenticationModeCompositionConfig // requires the `redisVersionedSessionRepository` / `springSessionRepositoryFilter` pair once // that mode is active; StudioSessionInfrastructureConfig supplies the first, Spring Session's // SpringHttpSessionConfiguration the second. // OIDC Authorization Code 로그인 자동설정(ClientRegistrationRepository 등). 기존의 // spring-security-oauth2-client 는 라이브러리만 주고 Boot 자동설정은 스타터가 준다. implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.session:spring-session-data-redis' implementation 'org.springframework.boot:spring-boot-starter-data-redis' // test-only: ArchUnit needs actuator types to verify the health-shape guardrail. See README. testImplementation 'org.springframework.boot:spring-boot-starter-actuator' // test-only: @WithMockUser for the actuator security authorization tests. See README. testImplementation 'org.springframework.security:spring-security-test' // test-only: Testcontainers PostgreSQL for the outbox contract tests. testImplementation 'org.testcontainers:testcontainers-postgresql' testImplementation 'org.testcontainers:testcontainers-junit-jupiter' // test-only: Boot 4 split JPA slice annotations into dedicated test modules. testImplementation 'org.springframework.boot:spring-boot-data-jpa-test' // test-only: PG JDBC driver (the vendor module's runtimeOnly does not leak here). See README. testRuntimeOnly 'org.postgresql:postgresql' // test-only: JPA/Hikari for the outbox tests' minimal context (not leaked from rdbms). See README. testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa' testImplementation 'com.zaxxer:HikariCP' // test-only: JdbcLockRegistry for the distributed-lock contract test (two simulated instances). See README. testImplementation 'org.springframework.integration:spring-integration-jdbc' testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0' // test-only: jackson-databind for the deserialization-policy boundary test. See README. testImplementation 'org.springframework.boot:spring-boot-starter-json' // test-only: parses checked-in Messaging capability registries for the fail-closed drift gate. testImplementation 'org.yaml:snakeyaml' // test-only: ArchUnit violation fixtures intentionally import forbidden types. See README. testCompileOnly 'org.springframework:spring-tx' // test-only: streaming/websocket violation fixtures import these forbidden packages. See README. testCompileOnly 'org.springframework:spring-webmvc' // SseEmitter, ResponseBodyEmitter, StreamingResponseBody testCompileOnly 'org.springframework:spring-websocket' // org.springframework.web.socket.. testCompileOnly 'jakarta.websocket:jakarta.websocket-api' // jakarta.websocket.. // test-only: transport-free domain-event fixtures import these forbidden broker/wire packages. See README. testCompileOnly 'org.apache.kafka:kafka-clients' // org.apache.kafka.. testCompileOnly 'jakarta.ws.rs:jakarta.ws.rs-api' // jakarta.ws.rs.. // test-only: @RefreshScope for the no-refresh-scope violation fixture (version pinned; not in the BOM). See README. testCompileOnly 'org.springframework.cloud:spring-cloud-context:4.1.4' // org.springframework.cloud.context.. // JSON log encoder; implementation (not runtimeOnly) because StartupFailures uses StructuredArguments at compile time. See README. implementation 'net.logstash.logback:logstash-logback-encoder:8.0' // test-only: ApprovalTests JSON snapshot verification for the contract-verification suite // (feature-contract-verification-test-suite D7 — envelope/error shape snapshots). See README. testImplementation 'com.approvaltests:approvaltests:31.0.0' // test-only: JUnit Platform Test Kit — proves optional-adapter tests report SKIPPED (never FAILED) // when their enable-flag env var is unset (feature-contract-verification-test-suite D3, Claims #7). See README. testImplementation 'org.junit.platform:junit-platform-testkit' // functional-test-only: executes isolated Gradle fixtures without placing Gradle's SLF4J // provider on the ordinary test runtime classpath. functionalTestImplementation gradleTestKit() functionalTestImplementation 'org.junit.jupiter:junit-jupiter' functionalTestImplementation 'org.assertj:assertj-core' functionalTestRuntimeOnly 'org.junit.platform:junit-platform-launcher' // feature-techlog-studio-backend Task 10 — StudioContractDriftTest boots a minimal Studio web // slice (real StudioSessionController/StudioCatalogController, no persistence/messaging/cache) // to diff springdoc's published /api/v1/studio/** surface against studio-v1.yaml. Only the two // modules the slice actually needs — deliberately not the full app-bootstrap runtime graph, so // no DataSource/Flyway/Redis auto-configuration is even on this classpath to exclude. functionalTestImplementation project(':adapter:inbound:web') functionalTestImplementation project(':application-core') // test-only: @SpringBootTest/MockMvc/@AutoConfigureMockMvc — mirrors the root build.gradle // subprojects{} pair every non-core module already gets on its ordinary `test` sourceSet. functionalTestImplementation 'org.springframework.boot:spring-boot-starter-test' functionalTestImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test' // test-only: classic Jackson (2.x) ObjectMapper/JsonNode to read /v3/api-docs and convert the // SnakeYaml-parsed contract into a comparable tree. adapter-inbound-web/application-core pull // this in only as a project-dependency `implementation` (hidden from a consumer's // compileClasspath by Gradle's api/implementation split), so it must be declared directly here // — mirrors the existing `testImplementation 'org.springframework.boot:spring-boot-starter-json'` // pattern below for app-bootstrap's own `test` sourceSet. functionalTestImplementation 'com.fasterxml.jackson.core:jackson-databind' // test-only: org.springframework.boot.security.autoconfigure.SecurityAutoConfiguration (excluded // below) is part of spring-boot-security, only pulled in transitively by spring-boot-starter-security // — same api/implementation-hiding reason as jackson-databind above. app-bootstrap declares this // on `implementation` for its own main/test sourceSets, which functionalTest does not extend. functionalTestImplementation 'org.springframework.boot:spring-boot-starter-security' // test-only: parses config/openapi/studio-v1.yaml with the same library StudioErrorRegistryTest // already uses for docs/registries/error-codes.yaml — avoids adding jackson-dataformat-yaml // (present only on runtimeClasspath repo-wide, transitively via springdoc, not compileClasspath). functionalTestImplementation 'org.yaml:snakeyaml' // Explicit qualification-only composition. These projects remain absent from main // api/implementation/compileOnly/runtimeOnly and therefore from both shipped runtime graphs. conditionalTransportTestImplementation project(':adapter:inbound:graphql') conditionalTransportTestImplementation project(':adapter:inbound:grpc') conditionalTransportTestImplementation project(':adapter:inbound:websocket') conditionalTransportTestImplementation 'org.junit.jupiter:junit-jupiter' conditionalTransportTestImplementation 'org.assertj:assertj-core' conditionalTransportTestImplementation 'org.yaml:snakeyaml' conditionalTransportTestRuntimeOnly 'org.junit.platform:junit-platform-launcher' } def repositoryRootForContractTests = rootProject.projectDir.parentFile.absolutePath def contractRegistriesDirectory = rootProject.projectDir.parentFile.toPath() .resolve('docs/registries').toFile() tasks.withType(Test).configureEach { systemProperty 'ca.repository.root', repositoryRootForContractTests } // Pin UTC for the TEST JVM so timestamp tests are host-locale-independent (production UTC owned elsewhere). See README. tasks.named('test') { inputs.dir(contractRegistriesDirectory) .withPathSensitivity(PathSensitivity.RELATIVE) jvmArgs '-Duser.timezone=UTC' } tasks.register('sampleOffCompile') { group = 'verification' description = 'Compiles the complete app-bootstrap test corpus without sample-portfolio.' dependsOn tasks.named(sourceSets.sampleOffTest.classesTaskName) } def sampleOffQualification = registerStrictQualificationTest( name: 'sampleOffTest', sourceSet: sourceSets.sampleOffTest, requiredClasses: [ 'dev.caskeleton.bootstrap.contract.SampleOffClasspathContractTest' ], description: 'Runs the exact no-skip sample-off classpath qualification.') sampleOffQualification.configure { shouldRunAfter tasks.named('test') systemProperty 'ca.sample.mode', 'off' } tasks.register('functionalTest', Test) { group = 'verification' description = 'Runs isolated Gradle TestKit contracts for repository build behavior, plus the ' + 'feature-techlog-studio-backend Studio contract drift gate.' testClassesDirs = sourceSets.functionalTest.output.classesDirs classpath = sourceSets.functionalTest.runtimeClasspath useJUnitPlatform() failOnNoDiscoveredTests = true shouldRunAfter tasks.named('test') jvmArgs '-Duser.timezone=UTC' // feature-techlog-studio-backend Task 10 — StudioContractDriftTest boots a real (minimal) // Spring Boot context that needs Logback, on the same classpath as gradleTestKit() (whose own // SLF4J provider — org.gradle.internal.logging.slf4j.OutputEventListenerBackedLoggerContext — // wins classpath scanning over the real one). Spring Boot's LogbackLoggingSystem then finds // Logback's jar present but the bound ILoggerFactory is Gradle's fake context, and fails fast // with IllegalStateException before the context even starts. LoggingSystem=none skips Boot's // logging bootstrap entirely — this task doesn't assert on log output, so there is nothing lost. systemProperty 'org.springframework.boot.logging.LoggingSystem', 'none' } def conditionalTransportCompositionQualification = registerStrictQualificationTest( name: 'conditionalTransportCompositionTest', sourceSet: sourceSets.conditionalTransportTest, requiredClasses: [ 'dev.caskeleton.bootstrap.transport.ConditionalTransportCompositionContractTest' ], description: 'Proves the explicit test-only GraphQL/gRPC/WebSocket opt-in classpath.') conditionalTransportCompositionQualification.configure { shouldRunAfter tasks.named('test') } tasks.named('check') { dependsOn tasks.named('functionalTest') } bootJar { mainClass = 'dev.caskeleton.bootstrap.CaSkeletonApplication' } tasks.register('stageDockerJar', Sync) { dependsOn tasks.named('bootJar') from(tasks.named('bootJar').flatMap { it.archiveFile }) into(layout.buildDirectory.dir('docker')) rename { 'application.jar' } } // Run from the repo's src/ root and inject src/.env into the Java process environment. // Boot 4 initializes profiles/logging before spring-dotenv can reliably contribute .env values. bootRun { workingDir = rootProject.projectDir doFirst { File envFile = rootProject.file('.env') if (!envFile.isFile()) { return } envFile.eachLine { raw -> String line = raw.trim() if (line.isEmpty() || line.startsWith('#') || !line.contains('=')) { return } int separator = line.indexOf('=') String key = line.substring(0, separator).trim() String value = line.substring(separator + 1).trim() if (!key.isEmpty() && System.getenv(key) == null && !environment.containsKey(key)) { environment key, value } } } }