translate properly docs/

This commit is contained in:
AlexisLeDain
2026-04-08 21:49:38 +02:00
parent 08eb812da6
commit b54ce43ef3
12 changed files with 1158 additions and 2759 deletions

View File

@@ -1,29 +1,27 @@
---
name: quarkus-patterns
description: Quarkus 3.x LTS architecture patterns with Camel for messaging, RESTful API design, CDI services, data access with Panache, and async processing. Use for Java Quarkus backend work with event-driven architectures.
description: Quarkus 3.x LTSアーキテクチャパターン、Camelメッセージング、RESTful API設計、CDIサービス、Panacheデータアクセス、非同期処理。イベント駆動アーキテクチャを持つJava Quarkusバックエンド作業に使用。
origin: ECC
---
> **Note / 注意**: このファイルはまだ日本語に翻訳されていません。現在は英語の原文です。翻訳PRを歓迎します。
# Quarkus 開発パターン
# Quarkus Development Patterns
Apache Camelを使用したクラウドネイティブなイベント駆動サービスのためのQuarkus 3.xアーキテクチャとAPIパターン。
Quarkus 3.x architecture and API patterns for cloud-native, event-driven services with Apache Camel.
## いつアクティブにするか
## When to Activate
- JAX-RSまたはRESTEasy ReactiveでREST APIを構築する
- リソース → サービス → リポジトリレイヤーを構造化する
- Apache CamelとRabbitMQでイベント駆動パターンを実装する
- Hibernate Panache、キャッシング、またはリアクティブストリームを構成する
- バリデーション、例外マッピング、またはページネーションを追加する
- dev/staging/production環境のプロファイルを設定するYAML構成
- LogContextとLogback/Logstashエンコーダーでカスタムロギング
- CompletableFutureで非同期操作を行う
- 条件付きフロー処理を実装する
- GraalVMネイティブコンパイルで作業する
- Building REST APIs with JAX-RS or RESTEasy Reactive
- Structuring resource → service → repository layers
- Implementing event-driven patterns with Apache Camel and RabbitMQ
- Configuring Hibernate Panache, caching, or reactive streams
- Adding validation, exception mapping, or pagination
- Setting up profiles for dev/staging/production environments (YAML config)
- Custom logging with LogContext and Logback/Logstash encoder
- Working with CompletableFuture for async operations
- Implementing conditional flow processing
- Working with GraalVM native compilation
## Service Layer with Multiple Dependencies (Lombok)
## 複数依存関係を持つサービスレイヤーLombok
```java
@Slf4j
@@ -43,7 +41,7 @@ public class As2ProcessingService {
String structureIdPartner = logContext.get(As2Constants.STRUCTURE_ID);
// Conditional flow logic
// 条件付きフローロジック
boolean isChorusFlow = Boolean.parseBoolean(logContext.get(As2Constants.CHORUS_FLOW));
log.info("Is CHORUS_FLOW message: {}", isChorusFlow);
@@ -62,7 +60,7 @@ public class As2ProcessingService {
log.info("Invoice validation completed. Message is valid");
// CompletableFuture async operation
// CompletableFuture非同期操作
try(InputStream inputStream = Files.newInputStream(filePath)) {
CompletableFuture<StoredDocumentInfo> documentInfoCompletableFuture =
fileStorageService.uploadOriginalFile(inputStream,
@@ -85,7 +83,7 @@ public class As2ProcessingService {
documentInfo, originalFileName, structureIdPartner,
flowProfile, invoiceValidationResult.getDocumentHash());
// Async Camel publishing
// 非同期Camelパブリッシング
businessRulesPublisher.publishAsync(payload);
this.eventService.createSuccessEvent(payload, "BUSINESS_RULES_MESSAGE_SENT");
}
@@ -94,16 +92,16 @@ public class As2ProcessingService {
}
```
**Key Patterns:**
- `@RequiredArgsConstructor` for constructor injection via Lombok
- `@Slf4j` for Logback logging
- Scoped LogContext with try-with-resources
- Conditional flow logic based on runtime parameters
- CompletableFuture with `.join()` for async operations
- Event tracking for success/error scenarios
- Async Camel message publishing
**主要パターン:**
- Lombokによるコンストラクタインジェクション用の`@RequiredArgsConstructor`
- Logbackロギング用の`@Slf4j`
- try-with-resourcesによるスコープ付きLogContext
- ランタイムパラメータに基づく条件付きフローロジック
- 非同期操作用の`.join()`付きCompletableFuture
- 成功/エラーシナリオのイベントトラッキング
- 非同期Camelメッセージパブリッシング
## Custom Logging Context Pattern (Logback)
## カスタムロギングコンテキストパターン(Logback
```java
@ApplicationScoped
@@ -112,14 +110,14 @@ public class ProcessingService {
public void processDocument(Document doc) {
LogContext logContext = CustomLog.getCurrentContext();
try (SafeAutoCloseable ignored = CustomLog.startScope(logContext)) {
// Add context to all log statements
// すべてのログステートメントにコンテキストを追加
logContext.put("documentId", doc.getId().toString());
logContext.put("documentType", doc.getType());
logContext.put("userId", SecurityContext.getUserId());
log.info("Starting document processing");
// All logs within this scope inherit the context
// このスコープ内のすべてのログはコンテキストを継承
processInternal(doc);
log.info("Document processing completed");
@@ -131,7 +129,7 @@ public class ProcessingService {
}
```
**Logback Configuration (logback.xml):**
**Logback構成(logback.xml:**
```xml
<configuration>
@@ -149,7 +147,7 @@ public class ProcessingService {
</configuration>
```
## Event Service Pattern
## イベントサービスパターン
```java
@ApplicationScoped
@@ -181,13 +179,13 @@ public class EventService {
}
private String serializePayload(Object payload) {
// JSON serialization
// JSONシリアライゼーション
return objectMapper.writeValueAsString(payload);
}
}
```
## Camel Message Publishing (RabbitMQ)
## Camelメッセージパブリッシング(RabbitMQ
```java
@ApplicationScoped
@@ -215,7 +213,7 @@ public class BusinessRulesPublisher {
}
```
**Camel Route Configuration:**
**Camelルート構成:**
```java
@ApplicationScoped
@@ -242,7 +240,7 @@ public class BusinessRulesRoute extends RouteBuilder {
}
```
## Camel Direct Routes (In-Memory)
## Camel Directルート(インメモリ)
```java
@ApplicationScoped
@@ -250,13 +248,13 @@ public class DocumentProcessingRoute extends RouteBuilder {
@Override
public void configure() {
// Error handling
// エラーハンドリング
onException(ValidationException.class)
.handled(true)
.to("direct:validation-error-handler")
.log("Validation error: ${exception.message}");
// Main processing route
// メイン処理ルート
from("direct:process-document")
.routeId("document-processing")
.log("Processing document: ${header.documentId}")
@@ -278,7 +276,7 @@ public class DocumentProcessingRoute extends RouteBuilder {
}
```
## Camel File Processing
## Camelファイル処理
```java
@ApplicationScoped
@@ -308,7 +306,7 @@ public class FileMonitoringRoute extends RouteBuilder {
}
```
## Camel Bean Invocation
## Camel Bean呼び出し
```java
@ApplicationScoped
@@ -328,7 +326,7 @@ public class InvoiceRoute extends RouteBuilder {
}
```
## REST API Structure
## REST API構造
```java
@Path("/api/documents")
@@ -367,7 +365,7 @@ public class DocumentResource {
}
```
## Repository Pattern (Panache Repository)
## リポジトリパターン(Panache Repository
```java
@ApplicationScoped
@@ -389,7 +387,7 @@ public class DocumentRepository implements PanacheRepository<Document> {
}
```
## Service Layer with Transactions
## トランザクション付きサービスレイヤー
```java
@ApplicationScoped
@@ -425,7 +423,7 @@ public class DocumentService {
}
```
## DTOs and Validation
## DTOとバリデーション
```java
public record CreateDocumentRequest(
@@ -442,7 +440,7 @@ public record DocumentResponse(Long id, String referenceNumber, DocumentStatus s
}
```
## Exception Mapping
## 例外マッピング
```java
@Provider
@@ -473,7 +471,7 @@ public class GenericExceptionMapper implements ExceptionMapper<Exception> {
}
```
## CompletableFuture Async Operations
## CompletableFuture非同期操作
```java
@ApplicationScoped
@@ -512,7 +510,7 @@ public class FileStorageService {
}
```
## Caching
## キャッシング
```java
@ApplicationScoped
@@ -533,7 +531,7 @@ public class DocumentCacheService {
}
```
## Configuration as YAML
## YAML構成
```yaml
# application.yml
@@ -580,7 +578,7 @@ public class DocumentCacheService {
username: ${RABBITMQ_USER}
password: ${RABBITMQ_PASSWORD}
# Camel configuration
# Camel構成
camel:
rabbitmq:
queue:
@@ -588,7 +586,7 @@ camel:
invoice-processing: invoice-processing-queue
```
## Health Checks
## ヘルスチェック
```java
@Readiness
@@ -626,7 +624,7 @@ public class CamelHealthCheck implements HealthCheck {
}
```
## Dependencies (Maven)
## 依存関係(Maven
```xml
<properties>
@@ -657,7 +655,7 @@ public class CamelHealthCheck implements HealthCheck {
</dependencyManagement>
<dependencies>
<!-- Quarkus Core -->
<!-- Quarkusコア -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
@@ -667,7 +665,7 @@ public class CamelHealthCheck implements HealthCheck {
<artifactId>quarkus-config-yaml</artifactId>
</dependency>
<!-- Camel Extensions -->
<!-- Camelエクステンション -->
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-spring-rabbitmq</artifactId>
@@ -689,7 +687,7 @@ public class CamelHealthCheck implements HealthCheck {
<scope>provided</scope>
</dependency>
<!-- Logging -->
<!-- ロギング -->
<dependency>
<groupId>io.quarkiverse.logging.logback</groupId>
<artifactId>quarkus-logging-logback</artifactId>
@@ -701,56 +699,56 @@ public class CamelHealthCheck implements HealthCheck {
</dependencies>
```
## Best Practices
## ベストプラクティス
### Architecture
- Use `@RequiredArgsConstructor` with Lombok for constructor injection
- Keep service layer thin; delegate complex logic to specialized classes
- Use Camel routes for message routing and integration patterns
- Prefer Panache Repository pattern for data access
### アーキテクチャ
- コンストラクタインジェクション用にLombokの`@RequiredArgsConstructor`を使用
- サービスレイヤーは薄く保ち、複雑なロジックは専門クラスに委譲
- メッセージルーティングと統合パターンにCamelルートを使用
- データアクセスにはPanache Repositoryパターンを優先
### Event-Driven
- Always track operations with EventService (success/error events)
- Use Camel `direct:` endpoints for in-memory routing
- Use `spring-rabbitmq` component for RabbitMQ integration
- Implement async publishing with `ProducerTemplate.asyncSendBody()`
### イベント駆動
- 常にEventServiceで操作をトラッキング成功/エラーイベント)
- インメモリルーティングにCamel`direct:`エンドポイントを使用
- RabbitMQ統合に`spring-rabbitmq`コンポーネントを使用
- `ProducerTemplate.asyncSendBody()`で非同期パブリッシングを実装
### Logging
- Use Logback with Logstash encoder for structured logging
- Propagate LogContext through service calls with `SafeAutoCloseable`
- Add contextual information to LogContext for request tracing
- Use `@Slf4j` instead of manual logger instantiation
### ロギング
- 構造化ロギング用にLogstashエンコーダー付きLogbackを使用
- `SafeAutoCloseable`でサービスコール間でLogContextを伝播
- リクエストトレーシング用にLogContextにコンテキスト情報を追加
- 手動ロガーインスタンス化の代わりに`@Slf4j`を使用
### Async Operations
- Use CompletableFuture for non-blocking I/O operations
- Call `.join()` when you need to wait for completion
- Handle exceptions from CompletableFuture properly
- Pass LogContext to async operations for tracing
### 非同期操作
- ンブロッキングI/O操作にCompletableFutureを使用
- 完了を待つ必要がある場合は`.join()`を呼び出す
- CompletableFutureからの例外を適切にハンドリング
- トレーシング用に非同期操作にLogContextを渡す
### Configuration
- Use YAML configuration (`quarkus-config-yaml`)
- Profile-aware configuration for dev/test/prod environments
- Externalize sensitive configuration to environment variables
- Use `@ConfigProperty` for type-safe config injection
### 構成
- YAML構成を使用`quarkus-config-yaml`
- dev/test/prod環境のプロファイル対応構成
- 機密構成を環境変数に外部化
- 型安全な構成インジェクション用に`@ConfigProperty`を使用
### Validation
- Validate at resource layer with `@Valid`
- Use Bean Validation annotations on DTOs
- Map exceptions to proper HTTP responses with `@Provider`
### バリデーション
- リソースレイヤーで`@Valid`によるバリデーション
- DTOにBean Validationアノテーションを使用
- `@Provider`で例外を適切なHTTPレスポンスにマッピング
### Transactions
- Use `@Transactional` on service methods that modify data
- Keep transactions short and focused
- Avoid calling async operations within transactions
### トランザクション
- データを変更するサービスメソッドに`@Transactional`を使用
- トランザクションは短く焦点を絞る
- トランザクション内で非同期操作を呼び出さない
### Testing
- Use `camel-quarkus-junit5` for route testing
- Use AssertJ for assertions
- Mock all external dependencies
- Test conditional flow logic thoroughly
### テスト
- ルートテストに`camel-quarkus-junit5`を使用
- アサーションにAssertJを使用
- すべての外部依存関係をモック
- 条件付きフローロジックを徹底的にテスト
### Quarkus-Specific
- Stay on latest LTS version (3.x)
- Use Quarkus dev mode for hot reload
- Add health checks for production readiness
- Test native compilation compatibility periodically
### Quarkus固有
- 最新のLTSバージョン3.xを維持
- ホットリロード用にQuarkus devモードを使用
- 本番準備のためにヘルスチェックを追加
- ネイティブコンパイル互換性を定期的にテスト

View File

@@ -1,32 +1,29 @@
---
name: quarkus-security
description: Quarkus Security best practices for authentication, authorization, JWT/OIDC, RBAC, input validation, CSRF, secrets management, and dependency security.
description: Quarkusセキュリティのベストプラクティス:認証、認可、JWT/OIDCRBAC、入力バリデーション、CSRF、シークレット管理、依存関係セキュリティ。
origin: ECC
---
> **Note / 注意**: このファイルはまだ日本語に翻訳されていません。現在は英語の原文です。翻訳PRを歓迎します。
# Quarkus セキュリティレビュー
# Quarkus Security Review
認証、認可、入力バリデーションによるQuarkusアプリケーションのセキュリティベストプラクティス。
Best practices for securing Quarkus applications with authentication, authorization, and input validation.
## いつアクティブにするか
## When to Activate
- 認証の追加JWT、OIDC、Basic Auth
- @RolesAllowedまたはSecurityIdentityによる認可の実装
- ユーザー入力のバリデーションBean Validation、カスタムバリデータ
- CORSまたはセキュリティヘッダーの構成
- シークレット管理Vault、環境変数、構成ソース
- レート制限またはブルートフォース保護の追加
- 依存関係のCVEスキャン
- MicroProfile JWTまたはSmallRye JWTの使用
- Adding authentication (JWT, OIDC, Basic Auth)
- Implementing authorization with @RolesAllowed or SecurityIdentity
- Validating user input (Bean Validation, custom validators)
- Configuring CORS or security headers
- Managing secrets (Vault, environment variables, config sources)
- Adding rate limiting or brute-force protection
- Scanning dependencies for CVEs
- Working with MicroProfile JWT or SmallRye JWT
## 認証
## Authentication
### JWT Authentication
### JWT認証
```java
// Resource protected with JWT
@Path("/api/protected")
@Authenticated
public class ProtectedResource {
@@ -50,7 +47,7 @@ public class ProtectedResource {
}
```
Configuration (application.properties):
構成(application.properties:
```properties
mp.jwt.verify.publickey.location=publicKey.pem
mp.jwt.verify.issuer=https://auth.example.com
@@ -61,7 +58,7 @@ quarkus.oidc.client-id=backend-service
quarkus.oidc.credentials.secret=${OIDC_SECRET}
```
### Custom Authentication Filter
### カスタム認証フィルター
```java
@Provider
@@ -77,7 +74,6 @@ public class CustomAuthFilter implements ContainerRequestFilter {
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
// Validate token and set SecurityIdentity
if (!validateToken(token)) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
@@ -85,15 +81,15 @@ public class CustomAuthFilter implements ContainerRequestFilter {
}
private boolean validateToken(String token) {
// Token validation logic
// トークンバリデーションロジック
return true;
}
}
```
## Authorization
## 認可
### Role-Based Access Control
### ロールベースアクセス制御
```java
@Path("/api/admin")
@@ -125,7 +121,7 @@ public class UserResource {
@Path("/{id}")
@RolesAllowed("USER")
public Response getUser(@PathParam("id") Long id) {
// Check ownership
// オーナーシップチェック
if (!securityIdentity.hasRole("ADMIN") &&
!isOwner(id, securityIdentity.getPrincipal().getName())) {
return Response.status(Response.Status.FORBIDDEN).build();
@@ -139,7 +135,7 @@ public class UserResource {
}
```
### Programmatic Security
### プログラマティックセキュリティ
```java
@ApplicationScoped
@@ -163,18 +159,18 @@ public class SecurityService {
}
```
## Input Validation
## 入力バリデーション
### Bean Validation
```java
// BAD: No validation
// BAD: バリデーションなし
@POST
public Response createUser(UserDto dto) {
return Response.ok(userService.create(dto)).build();
}
// GOOD: Validated DTO
// GOOD: バリデーション付きDTO
public record CreateUserDto(
@NotBlank @Size(max = 100) String name,
@NotBlank @Email String email,
@@ -190,7 +186,7 @@ public Response createUser(@Valid CreateUserDto dto) {
}
```
### Custom Validators
### カスタムバリデータ
```java
@Target({ElementType.FIELD, ElementType.PARAMETER})
@@ -209,36 +205,30 @@ public class UsernameValidator implements ConstraintValidator<ValidUsername, Str
return value.matches("^[a-zA-Z0-9_-]{3,20}$");
}
}
// Usage
public record CreateUserDto(
@ValidUsername String username,
@NotBlank @Email String email
) {}
```
## SQL Injection Prevention
## SQLインジェクション防止
### Panache Active Record (Safe by Default)
### Panache Active Record(デフォルトで安全)
```java
// GOOD: Parameterized queries with Panache
// GOOD: Panacheによるパラメータ化クエリ
List<User> users = User.list("email = ?1 and active = ?2", email, true);
Optional<User> user = User.find("username", username).firstResultOptional();
// GOOD: Named parameters
// GOOD: 名前付きパラメータ
List<User> users = User.list("email = :email and age > :minAge",
Parameters.with("email", email).and("minAge", 18));
```
### Native Queries (Use Parameters)
### ネイティブクエリ(パラメータを使用)
```java
// BAD: String concatenation
// BAD: 文字列連結
@Query(value = "SELECT * FROM users WHERE name = '" + name + "'", nativeQuery = true)
// GOOD: Parameterized native query
// GOOD: パラメータ化ネイティブクエリ
@Entity
public class User extends PanacheEntity {
public static List<User> findByEmailNative(String email) {
@@ -250,7 +240,7 @@ public class User extends PanacheEntity {
}
```
## Password Hashing
## パスワードハッシュ
```java
@ApplicationScoped
@@ -264,33 +254,9 @@ public class PasswordService {
return BcryptUtil.matches(plainPassword, hashedPassword);
}
}
// In service
@ApplicationScoped
public class UserService {
@Inject
PasswordService passwordService;
@Transactional
public User register(CreateUserDto dto) {
String hashedPassword = passwordService.hash(dto.password());
User user = new User();
user.email = dto.email();
user.password = hashedPassword;
user.persist();
return user;
}
public boolean authenticate(String email, String password) {
return User.find("email", email)
.firstResultOptional()
.map(u -> passwordService.verify(password, u.password))
.orElse(false);
}
}
```
## CORS Configuration
## CORS構成
```properties
# application.properties
@@ -303,37 +269,22 @@ quarkus.http.cors.access-control-max-age=24H
quarkus.http.cors.access-control-allow-credentials=true
```
## Secrets Management
## シークレット管理
```properties
# application.properties - NO SECRETS HERE
# application.properties — ここにシークレットを置かない
# Use environment variables
# 環境変数を使用
quarkus.datasource.username=${DB_USER}
quarkus.datasource.password=${DB_PASSWORD}
quarkus.oidc.credentials.secret=${OIDC_CLIENT_SECRET}
# Or use Vault
# またはVaultを使用
quarkus.vault.url=https://vault.example.com
quarkus.vault.authentication.kubernetes.role=my-role
```
### HashiCorp Vault Integration
```java
@ApplicationScoped
public class SecretService {
@ConfigProperty(name = "api-key")
String apiKey; // Fetched from Vault
public String getSecret(String key) {
return ConfigProvider.getConfig().getValue(key, String.class);
}
}
```
## Rate Limiting
## レート制限
```java
@ApplicationScoped
@@ -344,7 +295,7 @@ public class RateLimitFilter implements ContainerRequestFilter {
public void filter(ContainerRequestContext requestContext) {
String clientId = getClientIdentifier(requestContext);
RateLimiter limiter = limiters.computeIfAbsent(clientId,
k -> RateLimiter.create(100.0)); // 100 requests per second
k -> RateLimiter.create(100.0)); // 1秒あたり100リクエスト
if (!limiter.tryAcquire()) {
requestContext.abortWith(
@@ -356,13 +307,13 @@ public class RateLimitFilter implements ContainerRequestFilter {
}
private String getClientIdentifier(ContainerRequestContext ctx) {
// Use IP, API key, or user ID
// IP、APIキー、またはユーザーIDを使用
return ctx.getHeaderString("X-Forwarded-For");
}
}
```
## Security Headers
## セキュリティヘッダー
```java
@Provider
@@ -372,10 +323,10 @@ public class SecurityHeadersFilter implements ContainerResponseFilter {
public void filter(ContainerRequestContext request, ContainerResponseContext response) {
MultivaluedMap<String, Object> headers = response.getHeaders();
// Prevent clickjacking
// クリックジャッキング防止
headers.putSingle("X-Frame-Options", "DENY");
// XSS protection
// XSS保護
headers.putSingle("X-Content-Type-Options", "nosniff");
headers.putSingle("X-XSS-Protection", "1; mode=block");
@@ -389,7 +340,7 @@ public class SecurityHeadersFilter implements ContainerResponseFilter {
}
```
## Audit Logging
## 監査ロギング
```java
@ApplicationScoped
@@ -408,23 +359,9 @@ public class AuditService {
user, action, resource, Instant.now());
}
}
// Usage in resource
@Path("/api/sensitive")
public class SensitiveResource {
@Inject
AuditService auditService;
@GET
@RolesAllowed("ADMIN")
public Response getData() {
auditService.logAccess("sensitive-data", "READ");
return Response.ok(data).build();
}
}
```
## Dependency Security Scanning
## 依存関係セキュリティスキャン
```bash
# Maven
@@ -433,23 +370,23 @@ mvn org.owasp:dependency-check-maven:check
# Gradle
./gradlew dependencyCheckAnalyze
# Check Quarkus extensions
# Quarkusエクステンション確認
quarkus extension list --installable
```
## Best Practices
## ベストプラクティス
- Always use HTTPS in production
- Enable JWT or OIDC for stateless authentication
- Use `@RolesAllowed` for declarative authorization
- Validate all input with Bean Validation
- Hash passwords with BCrypt (never plaintext)
- Store secrets in Vault or environment variables
- Use parameterized queries to prevent SQL injection
- Add security headers to all responses
- Implement rate limiting for public endpoints
- Audit sensitive operations
- Keep dependencies updated and scan for CVEs
- Use SecurityIdentity for programmatic checks
- Set appropriate CORS policies
- Test authentication and authorization paths
- 本番環境では常にHTTPSを使用
- ステートレス認証にJWTまたはOIDCを有効化
- 宣言的認可に`@RolesAllowed`を使用
- Bean Validationですべての入力をバリデーション
- BCryptでパスワードをハッシュ平文禁止
- シークレットはVaultまたは環境変数に保存
- SQLインジェクション防止にパラメータ化クエリを使用
- すべてのレスポンスにセキュリティヘッダーを追加
- パブリックエンドポイントにレート制限を実装
- 機密操作を監査
- 依存関係を最新に保ちCVEをスキャン
- プログラマティックチェックにSecurityIdentityを使用
- 適切なCORSポリシーを設定
- 認証・認可パスをテスト

View File

@@ -1,36 +1,34 @@
---
name: quarkus-tdd
description: Test-driven development for Quarkus 3.x LTS using JUnit 5, Mockito, REST Assured, Camel testing, and JaCoCo. Use when adding features, fixing bugs, or refactoring event-driven services.
description: Quarkus 3.x LTS向けテスト駆動開発。JUnit 5MockitoREST AssuredCamelテスト、JaCoCoを使用。機能追加、バグ修正、イベント駆動サービスのリファクタリングに使用。
origin: ECC
---
> **Note / 注意**: このファイルはまだ日本語に翻訳されていません。現在は英語の原文です。翻訳PRを歓迎します。
# Quarkus TDDワークフロー
# Quarkus TDD Workflow
80%以上のカバレッジユニット統合を目指すQuarkus 3.xサービスのTDDガイダンス。Apache Camelによるイベント駆動アーキテクチャに最適化。
TDD guidance for Quarkus 3.x services with 80%+ coverage (unit + integration). Optimized for event-driven architectures with Apache Camel.
## いつ使用するか
## When to Use
- 新機能またはRESTエンドポイント
- バグ修正またはリファクタリング
- データアクセスロジック、セキュリティルール、またはリアクティブストリームの追加
- Apache Camelルートとイベントハンドラーのテスト
- RabbitMQによるイベント駆動サービスのテスト
- 条件付きフローロジックのテスト
- CompletableFuture非同期操作のバリデーション
- LogContext伝播のテスト
- New features or REST endpoints
- Bug fixes or refactors
- Adding data access logic, security rules, or reactive streams
- Testing Apache Camel routes and event handlers
- Testing event-driven services with RabbitMQ
- Testing conditional flow logic
- Validating CompletableFuture async operations
- Testing LogContext propagation
## ワークフロー
## Workflow
1. まずテストを書く(失敗するはず)
2. テストを通過する最小限のコードを実装
3. テストがグリーンの状態でリファクタリング
4. JaCoCoでカバレッジを強制80%以上が目標)
1. Write tests first (they should fail)
2. Implement minimal code to pass
3. Refactor with tests green
4. Enforce coverage with JaCoCo (80%+ target)
## @Nestedによるユニットテスト構成
## Unit Tests with @Nested Organization
Follow this structured approach for comprehensive, readable tests:
包括的で読みやすいテストのための構造化アプローチ:
```java
@ExtendWith(MockitoExtension.class)
@@ -62,7 +60,7 @@ class As2ProcessingServiceTest {
@BeforeEach
void setUp() {
// ARRANGE - Common test data
// ARRANGE - 共通テストデータ
testFilePath = Path.of("/tmp/test-invoice.xml");
testLogContext = new LogContext();
@@ -119,48 +117,9 @@ class As2ProcessingServiceTest {
verify(eventService).createSuccessEvent(any(StoredDocumentInfo.class),
eq("PERSISTENCE_BLOB_EVENT_TYPE"));
verify(eventService).createSuccessEvent(any(BusinessRulesPayload.class),
eq("BUSINESS_RULES_MESSAGE_SENT"));
verify(businessRulesPublisher).publishAsync(any(BusinessRulesPayload.class));
}
@Test
@DisplayName("Should bypass schematron validation for CHORUS_FLOW")
void givenChorusFlow_whenProcessFile_thenSchematronBypassed() throws Exception {
// ARRANGE
testLogContext.put(As2Constants.CHORUS_FLOW, "true");
CustomLog.setCurrentContext(testLogContext);
when(invoiceFlowValidator.validateFlowWithConfig(
eq(testFilePath),
eq(ValidationFlowConfig.xsdOnly()),
eq(EInvoiceSyntaxFormat.UBL),
any(LogContext.class)))
.thenReturn(validationResult);
when(fileStorageService.uploadOriginalFile(any(), anyLong(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(documentInfo));
when(documentJobService.createDocumentAndJobEntities(any(), any(), any(),
eq(FlowProfile.EXTENDED_CTC_FR), any()))
.thenReturn(new BusinessRulesPayload());
// ACT
assertDoesNotThrow(() -> as2ProcessingService.processFile(testFilePath));
// ASSERT
verify(invoiceFlowValidator).validateFlowWithConfig(
eq(testFilePath),
eq(ValidationFlowConfig.xsdOnly()),
eq(EInvoiceSyntaxFormat.UBL),
any(LogContext.class));
verify(documentJobService).createDocumentAndJobEntities(
any(), any(), any(),
eq(FlowProfile.EXTENDED_CTC_FR),
any());
}
@Test
@DisplayName("Should create error event when file upload fails")
void givenUploadFailure_whenProcessFile_thenErrorEventCreated() throws Exception {
@@ -174,7 +133,7 @@ class As2ProcessingServiceTest {
when(invoiceFlowValidator.computeFlowProfile(any(), any()))
.thenReturn(FlowProfile.BASIC);
documentInfo.setPath(""); // Blank path triggers error
documentInfo.setPath(""); // 空パスでエラーをトリガー
when(fileStorageService.uploadOriginalFile(any(), anyLong(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(documentInfo));
@@ -187,71 +146,26 @@ class As2ProcessingServiceTest {
assertThat(exception.getMessage())
.contains("File path is empty after upload");
verify(eventService).createErrorEvent(
eq(documentInfo),
eq("FILE_UPLOAD_FAILED"),
contains("File path is empty"));
verify(businessRulesPublisher, never()).publishAsync(any());
}
@Test
@DisplayName("Should handle CompletableFuture.join() failure")
void givenAsyncUploadFailure_whenProcessFile_thenExceptionThrown() throws Exception {
// ARRANGE
testLogContext.put(As2Constants.CHORUS_FLOW, "false");
CustomLog.setCurrentContext(testLogContext);
when(invoiceFlowValidator.validateFlowWithConfig(any(), any(), any(), any()))
.thenReturn(validationResult);
when(invoiceFlowValidator.computeFlowProfile(any(), any()))
.thenReturn(FlowProfile.BASIC);
CompletableFuture<StoredDocumentInfo> failedFuture =
CompletableFuture.failedFuture(new StorageException("S3 connection failed"));
when(fileStorageService.uploadOriginalFile(any(), anyLong(), any(), any()))
.thenReturn(failedFuture);
// ACT & ASSERT
assertThrows(
CompletionException.class,
() -> as2ProcessingService.processFile(testFilePath)
);
}
@Test
@DisplayName("Should throw exception when file path is null")
void givenNullFilePath_whenProcessFile_thenThrowsException() {
// ARRANGE
Path nullPath = null;
// ACT & ASSERT
NullPointerException exception = assertThrows(
NullPointerException.class,
() -> as2ProcessingService.processFile(nullPath)
);
verify(invoiceFlowValidator, never()).validateFlowWithConfig(any(), any(), any(), any());
}
}
}
```
### Key Testing Patterns
### 主要テストパターン
1. **@Nested Classes**: Group tests by method being tested
2. **@DisplayName**: Provide readable test descriptions for test reports
3. **Naming Convention**: `givenX_whenY_thenZ` for clarity
4. **AAA Pattern**: Explicit `// ARRANGE`, `// ACT`, `// ASSERT` comments
5. **@BeforeEach**: Setup common test data to reduce duplication
6. **assertDoesNotThrow**: Test success scenarios without catching exceptions
7. **assertThrows**: Test exception scenarios with message validation using AssertJ
8. **Comprehensive Coverage**: Test happy paths, null inputs, edge cases, exceptions
9. **Verify Interactions**: Use Mockito `verify()` to ensure methods are called correctly
10. **Never Verify**: Use `never()` to ensure methods are NOT called in error scenarios
1. **@Nestedクラス**: テスト対象メソッドごとにテストをグループ化
2. **@DisplayName**: テストレポート用の読みやすい説明
3. **命名規則**: 明確さのために`givenX_whenY_thenZ`
4. **AAAパターン**: 明示的な`// ARRANGE``// ACT``// ASSERT`コメント
5. **@BeforeEach**: 重複削減のための共通テストデータセットアップ
6. **assertDoesNotThrow**: 例外をキャッチせずに成功シナリオをテスト
7. **assertThrows**: メッセージバリデーション付きの例外シナリオテスト
8. **包括的カバレッジ**: ハッピーパス、null入力、エッジケース、例外をテスト
9. **インタラクション検証**: Mockito`verify()`でメソッドが正しく呼ばれることを確認
10. **Never検証**: エラーシナリオでメソッドが呼ばれないことを`never()`で確認
## Testing Camel Routes
## Camelルートのテスト
```java
@QuarkusTest
@@ -267,338 +181,30 @@ class BusinessRulesRouteTest {
@InjectMock
EventService eventService;
private BusinessRulesPayload testPayload;
@BeforeEach
void setUp() {
// ARRANGE - Test data
testPayload = new BusinessRulesPayload();
testPayload.setDocumentId(1L);
testPayload.setFlowProfile(FlowProfile.BASIC);
}
@Nested
@DisplayName("Tests for business-rules-publisher route")
class BusinessRulesPublisher {
@Test
@DisplayName("Should successfully publish message to RabbitMQ")
void givenValidPayload_whenPublish_thenMessageSentToQueue() throws Exception {
// ARRANGE
MockEndpoint mockRabbitMQ = camelContext.getEndpoint("mock:rabbitmq", MockEndpoint.class);
mockRabbitMQ.expectedMessageCount(1);
mockRabbitMQ.expectedBodiesReceived(testPayload);
// Replace real endpoint with mock for testing
camelContext.getRouteController().stopRoute("business-rules-publisher");
AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {
advice.replaceFromWith("direct:business-rules-publisher");
advice.weaveByToString(".*spring-rabbitmq.*").replace().to("mock:rabbitmq");
});
camelContext.getRouteController().startRoute("business-rules-publisher");
// ACT
producerTemplate.sendBody("direct:business-rules-publisher", testPayload);
// ASSERT
mockRabbitMQ.assertIsSatisfied(5000);
assertThat(mockRabbitMQ.getExchanges()).hasSize(1);
assertThat(mockRabbitMQ.getExchanges().get(0).getIn().getBody(BusinessRulesPayload.class))
.isEqualTo(testPayload);
}
@Test
@DisplayName("Should handle marshalling to JSON")
void givenPayload_whenPublish_thenMarshalledToJson() throws Exception {
// ARRANGE
MockEndpoint mockMarshal = new MockEndpoint("mock:marshal");
camelContext.addEndpoint("mock:marshal", mockMarshal);
mockMarshal.expectedMessageCount(1);
camelContext.getRouteController().stopRoute("business-rules-publisher");
AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {
advice.weaveAddLast().to("mock:marshal");
});
camelContext.getRouteController().startRoute("business-rules-publisher");
// ACT
producerTemplate.sendBody("direct:business-rules-publisher", testPayload);
// ASSERT
mockMarshal.assertIsSatisfied(5000);
String body = mockMarshal.getExchanges().get(0).getIn().getBody(String.class);
assertThat(body).contains("\"documentId\":1");
assertThat(body).contains("\"flowProfile\":\"BASIC\"");
}
}
@Nested
@DisplayName("Tests for document-processing route")
class DocumentProcessing {
@Test
@DisplayName("Should route invoice to correct processor")
void givenInvoiceType_whenProcess_thenRoutesToInvoiceProcessor() throws Exception {
// ARRANGE
MockEndpoint mockInvoice = camelContext.getEndpoint("mock:invoice", MockEndpoint.class);
mockInvoice.expectedMessageCount(1);
camelContext.getRouteController().stopRoute("document-processing");
AdviceWith.adviceWith(camelContext, "document-processing", advice -> {
advice.weaveByToString(".*direct:process-invoice.*").replace().to("mock:invoice");
});
camelContext.getRouteController().startRoute("document-processing");
// ACT
producerTemplate.sendBodyAndHeader("direct:process-document",
testPayload, "documentType", "INVOICE");
// ASSERT
mockInvoice.assertIsSatisfied(5000);
}
@Test
@DisplayName("Should handle validation errors gracefully")
void givenValidationError_whenProcess_thenRoutesToErrorHandler() throws Exception {
// ARRANGE
MockEndpoint mockError = camelContext.getEndpoint("mock:error", MockEndpoint.class);
mockError.expectedMessageCount(1);
camelContext.getRouteController().stopRoute("document-processing");
AdviceWith.adviceWith(camelContext, "document-processing", advice -> {
advice.weaveByToString(".*direct:validation-error-handler.*")
.replace().to("mock:error");
});
camelContext.getRouteController().startRoute("document-processing");
// Mock validator to throw exception
when(eventService.validate(any())).thenThrow(new ValidationException("Invalid document"));
// ACT
producerTemplate.sendBody("direct:process-document", testPayload);
// ASSERT
mockError.assertIsSatisfied(5000);
Exception exception = mockError.getExchanges().get(0).getException();
assertThat(exception).isInstanceOf(ValidationException.class);
assertThat(exception.getMessage()).contains("Invalid document");
}
}
}
```
## Testing Event Services
```java
@ExtendWith(MockitoExtension.class)
@DisplayName("EventService Unit Tests")
class EventServiceTest {
@Mock
private EventRepository eventRepository;
@Mock
private ObjectMapper objectMapper;
@InjectMocks
private EventService eventService;
private BusinessRulesPayload testPayload;
@BeforeEach
void setUp() {
@Test
@DisplayName("Should successfully publish message to RabbitMQ")
void givenValidPayload_whenPublish_thenMessageSentToQueue() throws Exception {
// ARRANGE
testPayload = new BusinessRulesPayload();
testPayload.setDocumentId(1L);
}
@Nested
@DisplayName("Tests for createSuccessEvent")
class CreateSuccessEvent {
MockEndpoint mockRabbitMQ = camelContext.getEndpoint("mock:rabbitmq", MockEndpoint.class);
mockRabbitMQ.expectedMessageCount(1);
@Test
@DisplayName("Should create success event with correct attributes")
void givenValidPayload_whenCreateSuccessEvent_thenEventPersisted() throws Exception {
// ARRANGE
when(objectMapper.writeValueAsString(testPayload)).thenReturn("{\"documentId\":1}");
// ACT
assertDoesNotThrow(() ->
eventService.createSuccessEvent(testPayload, "DOCUMENT_PROCESSED"));
// ASSERT
verify(eventRepository).persist(argThat(event ->
event.getType().equals("DOCUMENT_PROCESSED") &&
event.getStatus() == EventStatus.SUCCESS &&
event.getPayload().equals("{\"documentId\":1}") &&
event.getTimestamp() != null
));
}
@Test
@DisplayName("Should throw exception when payload is null")
void givenNullPayload_whenCreateSuccessEvent_thenThrowsException() {
// ARRANGE
Object nullPayload = null;
// ACT & ASSERT
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> eventService.createSuccessEvent(nullPayload, "EVENT_TYPE")
);
assertThat(exception.getMessage()).isEqualTo("Payload cannot be null");
verify(eventRepository, never()).persist(any());
}
}
@Nested
@DisplayName("Tests for createErrorEvent")
class CreateErrorEvent {
camelContext.getRouteController().stopRoute("business-rules-publisher");
AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {
advice.replaceFromWith("direct:business-rules-publisher");
advice.weaveByToString(".*spring-rabbitmq.*").replace().to("mock:rabbitmq");
});
camelContext.getRouteController().startRoute("business-rules-publisher");
@Test
@DisplayName("Should create error event with error message")
void givenError_whenCreateErrorEvent_thenEventPersistedWithMessage() throws Exception {
// ARRANGE
String errorMessage = "Processing failed";
when(objectMapper.writeValueAsString(testPayload)).thenReturn("{\"documentId\":1}");
// ACT
assertDoesNotThrow(() ->
eventService.createErrorEvent(testPayload, "PROCESSING_ERROR", errorMessage));
// ASSERT
verify(eventRepository).persist(argThat(event ->
event.getType().equals("PROCESSING_ERROR") &&
event.getStatus() == EventStatus.ERROR &&
event.getErrorMessage().equals(errorMessage) &&
event.getPayload().equals("{\"documentId\":1}")
));
}
@ParameterizedTest
@DisplayName("Should reject invalid error messages")
@ValueSource(strings = {"", " "})
void givenBlankErrorMessage_whenCreateErrorEvent_thenThrowsException(String blankMessage) {
// ACT & ASSERT
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> eventService.createErrorEvent(testPayload, "ERROR", blankMessage)
);
assertThat(exception.getMessage()).contains("Error message cannot be blank");
}
// ACT
producerTemplate.sendBody("direct:business-rules-publisher", testPayload);
// ASSERT
mockRabbitMQ.assertIsSatisfied(5000);
}
}
```
## Testing CompletableFuture
```java
@ExtendWith(MockitoExtension.class)
@DisplayName("FileStorageService Unit Tests")
class FileStorageServiceTest {
@Mock
private S3Client s3Client;
@Mock
private ExecutorService executorService;
@InjectMocks
private FileStorageService fileStorageService;
private InputStream testInputStream;
private LogContext testLogContext;
@BeforeEach
void setUp() {
// ARRANGE
testInputStream = new ByteArrayInputStream("test content".getBytes());
testLogContext = new LogContext();
testLogContext.put("traceId", "trace-123");
}
@Nested
@DisplayName("Tests for uploadOriginalFile")
class UploadOriginalFile {
@Test
@DisplayName("Should successfully upload file and return document info")
void givenValidFile_whenUpload_thenReturnsDocumentInfo() throws Exception {
// ARRANGE
when(executorService.submit(any(Callable.class))).thenAnswer(invocation -> {
Callable<?> callable = invocation.getArgument(0);
return CompletableFuture.completedFuture(callable.call());
});
when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class)))
.thenReturn(PutObjectResponse.builder().build());
// ACT
CompletableFuture<StoredDocumentInfo> future =
fileStorageService.uploadOriginalFile(testInputStream, 1024L,
testLogContext, InvoiceFormat.UBL);
StoredDocumentInfo result = future.join();
// ASSERT
assertThat(result).isNotNull();
assertThat(result.getPath()).isNotBlank();
assertThat(result.getSize()).isEqualTo(1024L);
assertThat(result.getUploadedAt()).isNotNull();
verify(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class));
}
@Test
@DisplayName("Should handle S3 upload failure")
void givenS3Failure_whenUpload_thenCompletableFutureFails() {
// ARRANGE
when(executorService.submit(any(Callable.class))).thenAnswer(invocation -> {
return CompletableFuture.failedFuture(new StorageException("S3 unavailable"));
});
// ACT
CompletableFuture<StoredDocumentInfo> future =
fileStorageService.uploadOriginalFile(testInputStream, 1024L,
testLogContext, InvoiceFormat.UBL);
// ASSERT
assertThatThrownBy(() -> future.join())
.isInstanceOf(CompletionException.class)
.hasCauseInstanceOf(StorageException.class)
.hasMessageContaining("S3 unavailable");
}
@Test
@DisplayName("Should propagate LogContext to async operation")
void givenLogContext_whenUpload_thenContextPropagated() throws Exception {
// ARRANGE
AtomicReference<LogContext> capturedContext = new AtomicReference<>();
when(executorService.submit(any(Callable.class))).thenAnswer(invocation -> {
Callable<?> callable = invocation.getArgument(0);
capturedContext.set(CustomLog.getCurrentContext());
return CompletableFuture.completedFuture(callable.call());
});
// ACT
fileStorageService.uploadOriginalFile(testInputStream, 1024L,
testLogContext, InvoiceFormat.UBL).join();
// ASSERT
assertThat(capturedContext.get()).isNotNull();
assertThat(capturedContext.get().get("traceId")).isEqualTo("trace-123");
}
}
}
```
## Resource Layer Tests (REST Assured)
## リソースレイヤーテストREST Assured
```java
@QuarkusTest
@@ -608,27 +214,6 @@ class DocumentResourceTest {
@InjectMock
DocumentService documentService;
@Nested
@DisplayName("Tests for GET /api/documents")
class ListDocuments {
@Test
@DisplayName("Should return list of documents")
void givenDocumentsExist_whenList_thenReturnsOk() {
// ARRANGE
List<Document> documents = List.of(createDocument(1L, "DOC-001"));
when(documentService.list(0, 20)).thenReturn(documents);
// ACT & ASSERT
given()
.when().get("/api/documents")
.then()
.statusCode(200)
.body("$.size()", is(1))
.body("[0].referenceNumber", equalTo("DOC-001"));
}
}
@Nested
@DisplayName("Tests for POST /api/documents")
class CreateDocument {
@@ -654,14 +239,12 @@ class DocumentResourceTest {
.when().post("/api/documents")
.then()
.statusCode(201)
.header("Location", containsString("/api/documents/1"))
.body("referenceNumber", equalTo("DOC-001"));
}
@Test
@DisplayName("Should return 400 for invalid input")
void givenInvalidRequest_whenCreate_thenReturns400() {
// ACT & ASSERT
given()
.contentType(ContentType.JSON)
.body("""
@@ -675,58 +258,12 @@ class DocumentResourceTest {
.statusCode(400);
}
}
private Document createDocument(Long id, String referenceNumber) {
Document document = new Document();
document.setId(id);
document.setReferenceNumber(referenceNumber);
document.setStatus(DocumentStatus.PENDING);
return document;
}
}
```
## Integration Tests with Real Database
## JaCoCoカバレッジ
```java
@QuarkusTest
@TestProfile(IntegrationTestProfile.class)
@DisplayName("Document Integration Tests")
class DocumentIntegrationTest {
@Test
@Transactional
@DisplayName("Should create and retrieve document via API")
void givenNewDocument_whenCreateAndRetrieve_thenSuccessful() {
// ACT - Create via API
Long id = given()
.contentType(ContentType.JSON)
.body("""
{
"referenceNumber": "INT-001",
"description": "Integration test",
"validUntil": "2030-01-01T00:00:00Z",
"categories": ["test"]
}
""")
.when().post("/api/documents")
.then()
.statusCode(201)
.extract().path("id");
// ASSERT - Retrieve via API
given()
.when().get("/api/documents/" + id)
.then()
.statusCode(200)
.body("referenceNumber", equalTo("INT-001"));
}
}
```
## Coverage with JaCoCo
### Maven Configuration (Complete)
### Maven構成
```xml
<plugin>
@@ -734,29 +271,18 @@ class DocumentIntegrationTest {
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.13</version>
<executions>
<!-- Prepare agent for test execution -->
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<goals><goal>prepare-agent</goal></goals>
</execution>
<!-- Generate coverage report -->
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
<goals><goal>report</goal></goals>
</execution>
<!-- Enforce coverage thresholds -->
<execution>
<id>check</id>
<goals>
<goal>check</goal>
</goals>
<goals><goal>check</goal></goals>
<configuration>
<rules>
<rule>
@@ -767,11 +293,6 @@ class DocumentIntegrationTest {
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.70</minimum>
</limit>
</limits>
</rule>
</rules>
@@ -781,20 +302,19 @@ class DocumentIntegrationTest {
</plugin>
```
Run tests with coverage:
カバレッジ付きテスト実行:
```bash
mvn clean test
mvn jacoco:report
mvn jacoco:check
# Report at: target/site/jacoco/index.html
# レポート: target/site/jacoco/index.html
```
## Test Dependencies
## テスト依存関係
```xml
<dependencies>
<!-- Quarkus Testing -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
@@ -805,30 +325,17 @@ mvn jacoco:check
<artifactId>quarkus-junit5-mockito</artifactId>
<scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<!-- AssertJ (preferred over JUnit assertions) -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.2</version>
<scope>test</scope>
</dependency>
<!-- REST Assured -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<!-- Camel Testing -->
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-junit5</artifactId>
@@ -837,74 +344,33 @@ mvn jacoco:check
</dependencies>
```
## Best Practices
## ベストプラクティス
### Test Organization
- Use `@Nested` classes to group tests by method being tested
- Use `@DisplayName` for readable test descriptions visible in reports
- Follow `givenX_whenY_thenZ` naming convention for test methods
- Use `@BeforeEach` for common test data setup to reduce duplication
### テスト構成
- テスト対象メソッドごとに`@Nested`クラスでグループ化
- レポートに表示される読みやすい説明に`@DisplayName`を使用
- テストメソッドに`givenX_whenY_thenZ`命名規則を使用
### Test Structure
- Follow AAA pattern with explicit comments (`// ARRANGE`, `// ACT`, `// ASSERT`)
- Use `assertDoesNotThrow` for success scenarios
- Use `assertThrows` for exception scenarios with message validation
- Verify exception messages match expected values using AssertJ `contains()` or `isEqualTo()`
### テスト構造
- 明示的コメント付きAAAパターン`// ARRANGE``// ACT``// ASSERT`
- 成功シナリオに`assertDoesNotThrow`を使用
- メッセージバリデーション付き例外シナリオに`assertThrows`を使用
### Test Coverage
- Test happy paths for all public methods
- Test null input handling
- Test edge cases (empty collections, boundary values, negative IDs, blank strings)
- Test exception scenarios comprehensively
- Mock all external dependencies (repositories, services, Camel endpoints)
- Aim for 80%+ line coverage, 70%+ branch coverage
### アサーション
- JUnitアサーションの代わりに**常にAssertJ**`assertThat`)を使用
- 読みやすさのためにAssertJのfluent APIを使用
- 例外: `assertThatThrownBy(() -> ...).isInstanceOf(...).hasMessageContaining(...)`
### Assertions
- **Always use AssertJ** (`assertThat`) instead of JUnit assertions
- Use fluent AssertJ API for readability: `assertThat(list).hasSize(3).contains(item)`
- For exceptions: `assertThatThrownBy(() -> ...).isInstanceOf(...).hasMessageContaining(...)`
- For collections: `extracting()`, `filteredOn()`, `containsExactly()`
### イベント駆動テスト
- `AdviceWith``MockEndpoint`でCamelルートをテスト
- メッセージコンテンツ、ヘッダー、ルーティングロジックを検証
- エラーハンドリングルートを個別にテスト
- ユニットテストで外部システムRabbitMQ、S3、データベースをモック
### Testing Integration
- Use `@QuarkusTest` for integration tests
- Use `@InjectMock` to mock dependencies in Quarkus tests
- Prefer REST Assured for API testing
- Use `@TestProfile` for test-specific configuration
### Quarkus固有
- 最新のLTSバージョンQuarkus 3.xを維持
- ネイティブコンパイル互換性を定期的にテスト
- 異なるシナリオにQuarkusテストプロファイルを使用
- `@MockBean`の代わりに`@InjectMock`を使用Quarkus固有
### Event-Driven Testing
- Test Camel routes with `AdviceWith` and `MockEndpoint`
- Use `@CamelQuarkusTest` annotation (if using standalone Camel tests)
- Verify message content, headers, and routing logic
- Test error handling routes separately
- Mock external systems (RabbitMQ, S3, databases) in unit tests
### Camel Route Testing
- Use `MockEndpoint` for asserting message flow
- Use `AdviceWith` to modify routes for testing (replace endpoints with mocks)
- Test message transformation and marshalling
- Test exception handling and dead letter queues
### Testing Async Operations
- Test CompletableFuture success and failure scenarios
- Use `.join()` in tests to wait for async completion
- Test exception propagation from CompletableFuture
- Verify LogContext propagation to async operations
### Performance
- Keep tests fast and isolated
- Run tests in continuous mode: `mvn quarkus:test`
- Use parameterized tests (`@ParameterizedTest`) for input variations
- Build reusable test data builders or factory methods
### Quarkus-Specific
- Stay on latest LTS version (Quarkus 3.x)
- Test native compilation compatibility periodically
- Use Quarkus test profiles for different scenarios
- Leverage Quarkus dev services for local testing
- Use `@InjectMock` instead of `@MockBean` (Quarkus-specific)
### Verification Best Practices
- Always verify interactions on mocked dependencies
- Use `verify(mock, never())` to ensure methods are NOT called in error scenarios
- Use `argThat()` for complex argument matching
- Verify the order of calls when it matters: `InOrder` from Mockito
**覚えておいてください**: テストは高速、分離、決定的に保ちます。実装の詳細ではなく動作をテストしてください。

View File

@@ -1,25 +1,23 @@
---
name: quarkus-verification
description: "Verification loop for Quarkus projects: build, static analysis, tests with coverage, security scans, native compilation, and diff review before release or PR."
description: "Quarkusプロジェクトの検証ループ: ビルド、静的解析、カバレッジ付きテスト、セキュリティスキャン、ネイティブコンパイル、リリースまたはPR前のdiffレビュー。"
origin: ECC
---
> **Note / 注意**: このファイルはまだ日本語に翻訳されていません。現在は英語の原文です。翻訳PRを歓迎します。
# Quarkus 検証ループ
# Quarkus Verification Loop
PR前、大きな変更後、デプロイ前に実行。
Run before PRs, after major changes, and pre-deploy.
## いつアクティブにするか
## When to Activate
- Quarkusサービスのプルリクエストを開く前
- 大規模なリファクタリングまたは依存関係アップグレード後
- ステージングまたは本番のデプロイ前検証
- 完全なビルド → lint → テスト → セキュリティスキャン → ネイティブコンパイルパイプラインの実行
- テストカバレッジが閾値を満たしていることの検証80%以上)
- ネイティブイメージ互換性のテスト
- Before opening a pull request for a Quarkus service
- After major refactoring or dependency upgrades
- Pre-deployment verification for staging or production
- Running full build → lint → test → security scan → native compilation pipeline
- Validating test coverage meets thresholds (80%+)
- Testing native image compatibility
## Phase 1: Build
## フェーズ1: ビルド
```bash
# Maven
@@ -29,17 +27,17 @@ mvn clean verify -DskipTests
./gradlew clean assemble -x test
```
If build fails, stop and fix compilation errors.
ビルドが失敗した場合、停止してコンパイルエラーを修正。
## Phase 2: Static Analysis
## フェーズ2: 静的解析
### Checkstyle, PMD, SpotBugs (Maven)
### CheckstylePMDSpotBugsMaven
```bash
mvn checkstyle:check pmd:check spotbugs:check
```
### SonarQube (if configured)
### SonarQube(構成されている場合)
```bash
mvn sonar:sonar \
@@ -48,33 +46,33 @@ mvn sonar:sonar \
-Dsonar.login=${SONAR_TOKEN}
```
### Common Issues to Address
### 対処すべき一般的な問題
- Unused imports or variables
- Complex methods (high cyclomatic complexity)
- Potential null pointer dereferences
- Security issues flagged by SpotBugs
- 未使用のimportまたは変数
- 複雑なメソッド(高い循環的複雑度)
- 潜在的なnullポインター参照
- SpotBugsが検出したセキュリティ問題
## Phase 3: Tests + Coverage
## フェーズ3: テスト + カバレッジ
```bash
# Run all tests
# すべてのテストを実行
mvn clean test
# Generate coverage report
# カバレッジレポートを生成
mvn jacoco:report
# Enforce coverage threshold (80%)
# カバレッジ閾値を強制(80%
mvn jacoco:check
# Or with Gradle
# またはGradle
./gradlew test jacocoTestReport jacocoTestCoverageVerification
```
### Test Categories
### テストカテゴリ
#### Unit Tests
Test service logic with mocked dependencies:
#### ユニットテスト
モック化された依存関係でサービスロジックをテスト:
```java
@ExtendWith(MockitoExtension.class)
@@ -99,8 +97,8 @@ class UserServiceTest {
}
```
#### Integration Tests
Test with real database (Testcontainers):
#### 統合テスト
実データベース(Testcontainers)でテスト:
```java
@QuarkusTest
@@ -126,8 +124,8 @@ class UserRepositoryIntegrationTest {
}
```
#### API Tests
Test REST endpoints with REST Assured:
#### APIテスト
REST AssuredでRESTエンドポイントをテスト:
```java
@QuarkusTest
@@ -160,124 +158,77 @@ class UserResourceTest {
}
```
### Coverage Report
### カバレッジレポート
Check `target/site/jacoco/index.html` for detailed coverage:
- Overall line coverage (target: 80%+)
- Branch coverage (target: 70%+)
- Identify uncovered critical paths
詳細カバレッジは`target/site/jacoco/index.html`を確認:
- 全体行カバレッジ(目標: 80%以上)
- ブランチカバレッジ(目標: 70%以上)
- カバーされていない重要パスを特定
## Phase 4: Security Scanning
## フェーズ4: セキュリティスキャン
### Dependency Vulnerabilities (Maven)
### 依存関係脆弱性(Maven
```bash
mvn org.owasp:dependency-check-maven:check
```
Review `target/dependency-check-report.html` for CVEs.
CVEについて`target/dependency-check-report.html`を確認。
### Quarkus Security Audit
### Quarkusセキュリティ監査
```bash
# Check vulnerable extensions
# 脆弱なエクステンションを確認
mvn quarkus:audit
# List all extensions
# すべてのエクステンションをリスト
mvn quarkus:list-extensions
```
### OWASP ZAP (API Security Testing)
### 一般的なセキュリティチェック
- [ ] すべてのシークレットが環境変数に(コード内ではなく)
- [ ] すべてのエンドポイントで入力バリデーション
- [ ] 認証/認可が構成済み
- [ ] CORSが適切に構成済み
- [ ] セキュリティヘッダーが設定済み
- [ ] パスワードがBCryptでハッシュ済み
- [ ] SQLインジェクション保護パラメータ化クエリ
- [ ] パブリックエンドポイントでレート制限
## フェーズ5: ネイティブコンパイル
GraalVMネイティブイメージ互換性をテスト:
```bash
docker run -t owasp/zap2docker-stable zap-api-scan.py \
-t http://localhost:8080/q/openapi \
-f openapi
```
### Common Security Checks
- [ ] All secrets in environment variables (not in code)
- [ ] Input validation on all endpoints
- [ ] Authentication/authorization configured
- [ ] CORS properly configured
- [ ] Security headers set
- [ ] Passwords hashed with BCrypt
- [ ] SQL injection protection (parameterized queries)
- [ ] Rate limiting on public endpoints
## Phase 5: Native Compilation
Test GraalVM native image compatibility:
```bash
# Build native executable
# ネイティブ実行可能ファイルをビルド
mvn package -Dnative
# Or with container
# またはコンテナで
mvn package -Dnative -Dquarkus.native.container-build=true
# Test native executable
# ネイティブ実行可能ファイルをテスト
./target/*-runner
# Run basic smoke tests
# 基本的なスモークテストを実行
curl http://localhost:8080/q/health/live
curl http://localhost:8080/q/health/ready
```
### Native Image Troubleshooting
### ネイティブイメージトラブルシューティング
Common issues:
- **Reflection**: Add reflection config for dynamic classes
- **Resources**: Include resources with `quarkus.native.resources.includes`
- **JNI**: Register JNI classes if using native libraries
一般的な問題:
- **Reflection**: 動的クラス用のreflection構成を追加
- **Resources**: `quarkus.native.resources.includes`でリソースを含める
- **JNI**: ネイティブライブラリ使用時にJNIクラスを登録
Example reflection config:
例のreflection構成:
```java
@RegisterForReflection(targets = {MyDynamicClass.class})
public class ReflectionConfiguration {}
```
## Phase 6: Performance Testing
### Load Testing with K6
```javascript
// load-test.js
import http from 'k6/http';
import { check } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 50 },
{ duration: '1m', target: 100 },
{ duration: '30s', target: 0 },
],
};
export default function () {
const res = http.get('http://localhost:8080/api/markets');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 200ms': (r) => r.timings.duration < 200,
});
}
```
Run:
```bash
k6 run load-test.js
```
### Metrics to Monitor
- Response time (p50, p95, p99)
- Throughput (requests/sec)
- Error rate
- Memory usage
- CPU usage
## Phase 7: Health Checks
## フェーズ6: ヘルスチェック
```bash
# Liveness
@@ -286,198 +237,78 @@ curl http://localhost:8080/q/health/live
# Readiness
curl http://localhost:8080/q/health/ready
# All health checks
# すべてのヘルスチェック
curl http://localhost:8080/q/health
# Metrics (if enabled)
# メトリクス(有効な場合)
curl http://localhost:8080/q/metrics
```
Expected responses:
```json
{
"status": "UP",
"checks": [
{
"name": "Database connection",
"status": "UP"
}
]
}
```
## 検証チェックリスト
## Phase 8: Container Image Build
### コード品質
- [ ] ビルドが警告なしで通過
- [ ] 静的解析クリーン(高/中の問題なし)
- [ ] コードがチーム規約に従う
- [ ] PRにコメントアウトされたコードやTODOがない
```bash
# Build container image
mvn package -Dquarkus.container-image.build=true
### テスト
- [ ] すべてのテストが通過
- [ ] コードカバレッジ ≥ 80%
- [ ] 実データベースとの統合テスト
- [ ] セキュリティテストが通過
- [ ] パフォーマンスが許容範囲内
# Or with specific registry
mvn package \
-Dquarkus.container-image.build=true \
-Dquarkus.container-image.registry=docker.io \
-Dquarkus.container-image.group=myorg \
-Dquarkus.container-image.tag=1.0.0
### セキュリティ
- [ ] 依存関係脆弱性なし
- [ ] 認証/認可がテスト済み
- [ ] 入力バリデーション完了
- [ ] ソースコードにシークレットなし
- [ ] セキュリティヘッダーが構成済み
# Test container
docker run -p 8080:8080 myorg/my-quarkus-app:1.0.0
```
### デプロイメント
- [ ] ネイティブコンパイル成功
- [ ] コンテナイメージがビルド可能
- [ ] ヘルスチェックが正しく応答
- [ ] ターゲット環境で構成が有効
### Container Security Scan
```bash
# Trivy
trivy image myorg/my-quarkus-app:1.0.0
# Grype
grype myorg/my-quarkus-app:1.0.0
```
## Phase 9: Configuration Validation
```bash
# Check all configuration properties
mvn quarkus:info
# List all config sources
curl http://localhost:8080/q/dev/io.quarkus.quarkus-vertx-http/config
```
### Environment-Specific Checks
- [ ] Database URLs configured per environment
- [ ] Secrets externalized (Vault, env vars)
- [ ] Logging levels appropriate
- [ ] CORS origins set correctly
- [ ] Rate limiting configured
- [ ] Monitoring/tracing enabled
## Phase 10: Documentation Review
- [ ] OpenAPI/Swagger docs up to date (`/q/swagger-ui`)
- [ ] README has setup instructions
- [ ] API changes documented
- [ ] Migration guide for breaking changes
- [ ] Configuration properties documented
Generate OpenAPI spec:
```bash
curl http://localhost:8080/q/openapi -o openapi.json
```
## Verification Checklist
### Code Quality
- [ ] Build passes without warnings
- [ ] Static analysis clean (no high/medium issues)
- [ ] Code follows team conventions
- [ ] No commented-out code or TODOs in PR
### Testing
- [ ] All tests pass
- [ ] Code coverage ≥ 80%
- [ ] Integration tests with real database
- [ ] Security tests pass
- [ ] Performance within acceptable limits
### Security
- [ ] No dependency vulnerabilities
- [ ] Authentication/authorization tested
- [ ] Input validation complete
- [ ] Secrets not in source code
- [ ] Security headers configured
### Deployment
- [ ] Native compilation successful
- [ ] Container image builds
- [ ] Health checks respond correctly
- [ ] Configuration valid for target environment
### Native Image
- [ ] Native executable builds
- [ ] Native tests pass
- [ ] Startup time < 100ms
- [ ] Memory footprint acceptable
## Automated Verification Script
## 自動検証スクリプト
```bash
#!/bin/bash
set -e
echo "=== Phase 1: Build ==="
echo "=== フェーズ1: ビルド ==="
mvn clean verify -DskipTests
echo "=== Phase 2: Static Analysis ==="
echo "=== フェーズ2: 静的解析 ==="
mvn checkstyle:check pmd:check spotbugs:check
echo "=== Phase 3: Tests + Coverage ==="
echo "=== フェーズ3: テスト + カバレッジ ==="
mvn test jacoco:report jacoco:check
echo "=== Phase 4: Security Scan ==="
echo "=== フェーズ4: セキュリティスキャン ==="
mvn org.owasp:dependency-check-maven:check
echo "=== Phase 5: Native Compilation ==="
echo "=== フェーズ5: ネイティブコンパイル ==="
mvn package -Dnative -Dquarkus.native.container-build=true
echo "=== All Phases Complete ==="
echo "Review reports:"
echo " - Coverage: target/site/jacoco/index.html"
echo " - Security: target/dependency-check-report.html"
echo " - Native: target/*-runner"
echo "=== 全フェーズ完了 ==="
echo "レポートを確認:"
echo " - カバレッジ: target/site/jacoco/index.html"
echo " - セキュリティ: target/dependency-check-report.html"
echo " - ネイティブ: target/*-runner"
```
## CI/CD Integration
## ベストプラクティス
### GitHub Actions Example
```yaml
name: Verification
on: [push, pull_request]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
- name: Build
run: mvn clean verify -DskipTests
- name: Test with Coverage
run: mvn test jacoco:report jacoco:check
- name: Security Scan
run: mvn org.owasp:dependency-check-maven:check
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
files: target/site/jacoco/jacoco.xml
```
## Best Practices
- Run verification loop before every PR
- Automate in CI/CD pipeline
- Fix issues immediately; don't accumulate debt
- Keep coverage above 80%
- Update dependencies regularly
- Test native compilation periodically
- Monitor performance trends
- Document breaking changes
- Review security scan results
- Validate configuration for each environment
- すべてのPR前に検証ループを実行
- CI/CDパイプラインで自動化
- 問題を即座に修正し、技術的負債を蓄積しない
- カバレッジを80%以上に維持
- 依存関係を定期的に更新
- ネイティブコンパイルを定期的にテスト
- パフォーマンストレンドを監視
- 破壊的変更を文書化
- セキュリティスキャン結果をレビュー
- 各環境の構成を検証

View File

@@ -4,26 +4,24 @@ description: Quarkus 3.x LTS architecture patterns with Camel for messaging, RES
origin: ECC
---
> **Not / Note**: Bu dosya henuz Turkceye cevrilmemistir, su anda Ingilizce orijinaldir. Ceviri PR'lari memnuniyetle karsilanir.
# Quarkus Geliştirme Desenleri
# Quarkus Development Patterns
Apache Camel ile bulut-native, event-driven servisler için Quarkus 3.x mimari ve API desenleri.
Quarkus 3.x architecture and API patterns for cloud-native, event-driven services with Apache Camel.
## Ne Zaman Aktif Edilir
## When to Activate
- JAX-RS veya RESTEasy Reactive ile REST API'leri oluşturma
- Resource → service → repository katmanlarını yapılandırma
- Apache Camel ve RabbitMQ ile event-driven desenler uygulama
- Hibernate Panache, caching veya reaktif akışları yapılandırma
- Validation, exception mapping veya sayfalama ekleme
- Dev/staging/production ortamları için profiller kurma (YAML yapılandırma)
- LogContext ve Logback/Logstash encoder ile özel loglama
- Async işlemler için CompletableFuture ile çalışma
- Koşullu akış işleme uygulama
- GraalVM native derleme ile çalışma
- Building REST APIs with JAX-RS or RESTEasy Reactive
- Structuring resource → service → repository layers
- Implementing event-driven patterns with Apache Camel and RabbitMQ
- Configuring Hibernate Panache, caching, or reactive streams
- Adding validation, exception mapping, or pagination
- Setting up profiles for dev/staging/production environments (YAML config)
- Custom logging with LogContext and Logback/Logstash encoder
- Working with CompletableFuture for async operations
- Implementing conditional flow processing
- Working with GraalVM native compilation
## Service Layer with Multiple Dependencies (Lombok)
## Birden Fazla Bağımlılıklı Service Katmanı (Lombok)
```java
@Slf4j
@@ -43,7 +41,7 @@ public class As2ProcessingService {
String structureIdPartner = logContext.get(As2Constants.STRUCTURE_ID);
// Conditional flow logic
// Koşullu akış mantığı
boolean isChorusFlow = Boolean.parseBoolean(logContext.get(As2Constants.CHORUS_FLOW));
log.info("Is CHORUS_FLOW message: {}", isChorusFlow);
@@ -62,7 +60,7 @@ public class As2ProcessingService {
log.info("Invoice validation completed. Message is valid");
// CompletableFuture async operation
// CompletableFuture async işlemi
try(InputStream inputStream = Files.newInputStream(filePath)) {
CompletableFuture<StoredDocumentInfo> documentInfoCompletableFuture =
fileStorageService.uploadOriginalFile(inputStream,
@@ -85,7 +83,7 @@ public class As2ProcessingService {
documentInfo, originalFileName, structureIdPartner,
flowProfile, invoiceValidationResult.getDocumentHash());
// Async Camel publishing
// Async Camel yayınlama
businessRulesPublisher.publishAsync(payload);
this.eventService.createSuccessEvent(payload, "BUSINESS_RULES_MESSAGE_SENT");
}
@@ -94,16 +92,16 @@ public class As2ProcessingService {
}
```
**Key Patterns:**
- `@RequiredArgsConstructor` for constructor injection via Lombok
- `@Slf4j` for Logback logging
- Scoped LogContext with try-with-resources
- Conditional flow logic based on runtime parameters
- CompletableFuture with `.join()` for async operations
- Event tracking for success/error scenarios
- Async Camel message publishing
**Temel Desenler:**
- Constructor injection için Lombok üzerinden `@RequiredArgsConstructor`
- Logback loglama için `@Slf4j`
- try-with-resources ile kapsamlı LogContext
- Runtime parametrelerine dayalı koşullu akış mantığı
- Async işlemler için `.join()` ile CompletableFuture
- Başarı/hata senaryoları için event takibi
- Async Camel mesaj yayınlama
## Custom Logging Context Pattern (Logback)
## Özel Loglama Bağlamı Deseni (Logback)
```java
@ApplicationScoped
@@ -112,14 +110,14 @@ public class ProcessingService {
public void processDocument(Document doc) {
LogContext logContext = CustomLog.getCurrentContext();
try (SafeAutoCloseable ignored = CustomLog.startScope(logContext)) {
// Add context to all log statements
// Tüm log ifadelerine bağlam ekle
logContext.put("documentId", doc.getId().toString());
logContext.put("documentType", doc.getType());
logContext.put("userId", SecurityContext.getUserId());
log.info("Starting document processing");
// All logs within this scope inherit the context
// Bu kapsam içindeki tüm loglar bağlamı devralır
processInternal(doc);
log.info("Document processing completed");
@@ -131,7 +129,7 @@ public class ProcessingService {
}
```
**Logback Configuration (logback.xml):**
**Logback Yapılandırması (logback.xml):**
```xml
<configuration>
@@ -149,7 +147,7 @@ public class ProcessingService {
</configuration>
```
## Event Service Pattern
## Event Service Deseni
```java
@ApplicationScoped
@@ -181,13 +179,13 @@ public class EventService {
}
private String serializePayload(Object payload) {
// JSON serialization
// JSON serileştirme
return objectMapper.writeValueAsString(payload);
}
}
```
## Camel Message Publishing (RabbitMQ)
## Camel Mesaj Yayınlama (RabbitMQ)
```java
@ApplicationScoped
@@ -215,7 +213,7 @@ public class BusinessRulesPublisher {
}
```
**Camel Route Configuration:**
**Camel Route Yapılandırması:**
```java
@ApplicationScoped
@@ -242,7 +240,7 @@ public class BusinessRulesRoute extends RouteBuilder {
}
```
## Camel Direct Routes (In-Memory)
## Camel Direct Route'ları (Bellek İçi)
```java
@ApplicationScoped
@@ -250,13 +248,13 @@ public class DocumentProcessingRoute extends RouteBuilder {
@Override
public void configure() {
// Error handling
// Hata yönetimi
onException(ValidationException.class)
.handled(true)
.to("direct:validation-error-handler")
.log("Validation error: ${exception.message}");
// Main processing route
// Ana işleme route'u
from("direct:process-document")
.routeId("document-processing")
.log("Processing document: ${header.documentId}")
@@ -278,7 +276,7 @@ public class DocumentProcessingRoute extends RouteBuilder {
}
```
## Camel File Processing
## Camel Dosya İşleme
```java
@ApplicationScoped
@@ -308,7 +306,7 @@ public class FileMonitoringRoute extends RouteBuilder {
}
```
## Camel Bean Invocation
## Camel Bean Çağrısı
```java
@ApplicationScoped
@@ -328,7 +326,7 @@ public class InvoiceRoute extends RouteBuilder {
}
```
## REST API Structure
## REST API Yapısı
```java
@Path("/api/documents")
@@ -367,7 +365,7 @@ public class DocumentResource {
}
```
## Repository Pattern (Panache Repository)
## Repository Deseni (Panache Repository)
```java
@ApplicationScoped
@@ -389,7 +387,7 @@ public class DocumentRepository implements PanacheRepository<Document> {
}
```
## Service Layer with Transactions
## Transaction'lı Service Katmanı
```java
@ApplicationScoped
@@ -425,7 +423,7 @@ public class DocumentService {
}
```
## DTOs and Validation
## DTO'lar ve Validation
```java
public record CreateDocumentRequest(
@@ -442,7 +440,7 @@ public record DocumentResponse(Long id, String referenceNumber, DocumentStatus s
}
```
## Exception Mapping
## Exception Eşleme
```java
@Provider
@@ -473,7 +471,7 @@ public class GenericExceptionMapper implements ExceptionMapper<Exception> {
}
```
## CompletableFuture Async Operations
## CompletableFuture Async İşlemleri
```java
@ApplicationScoped
@@ -533,10 +531,10 @@ public class DocumentCacheService {
}
```
## Configuration as YAML
## YAML Yapılandırması
```yaml
# application.yml
# application.yml (uygulama yapılandırması)
"%dev":
quarkus:
datasource:
@@ -580,7 +578,7 @@ public class DocumentCacheService {
username: ${RABBITMQ_USER}
password: ${RABBITMQ_PASSWORD}
# Camel configuration
# Camel yapılandırması
camel:
rabbitmq:
queue:
@@ -588,7 +586,7 @@ camel:
invoice-processing: invoice-processing-queue
```
## Health Checks
## Sağlık Kontrolleri
```java
@Readiness
@@ -626,7 +624,7 @@ public class CamelHealthCheck implements HealthCheck {
}
```
## Dependencies (Maven)
## Bağımlılıklar (Maven)
```xml
<properties>
@@ -657,7 +655,7 @@ public class CamelHealthCheck implements HealthCheck {
</dependencyManagement>
<dependencies>
<!-- Quarkus Core -->
<!-- Quarkus Çekirdek -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
@@ -667,7 +665,7 @@ public class CamelHealthCheck implements HealthCheck {
<artifactId>quarkus-config-yaml</artifactId>
</dependency>
<!-- Camel Extensions -->
<!-- Camel Uzantıları -->
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-spring-rabbitmq</artifactId>
@@ -689,7 +687,7 @@ public class CamelHealthCheck implements HealthCheck {
<scope>provided</scope>
</dependency>
<!-- Logging -->
<!-- Loglama -->
<dependency>
<groupId>io.quarkiverse.logging.logback</groupId>
<artifactId>quarkus-logging-logback</artifactId>
@@ -701,56 +699,56 @@ public class CamelHealthCheck implements HealthCheck {
</dependencies>
```
## Best Practices
## En İyi Uygulamalar
### Architecture
- Use `@RequiredArgsConstructor` with Lombok for constructor injection
- Keep service layer thin; delegate complex logic to specialized classes
- Use Camel routes for message routing and integration patterns
- Prefer Panache Repository pattern for data access
### Mimari
- Constructor injection için Lombok üzerinden `@RequiredArgsConstructor` kullanın
- Service katmanını ince tutun; karmaşık mantığı uzmanlaşmış sınıflara devredin
- Mesaj yönlendirme ve entegrasyon desenleri için Camel route'larını kullanın
- Veri erişimi için Panache Repository desenini tercih edin
### Event-Driven
- Always track operations with EventService (success/error events)
- Use Camel `direct:` endpoints for in-memory routing
- Use `spring-rabbitmq` component for RabbitMQ integration
- Implement async publishing with `ProducerTemplate.asyncSendBody()`
- EventService ile işlemleri her zaman takip edin (başarı/hata eventleri)
- Bellek içi yönlendirme için Camel `direct:` endpoint'leri kullanın
- RabbitMQ entegrasyonu için `spring-rabbitmq` bileşenini kullanın
- `ProducerTemplate.asyncSendBody()` ile async yayınlama uygulayın
### Logging
- Use Logback with Logstash encoder for structured logging
- Propagate LogContext through service calls with `SafeAutoCloseable`
- Add contextual information to LogContext for request tracing
- Use `@Slf4j` instead of manual logger instantiation
### Loglama
- Yapılandırılmış loglama için Logstash encoder ile Logback kullanın
- LogContext'i `SafeAutoCloseable` ile servis çağrıları boyunca yayın
- İstek takibi için LogContext'e bağlamsal bilgi ekleyin
- Manuel logger oluşturma yerine `@Slf4j` kullanın
### Async Operations
- Use CompletableFuture for non-blocking I/O operations
- Call `.join()` when you need to wait for completion
- Handle exceptions from CompletableFuture properly
- Pass LogContext to async operations for tracing
### Async İşlemler
- Bloklamayan I/O işlemleri için CompletableFuture kullanın
- Tamamlanmayı beklemek gerektiğinde `.join()` çağırın
- CompletableFuture'dan gelen exception'ları düzgün şekilde ele alın
- Takip için async işlemlere LogContext geçirin
### Configuration
- Use YAML configuration (`quarkus-config-yaml`)
- Profile-aware configuration for dev/test/prod environments
- Externalize sensitive configuration to environment variables
- Use `@ConfigProperty` for type-safe config injection
### Yapılandırma
- YAML yapılandırmasını kullanın (`quarkus-config-yaml`)
- Dev/test/prod ortamları için profil-duyarlı yapılandırma
- Hassas yapılandırmayı ortam değişkenlerine dışsallaştırın
- Tip-güvenli yapılandırma injection için `@ConfigProperty` kullanın
### Validation
- Validate at resource layer with `@Valid`
- Use Bean Validation annotations on DTOs
- Map exceptions to proper HTTP responses with `@Provider`
- Resource katmanında `@Valid` ile doğrulayın
- DTO'larda Bean Validation annotasyonları kullanın
- Exception'ları `@Provider` ile uygun HTTP yanıtlarına eşleyin
### Transactions
- Use `@Transactional` on service methods that modify data
- Keep transactions short and focused
- Avoid calling async operations within transactions
### Transaction'lar
- Veri değiştiren service metodlarında `@Transactional` kullanın
- Transaction'ları kısa ve odaklı tutun
- Transaction'lar içinden async işlem çağırmaktan kaçının
### Testing
- Use `camel-quarkus-junit5` for route testing
- Use AssertJ for assertions
- Mock all external dependencies
- Test conditional flow logic thoroughly
### Test
- Route testi için `camel-quarkus-junit5` kullanın
- Assertion'lar için AssertJ kullanın
- Tüm harici bağımlılıkları mock'layın
- Koşullu akış mantığını kapsamlı biçimde test edin
### Quarkus-Specific
- Stay on latest LTS version (3.x)
- Use Quarkus dev mode for hot reload
- Add health checks for production readiness
- Test native compilation compatibility periodically
### Quarkus'a Özgü
- En son LTS sürümünde kalın (3.x)
- Hot reload için Quarkus dev modunu kullanın
- Production hazırlığı için sağlık kontrolleri ekleyin
- Native derleme uyumluluğunu periyodik olarak test edin

View File

@@ -4,29 +4,27 @@ description: Quarkus Security best practices for authentication, authorization,
origin: ECC
---
> **Not / Note**: Bu dosya henuz Turkceye cevrilmemistir, su anda Ingilizce orijinaldir. Ceviri PR'lari memnuniyetle karsilanir.
# Quarkus Güvenlik İncelemesi
# Quarkus Security Review
Kimlik doğrulama, yetkilendirme ve girdi doğrulama ile Quarkus uygulamalarını güvenli hale getirmek için en iyi uygulamalar.
Best practices for securing Quarkus applications with authentication, authorization, and input validation.
## Ne Zaman Aktif Edilir
## When to Activate
- Kimlik doğrulama ekleme (JWT, OIDC, Basic Auth)
- `@RolesAllowed` veya SecurityIdentity ile yetkilendirme uygulama
- Kullanıcı girişini doğrulama (Bean Validation, özel doğrulayıcılar)
- CORS veya güvenlik başlıklarını yapılandırma
- Gizli bilgileri yönetme (Vault, ortam değişkenleri, config kaynakları)
- Rate limiting veya brute-force koruması ekleme
- Bağımlılıkları CVE için tarama
- MicroProfile JWT veya SmallRye JWT ile çalışma
- Adding authentication (JWT, OIDC, Basic Auth)
- Implementing authorization with @RolesAllowed or SecurityIdentity
- Validating user input (Bean Validation, custom validators)
- Configuring CORS or security headers
- Managing secrets (Vault, environment variables, config sources)
- Adding rate limiting or brute-force protection
- Scanning dependencies for CVEs
- Working with MicroProfile JWT or SmallRye JWT
## Kimlik Doğrulama
## Authentication
### JWT Authentication
### JWT Kimlik Doğrulama
```java
// Resource protected with JWT
// JWT ile korunan resource
@Path("/api/protected")
@Authenticated
public class ProtectedResource {
@@ -50,7 +48,7 @@ public class ProtectedResource {
}
```
Configuration (application.properties):
Yapılandırma (application.properties):
```properties
mp.jwt.verify.publickey.location=publicKey.pem
mp.jwt.verify.issuer=https://auth.example.com
@@ -61,7 +59,7 @@ quarkus.oidc.client-id=backend-service
quarkus.oidc.credentials.secret=${OIDC_SECRET}
```
### Custom Authentication Filter
### Özel Kimlik Doğrulama Filtresi
```java
@Provider
@@ -77,7 +75,7 @@ public class CustomAuthFilter implements ContainerRequestFilter {
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
// Validate token and set SecurityIdentity
// Token'ı doğrula ve SecurityIdentity'yi ayarla
if (!validateToken(token)) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
@@ -85,15 +83,15 @@ public class CustomAuthFilter implements ContainerRequestFilter {
}
private boolean validateToken(String token) {
// Token validation logic
// Token doğrulama mantığı
return true;
}
}
```
## Authorization
## Yetkilendirme
### Role-Based Access Control
### Rol Tabanlı Erişim Kontrolü
```java
@Path("/api/admin")
@@ -125,7 +123,7 @@ public class UserResource {
@Path("/{id}")
@RolesAllowed("USER")
public Response getUser(@PathParam("id") Long id) {
// Check ownership
// Sahipliği kontrol et
if (!securityIdentity.hasRole("ADMIN") &&
!isOwner(id, securityIdentity.getPrincipal().getName())) {
return Response.status(Response.Status.FORBIDDEN).build();
@@ -139,7 +137,7 @@ public class UserResource {
}
```
### Programmatic Security
### Programatik Güvenlik
```java
@ApplicationScoped
@@ -163,18 +161,18 @@ public class SecurityService {
}
```
## Input Validation
## Girdi Doğrulama
### Bean Validation
```java
// BAD: No validation
// KÖTÜ: Validation yok
@POST
public Response createUser(UserDto dto) {
return Response.ok(userService.create(dto)).build();
}
// GOOD: Validated DTO
// İYİ: Doğrulanmış DTO
public record CreateUserDto(
@NotBlank @Size(max = 100) String name,
@NotBlank @Email String email,
@@ -190,7 +188,7 @@ public Response createUser(@Valid CreateUserDto dto) {
}
```
### Custom Validators
### Özel Doğrulayıcılar
```java
@Target({ElementType.FIELD, ElementType.PARAMETER})
@@ -210,35 +208,35 @@ public class UsernameValidator implements ConstraintValidator<ValidUsername, Str
}
}
// Usage
// Kullanım
public record CreateUserDto(
@ValidUsername String username,
@NotBlank @Email String email
) {}
```
## SQL Injection Prevention
## SQL Injection Önleme
### Panache Active Record (Safe by Default)
### Panache Active Record (Varsayılan Olarak Güvenli)
```java
// GOOD: Parameterized queries with Panache
// İYİ: Panache ile parametreli sorgular
List<User> users = User.list("email = ?1 and active = ?2", email, true);
Optional<User> user = User.find("username", username).firstResultOptional();
// GOOD: Named parameters
// İYİ: İsimlendirilmiş parametreler
List<User> users = User.list("email = :email and age > :minAge",
Parameters.with("email", email).and("minAge", 18));
```
### Native Queries (Use Parameters)
### Native Sorgular (Parametre Kullanın)
```java
// BAD: String concatenation
// KÖTÜ: String birleştirme
@Query(value = "SELECT * FROM users WHERE name = '" + name + "'", nativeQuery = true)
// GOOD: Parameterized native query
// İYİ: Parametreli native sorgu
@Entity
public class User extends PanacheEntity {
public static List<User> findByEmailNative(String email) {
@@ -250,7 +248,7 @@ public class User extends PanacheEntity {
}
```
## Password Hashing
## Parola Hash'leme
```java
@ApplicationScoped
@@ -265,7 +263,7 @@ public class PasswordService {
}
}
// In service
// Servis içinde
@ApplicationScoped
public class UserService {
@Inject
@@ -290,7 +288,7 @@ public class UserService {
}
```
## CORS Configuration
## CORS Yapılandırması
```properties
# application.properties
@@ -303,12 +301,12 @@ quarkus.http.cors.access-control-max-age=24H
quarkus.http.cors.access-control-allow-credentials=true
```
## Secrets Management
## Gizli Bilgi Yönetimi
```properties
# application.properties - NO SECRETS HERE
# application.properties - BURADA GİZLİ BİLGİ YOK
# Use environment variables
# Ortam değişkenlerini kullanın
quarkus.datasource.username=${DB_USER}
quarkus.datasource.password=${DB_PASSWORD}
quarkus.oidc.credentials.secret=${OIDC_CLIENT_SECRET}
@@ -318,14 +316,14 @@ quarkus.vault.url=https://vault.example.com
quarkus.vault.authentication.kubernetes.role=my-role
```
### HashiCorp Vault Integration
### HashiCorp Vault Entegrasyonu
```java
@ApplicationScoped
public class SecretService {
@ConfigProperty(name = "api-key")
String apiKey; // Fetched from Vault
String apiKey; // Vault'tan alınır
public String getSecret(String key) {
return ConfigProvider.getConfig().getValue(key, String.class);
@@ -333,7 +331,7 @@ public class SecretService {
}
```
## Rate Limiting
## Rate Limiting (Hız Sınırlama)
```java
@ApplicationScoped
@@ -344,7 +342,7 @@ public class RateLimitFilter implements ContainerRequestFilter {
public void filter(ContainerRequestContext requestContext) {
String clientId = getClientIdentifier(requestContext);
RateLimiter limiter = limiters.computeIfAbsent(clientId,
k -> RateLimiter.create(100.0)); // 100 requests per second
k -> RateLimiter.create(100.0)); // Saniyede 100 istek
if (!limiter.tryAcquire()) {
requestContext.abortWith(
@@ -356,13 +354,13 @@ public class RateLimitFilter implements ContainerRequestFilter {
}
private String getClientIdentifier(ContainerRequestContext ctx) {
// Use IP, API key, or user ID
// IP, API anahtarı veya kullanıcı ID'si kullanın
return ctx.getHeaderString("X-Forwarded-For");
}
}
```
## Security Headers
## Güvenlik Başlıkları
```java
@Provider
@@ -372,10 +370,10 @@ public class SecurityHeadersFilter implements ContainerResponseFilter {
public void filter(ContainerRequestContext request, ContainerResponseContext response) {
MultivaluedMap<String, Object> headers = response.getHeaders();
// Prevent clickjacking
// Clickjacking'i önle
headers.putSingle("X-Frame-Options", "DENY");
// XSS protection
// XSS koruması
headers.putSingle("X-Content-Type-Options", "nosniff");
headers.putSingle("X-XSS-Protection", "1; mode=block");
@@ -389,7 +387,7 @@ public class SecurityHeadersFilter implements ContainerResponseFilter {
}
```
## Audit Logging
## Denetim Loglama
```java
@ApplicationScoped
@@ -409,7 +407,7 @@ public class AuditService {
}
}
// Usage in resource
// Resource içinde kullanım
@Path("/api/sensitive")
public class SensitiveResource {
@Inject
@@ -424,7 +422,7 @@ public class SensitiveResource {
}
```
## Dependency Security Scanning
## Bağımlılık Güvenliği Taraması
```bash
# Maven
@@ -437,19 +435,19 @@ mvn org.owasp:dependency-check-maven:check
quarkus extension list --installable
```
## Best Practices
## En İyi Uygulamalar
- Always use HTTPS in production
- Enable JWT or OIDC for stateless authentication
- Use `@RolesAllowed` for declarative authorization
- Validate all input with Bean Validation
- Hash passwords with BCrypt (never plaintext)
- Store secrets in Vault or environment variables
- Use parameterized queries to prevent SQL injection
- Add security headers to all responses
- Implement rate limiting for public endpoints
- Audit sensitive operations
- Keep dependencies updated and scan for CVEs
- Use SecurityIdentity for programmatic checks
- Set appropriate CORS policies
- Test authentication and authorization paths
- Production'da her zaman HTTPS kullanın
- Stateless kimlik doğrulama için JWT veya OIDC etkinleştirin
- Bildirimsel yetkilendirme için `@RolesAllowed` kullanın
- Bean Validation ile tüm girişleri doğrulayın
- Parolaları BCrypt ile hash'leyin (asla düz metin saklamayın)
- Gizli bilgileri Vault veya ortam değişkenlerinde saklayın
- SQL injection'ı önlemek için parametreli sorgular kullanın
- Tüm yanıtlara güvenlik başlıkları ekleyin
- Genel endpoint'lerde rate limiting uygulayın
- Hassas işlemleri denetleyin
- Bağımlılıkları güncel tutun ve CVE için tarayın
- Programatik kontroller için SecurityIdentity kullanın
- Uygun CORS politikaları belirleyin
- Kimlik doğrulama ve yetkilendirme yollarını test edin

View File

@@ -4,33 +4,31 @@ description: Test-driven development for Quarkus 3.x LTS using JUnit 5, Mockito,
origin: ECC
---
> **Not / Note**: Bu dosya henuz Turkceye cevrilmemistir, su anda Ingilizce orijinaldir. Ceviri PR'lari memnuniyetle karsilanir.
# Quarkus TDD İş Akışı
# Quarkus TDD Workflow
80%+ kapsam (unit + integration) ile Quarkus 3.x servisleri için TDD rehberi. Apache Camel ile event-driven mimariler için optimize edilmiştir.
TDD guidance for Quarkus 3.x services with 80%+ coverage (unit + integration). Optimized for event-driven architectures with Apache Camel.
## Ne Zaman Kullanılır
## When to Use
- Yeni özellikler veya REST endpoint'leri
- Bug düzeltmeleri veya refactoring'ler
- Veri erişim mantığı, güvenlik kuralları veya reaktif akışlar ekleme
- Apache Camel route'larını ve event handler'larını test etme
- RabbitMQ ile event-driven servisleri test etme
- Koşullu akış mantığını test etme
- CompletableFuture async işlemlerini doğrulama
- LogContext yayılımını test etme
- New features or REST endpoints
- Bug fixes or refactors
- Adding data access logic, security rules, or reactive streams
- Testing Apache Camel routes and event handlers
- Testing event-driven services with RabbitMQ
- Testing conditional flow logic
- Validating CompletableFuture async operations
- Testing LogContext propagation
## İş Akışı
## Workflow
1. Önce testleri yazın (başarısız olmalılar)
2. Geçmek için minimal kod uygulayın
3. Testleri yeşil tutarken refactor edin
4. JaCoCo ile kapsamı zorlayın (%80+ hedef)
1. Write tests first (they should fail)
2. Implement minimal code to pass
3. Refactor with tests green
4. Enforce coverage with JaCoCo (80%+ target)
## @Nested Organizasyonlu Unit Testler
## Unit Tests with @Nested Organization
Follow this structured approach for comprehensive, readable tests:
Kapsamlı ve okunabilir testler için bu yapılandırılmış yaklaşımı izleyin:
```java
@ExtendWith(MockitoExtension.class)
@@ -62,7 +60,7 @@ class As2ProcessingServiceTest {
@BeforeEach
void setUp() {
// ARRANGE - Common test data
// ARRANGE - Ortak test verisi
testFilePath = Path.of("/tmp/test-invoice.xml");
testLogContext = new LogContext();
@@ -81,11 +79,11 @@ class As2ProcessingServiceTest {
}
@Nested
@DisplayName("Tests for processFile")
@DisplayName("processFile için testler")
class ProcessFile {
@Test
@DisplayName("Should successfully process non-CHORUS file with all validations")
@DisplayName("CHORUS olmayan dosyayı tüm validasyonlarla başarıyla işlemeli")
void givenNonChorusFile_whenProcessFile_thenAllValidationsApplied() throws Exception {
// ARRANGE
testLogContext.put(As2Constants.CHORUS_FLOW, "false");
@@ -125,7 +123,7 @@ class As2ProcessingServiceTest {
}
@Test
@DisplayName("Should bypass schematron validation for CHORUS_FLOW")
@DisplayName("CHORUS_FLOW için schematron validasyonu atlanmalı")
void givenChorusFlow_whenProcessFile_thenSchematronBypassed() throws Exception {
// ARRANGE
testLogContext.put(As2Constants.CHORUS_FLOW, "true");
@@ -162,7 +160,7 @@ class As2ProcessingServiceTest {
}
@Test
@DisplayName("Should create error event when file upload fails")
@DisplayName("Dosya yükleme başarısız olduğunda hata eventi oluşturulmalı")
void givenUploadFailure_whenProcessFile_thenErrorEventCreated() throws Exception {
// ARRANGE
testLogContext.put(As2Constants.CHORUS_FLOW, "false");
@@ -174,7 +172,7 @@ class As2ProcessingServiceTest {
when(invoiceFlowValidator.computeFlowProfile(any(), any()))
.thenReturn(FlowProfile.BASIC);
documentInfo.setPath(""); // Blank path triggers error
documentInfo.setPath(""); // B path hatayı tetikler
when(fileStorageService.uploadOriginalFile(any(), anyLong(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(documentInfo));
@@ -196,7 +194,7 @@ class As2ProcessingServiceTest {
}
@Test
@DisplayName("Should handle CompletableFuture.join() failure")
@DisplayName("CompletableFuture.join() başarısızlığı ele alınmalı")
void givenAsyncUploadFailure_whenProcessFile_thenExceptionThrown() throws Exception {
// ARRANGE
testLogContext.put(As2Constants.CHORUS_FLOW, "false");
@@ -221,7 +219,7 @@ class As2ProcessingServiceTest {
}
@Test
@DisplayName("Should throw exception when file path is null")
@DisplayName("Dosya yolu null olduğunda exception fırlatılmalı")
void givenNullFilePath_whenProcessFile_thenThrowsException() {
// ARRANGE
Path nullPath = null;
@@ -238,20 +236,20 @@ class As2ProcessingServiceTest {
}
```
### Key Testing Patterns
### Temel Test Desenleri
1. **@Nested Classes**: Group tests by method being tested
2. **@DisplayName**: Provide readable test descriptions for test reports
3. **Naming Convention**: `givenX_whenY_thenZ` for clarity
4. **AAA Pattern**: Explicit `// ARRANGE`, `// ACT`, `// ASSERT` comments
5. **@BeforeEach**: Setup common test data to reduce duplication
6. **assertDoesNotThrow**: Test success scenarios without catching exceptions
7. **assertThrows**: Test exception scenarios with message validation using AssertJ
8. **Comprehensive Coverage**: Test happy paths, null inputs, edge cases, exceptions
9. **Verify Interactions**: Use Mockito `verify()` to ensure methods are called correctly
10. **Never Verify**: Use `never()` to ensure methods are NOT called in error scenarios
1. **@Nested Sınıflar**: Testleri test edilen metoda göre gruplandırın
2. **@DisplayName**: Test raporlarında okunabilir açıklamalar sağlayın
3. **İsimlendirme Kuralı**: Netlik için `givenX_whenY_thenZ`
4. **AAA Deseni**: Açık `// ARRANGE`, `// ACT`, `// ASSERT` yorumları
5. **@BeforeEach**: Tekrarı azaltmak için ortak test verisi kurulumu
6. **assertDoesNotThrow**: Exception yakalamadan başarı senaryolarını test edin
7. **assertThrows**: AssertJ kullanarak mesaj doğrulamalı exception senaryolarını test edin
8. **Kapsamlı Kapsam**: Mutlu yolları, null girdileri, edge case'leri, exception'ları test edin
9. **Etkileşimleri Doğrulama**: Metodların doğru çağrıldığından emin olmak için Mockito `verify()` kullanın
10. **Hiçbir Zaman Doğrulama**: Hata senaryolarında metodların ÇAĞRILMADIĞINI sağlamak için `never()` kullanın
## Testing Camel Routes
## Camel Route Testi
```java
@QuarkusTest
@@ -271,25 +269,25 @@ class BusinessRulesRouteTest {
@BeforeEach
void setUp() {
// ARRANGE - Test data
// ARRANGE - Test verisi
testPayload = new BusinessRulesPayload();
testPayload.setDocumentId(1L);
testPayload.setFlowProfile(FlowProfile.BASIC);
}
@Nested
@DisplayName("Tests for business-rules-publisher route")
@DisplayName("business-rules-publisher route için testler")
class BusinessRulesPublisher {
@Test
@DisplayName("Should successfully publish message to RabbitMQ")
@DisplayName("Mesajı başarıyla RabbitMQ'ya yayınlamalı")
void givenValidPayload_whenPublish_thenMessageSentToQueue() throws Exception {
// ARRANGE
MockEndpoint mockRabbitMQ = camelContext.getEndpoint("mock:rabbitmq", MockEndpoint.class);
mockRabbitMQ.expectedMessageCount(1);
mockRabbitMQ.expectedBodiesReceived(testPayload);
// Replace real endpoint with mock for testing
// Test için gerçek endpoint'i mock ile değiştir
camelContext.getRouteController().stopRoute("business-rules-publisher");
AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {
advice.replaceFromWith("direct:business-rules-publisher");
@@ -309,7 +307,7 @@ class BusinessRulesRouteTest {
}
@Test
@DisplayName("Should handle marshalling to JSON")
@DisplayName("JSON'a marshalling'i ele almalı")
void givenPayload_whenPublish_thenMarshalledToJson() throws Exception {
// ARRANGE
MockEndpoint mockMarshal = new MockEndpoint("mock:marshal");
@@ -335,11 +333,11 @@ class BusinessRulesRouteTest {
}
@Nested
@DisplayName("Tests for document-processing route")
@DisplayName("document-processing route için testler")
class DocumentProcessing {
@Test
@DisplayName("Should route invoice to correct processor")
@DisplayName("Faturayı doğru işlemciye yönlendirmeli")
void givenInvoiceType_whenProcess_thenRoutesToInvoiceProcessor() throws Exception {
// ARRANGE
MockEndpoint mockInvoice = camelContext.getEndpoint("mock:invoice", MockEndpoint.class);
@@ -360,7 +358,7 @@ class BusinessRulesRouteTest {
}
@Test
@DisplayName("Should handle validation errors gracefully")
@DisplayName("Validasyon hatalarını zarif şekilde ele almalı")
void givenValidationError_whenProcess_thenRoutesToErrorHandler() throws Exception {
// ARRANGE
MockEndpoint mockError = camelContext.getEndpoint("mock:error", MockEndpoint.class);
@@ -373,7 +371,7 @@ class BusinessRulesRouteTest {
});
camelContext.getRouteController().startRoute("document-processing");
// Mock validator to throw exception
// Validator'ı exception fırlatacak şekilde mock'la
when(eventService.validate(any())).thenThrow(new ValidationException("Invalid document"));
// ACT
@@ -390,7 +388,7 @@ class BusinessRulesRouteTest {
}
```
## Testing Event Services
## Event Service Testi
```java
@ExtendWith(MockitoExtension.class)
@@ -416,11 +414,11 @@ class EventServiceTest {
}
@Nested
@DisplayName("Tests for createSuccessEvent")
@DisplayName("createSuccessEvent için testler")
class CreateSuccessEvent {
@Test
@DisplayName("Should create success event with correct attributes")
@DisplayName("Doğru niteliklerle başarı eventi oluşturulmalı")
void givenValidPayload_whenCreateSuccessEvent_thenEventPersisted() throws Exception {
// ARRANGE
when(objectMapper.writeValueAsString(testPayload)).thenReturn("{\"documentId\":1}");
@@ -439,7 +437,7 @@ class EventServiceTest {
}
@Test
@DisplayName("Should throw exception when payload is null")
@DisplayName("Payload null olduğunda exception fırlatılmalı")
void givenNullPayload_whenCreateSuccessEvent_thenThrowsException() {
// ARRANGE
Object nullPayload = null;
@@ -456,11 +454,11 @@ class EventServiceTest {
}
@Nested
@DisplayName("Tests for createErrorEvent")
@DisplayName("createErrorEvent için testler")
class CreateErrorEvent {
@Test
@DisplayName("Should create error event with error message")
@DisplayName("Hata mesajıyla hata eventi oluşturulmalı")
void givenError_whenCreateErrorEvent_thenEventPersistedWithMessage() throws Exception {
// ARRANGE
String errorMessage = "Processing failed";
@@ -480,7 +478,7 @@ class EventServiceTest {
}
@ParameterizedTest
@DisplayName("Should reject invalid error messages")
@DisplayName("Geçersiz hata mesajları reddedilmeli")
@ValueSource(strings = {"", " "})
void givenBlankErrorMessage_whenCreateErrorEvent_thenThrowsException(String blankMessage) {
// ACT & ASSERT
@@ -495,7 +493,7 @@ class EventServiceTest {
}
```
## Testing CompletableFuture
## CompletableFuture Testi
```java
@ExtendWith(MockitoExtension.class)
@@ -523,11 +521,11 @@ class FileStorageServiceTest {
}
@Nested
@DisplayName("Tests for uploadOriginalFile")
@DisplayName("uploadOriginalFile için testler")
class UploadOriginalFile {
@Test
@DisplayName("Should successfully upload file and return document info")
@DisplayName("Dosyayı başarıyla yüklemeli ve belge bilgisi döndürmeli")
void givenValidFile_whenUpload_thenReturnsDocumentInfo() throws Exception {
// ARRANGE
when(executorService.submit(any(Callable.class))).thenAnswer(invocation -> {
@@ -555,7 +553,7 @@ class FileStorageServiceTest {
}
@Test
@DisplayName("Should handle S3 upload failure")
@DisplayName("S3 yükleme başarısızlığını ele almalı")
void givenS3Failure_whenUpload_thenCompletableFutureFails() {
// ARRANGE
when(executorService.submit(any(Callable.class))).thenAnswer(invocation -> {
@@ -575,7 +573,7 @@ class FileStorageServiceTest {
}
@Test
@DisplayName("Should propagate LogContext to async operation")
@DisplayName("LogContext'i async işleme yaymalı")
void givenLogContext_whenUpload_thenContextPropagated() throws Exception {
// ARRANGE
AtomicReference<LogContext> capturedContext = new AtomicReference<>();
@@ -598,7 +596,7 @@ class FileStorageServiceTest {
}
```
## Resource Layer Tests (REST Assured)
## Resource Katmanı Testleri (REST Assured)
```java
@QuarkusTest
@@ -609,11 +607,11 @@ class DocumentResourceTest {
DocumentService documentService;
@Nested
@DisplayName("Tests for GET /api/documents")
@DisplayName("GET /api/documents için testler")
class ListDocuments {
@Test
@DisplayName("Should return list of documents")
@DisplayName("Belge listesini döndürmeli")
void givenDocumentsExist_whenList_thenReturnsOk() {
// ARRANGE
List<Document> documents = List.of(createDocument(1L, "DOC-001"));
@@ -630,11 +628,11 @@ class DocumentResourceTest {
}
@Nested
@DisplayName("Tests for POST /api/documents")
@DisplayName("POST /api/documents için testler")
class CreateDocument {
@Test
@DisplayName("Should create document and return 201")
@DisplayName("Belge oluşturmalı ve 201 döndürmeli")
void givenValidRequest_whenCreate_thenReturns201() {
// ARRANGE
Document document = createDocument(1L, "DOC-001");
@@ -659,7 +657,7 @@ class DocumentResourceTest {
}
@Test
@DisplayName("Should return 400 for invalid input")
@DisplayName("Geçersiz girdi için 400 döndürmeli")
void givenInvalidRequest_whenCreate_thenReturns400() {
// ACT & ASSERT
given()
@@ -686,7 +684,7 @@ class DocumentResourceTest {
}
```
## Integration Tests with Real Database
## Gerçek Veritabanıyla Entegrasyon Testleri
```java
@QuarkusTest
@@ -696,9 +694,9 @@ class DocumentIntegrationTest {
@Test
@Transactional
@DisplayName("Should create and retrieve document via API")
@DisplayName("Belge oluşturulmalı ve API üzerinden alınabilmeli")
void givenNewDocument_whenCreateAndRetrieve_thenSuccessful() {
// ACT - Create via API
// ACT - API üzerinden oluştur
Long id = given()
.contentType(ContentType.JSON)
.body("""
@@ -714,7 +712,7 @@ class DocumentIntegrationTest {
.statusCode(201)
.extract().path("id");
// ASSERT - Retrieve via API
// ASSERT - API üzerinden al
given()
.when().get("/api/documents/" + id)
.then()
@@ -724,9 +722,9 @@ class DocumentIntegrationTest {
}
```
## Coverage with JaCoCo
## JaCoCo ile Kapsam
### Maven Configuration (Complete)
### Maven Yapılandırması (Tam)
```xml
<plugin>
@@ -734,7 +732,7 @@ class DocumentIntegrationTest {
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.13</version>
<executions>
<!-- Prepare agent for test execution -->
<!-- Test yürütmesi için agent'ı hazırla -->
<execution>
<id>prepare-agent</id>
<goals>
@@ -742,7 +740,7 @@ class DocumentIntegrationTest {
</goals>
</execution>
<!-- Generate coverage report -->
<!-- Kapsam raporu oluştur -->
<execution>
<id>report</id>
<phase>verify</phase>
@@ -751,7 +749,7 @@ class DocumentIntegrationTest {
</goals>
</execution>
<!-- Enforce coverage thresholds -->
<!-- Kapsam eşiklerini zorla -->
<execution>
<id>check</id>
<goals>
@@ -781,20 +779,20 @@ class DocumentIntegrationTest {
</plugin>
```
Run tests with coverage:
Kapsam ile testleri çalıştırın:
```bash
mvn clean test
mvn jacoco:report
mvn jacoco:check
# Report at: target/site/jacoco/index.html
# Rapor: target/site/jacoco/index.html
```
## Test Dependencies
## Test Bağımlılıkları
```xml
<dependencies>
<!-- Quarkus Testing -->
<!-- Quarkus Test -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
@@ -813,7 +811,7 @@ mvn jacoco:check
<scope>test</scope>
</dependency>
<!-- AssertJ (preferred over JUnit assertions) -->
<!-- AssertJ (JUnit assertion'larına tercih edilir) -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
@@ -828,7 +826,7 @@ mvn jacoco:check
<scope>test</scope>
</dependency>
<!-- Camel Testing -->
<!-- Camel Test -->
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-junit5</artifactId>
@@ -837,74 +835,74 @@ mvn jacoco:check
</dependencies>
```
## Best Practices
## En İyi Uygulamalar
### Test Organization
- Use `@Nested` classes to group tests by method being tested
- Use `@DisplayName` for readable test descriptions visible in reports
- Follow `givenX_whenY_thenZ` naming convention for test methods
- Use `@BeforeEach` for common test data setup to reduce duplication
### Test Organizasyonu
- Testleri test edilen metoda göre gruplandırmak için `@Nested` sınıflar kullanın
- Raporlarda görünür okunabilir açıklamalar için `@DisplayName` kullanın
- Test metodları için `givenX_whenY_thenZ` isimlendirme kuralını izleyin
- Tekrarı azaltmak için ortak test verisi kurulumunda `@BeforeEach` kullanın
### Test Structure
- Follow AAA pattern with explicit comments (`// ARRANGE`, `// ACT`, `// ASSERT`)
- Use `assertDoesNotThrow` for success scenarios
- Use `assertThrows` for exception scenarios with message validation
- Verify exception messages match expected values using AssertJ `contains()` or `isEqualTo()`
### Test Yapısı
- ık yorumlarla AAA desenini izleyin (`// ARRANGE`, `// ACT`, `// ASSERT`)
- Başarı senaryoları için `assertDoesNotThrow` kullanın
- Mesaj doğrulamalı exception senaryoları için `assertThrows` kullanın
- AssertJ `contains()` veya `isEqualTo()` kullanarak exception mesajlarının beklenen değerlerle eşleştiğini doğrulayın
### Test Coverage
- Test happy paths for all public methods
- Test null input handling
- Test edge cases (empty collections, boundary values, negative IDs, blank strings)
- Test exception scenarios comprehensively
- Mock all external dependencies (repositories, services, Camel endpoints)
- Aim for 80%+ line coverage, 70%+ branch coverage
### Test Kapsamı
- Tüm public metodlar için mutlu yolları test edin
- Null girdi işlemeyi test edin
- Edge case'leri test edin (boş koleksiyonlar, sınır değerleri, negatif ID'ler, boş string'ler)
- Exception senaryolarını kapsamlı biçimde test edin
- Tüm harici bağımlılıkları mock'layın (repository'ler, servisler, Camel endpoint'leri)
- %80+ satır kapsamı, %70+ branch kapsamı hedefleyin
### Assertions
- **Always use AssertJ** (`assertThat`) instead of JUnit assertions
- Use fluent AssertJ API for readability: `assertThat(list).hasSize(3).contains(item)`
- For exceptions: `assertThatThrownBy(() -> ...).isInstanceOf(...).hasMessageContaining(...)`
- For collections: `extracting()`, `filteredOn()`, `containsExactly()`
### Assertion'lar
- JUnit assertion'ları yerine **her zaman AssertJ** (`assertThat`) kullanın
- Okunabilirlik için akıcı AssertJ API'si kullanın: `assertThat(list).hasSize(3).contains(item)`
- Exception'lar için: `assertThatThrownBy(() -> ...).isInstanceOf(...).hasMessageContaining(...)`
- Koleksiyonlar için: `extracting()`, `filteredOn()`, `containsExactly()`
### Testing Integration
- Use `@QuarkusTest` for integration tests
- Use `@InjectMock` to mock dependencies in Quarkus tests
- Prefer REST Assured for API testing
- Use `@TestProfile` for test-specific configuration
### Entegrasyon Testi
- Entegrasyon testleri için `@QuarkusTest` kullanın
- Quarkus testlerinde bağımlılıkları mock'lamak için `@InjectMock` kullanın
- API testi için REST Assured'ı tercih edin
- Test'e özel yapılandırma için `@TestProfile` kullanın
### Event-Driven Testing
- Test Camel routes with `AdviceWith` and `MockEndpoint`
- Use `@CamelQuarkusTest` annotation (if using standalone Camel tests)
- Verify message content, headers, and routing logic
- Test error handling routes separately
- Mock external systems (RabbitMQ, S3, databases) in unit tests
### Event-Driven Test
- `AdviceWith` ve `MockEndpoint` ile Camel route'larını test edin
- `@CamelQuarkusTest` annotasyonu kullanın (bağımsız Camel testleri kullanıyorsanız)
- Mesaj içeriğini, başlıklarını ve yönlendirme mantığını doğrulayın
- Hata işleme route'larını ayrı ayrı test edin
- Unit testlerde harici sistemleri (RabbitMQ, S3, veritabanları) mock'layın
### Camel Route Testing
- Use `MockEndpoint` for asserting message flow
- Use `AdviceWith` to modify routes for testing (replace endpoints with mocks)
- Test message transformation and marshalling
- Test exception handling and dead letter queues
### Camel Route Testi
- Mesaj akışını doğrulamak için `MockEndpoint` kullanın
- Test için route'ları değiştirmek üzere `AdviceWith` kullanın (endpoint'leri mock'larla değiştirin)
- Mesaj dönüşümünü ve marshalling'i test edin
- Exception işleme ve dead letter queue'ları test edin
### Testing Async Operations
- Test CompletableFuture success and failure scenarios
- Use `.join()` in tests to wait for async completion
- Test exception propagation from CompletableFuture
- Verify LogContext propagation to async operations
### Async İşlem Testi
- CompletableFuture başarı ve başarısızlık senaryolarını test edin
- Async tamamlanmayı beklemek için testlerde `.join()` kullanın
- CompletableFuture'dan exception yayılımını test edin
- LogContext yayılımını async işlemlere doğrulayın
### Performance
- Keep tests fast and isolated
- Run tests in continuous mode: `mvn quarkus:test`
- Use parameterized tests (`@ParameterizedTest`) for input variations
- Build reusable test data builders or factory methods
### Performans
- Testleri hızlı ve izole tutun
- Testleri sürekli modda çalıştırın: `mvn quarkus:test`
- Girdi varyasyonları için parametreli testler (`@ParameterizedTest`) kullanın
- Yeniden kullanılabilir test verisi builder'ları veya factory metodları oluşturun
### Quarkus-Specific
- Stay on latest LTS version (Quarkus 3.x)
- Test native compilation compatibility periodically
- Use Quarkus test profiles for different scenarios
- Leverage Quarkus dev services for local testing
- Use `@InjectMock` instead of `@MockBean` (Quarkus-specific)
### Quarkus'a Özgü
- En son LTS sürümünde kalın (Quarkus 3.x)
- Native derleme uyumluluğunu periyodik olarak test edin
- Farklı senaryolar için Quarkus test profillerini kullanın
- Yerel test için Quarkus dev servislerinden yararlanın
- `@MockBean` yerine `@InjectMock` kullanın (Quarkus'a özgü)
### Verification Best Practices
- Always verify interactions on mocked dependencies
- Use `verify(mock, never())` to ensure methods are NOT called in error scenarios
- Use `argThat()` for complex argument matching
- Verify the order of calls when it matters: `InOrder` from Mockito
### Doğrulama En İyi Uygulamaları
- Mock'lanmış bağımlılıklardaki etkileşimleri her zaman doğrulayın
- Hata senaryolarında metodların ÇAĞRILMADIĞINI sağlamak için `verify(mock, never())` kullanın
- Karmaşık argüman eşleştirme için `argThat()` kullanın
- Önem taşıdığında çağrı sırasını doğrulayın: Mockito'dan `InOrder`

View File

@@ -4,22 +4,20 @@ description: "Verification loop for Quarkus projects: build, static analysis, te
origin: ECC
---
> **Not / Note**: Bu dosya henuz Turkceye cevrilmemistir, su anda Ingilizce orijinaldir. Ceviri PR'lari memnuniyetle karsilanir.
# Quarkus Doğrulama Döngüsü
# Quarkus Verification Loop
PR'lardan önce, büyük değişikliklerden sonra ve deployment öncesi çalıştırın.
Run before PRs, after major changes, and pre-deploy.
## Ne Zaman Aktif Edilir
## When to Activate
- Quarkus servisi için pull request açmadan önce
- Büyük refactoring veya bağımlılık yükseltmelerinden sonra
- Staging veya production için deployment öncesi doğrulama
- Tam build → lint → test → güvenlik taraması → native derleme pipeline'ı çalıştırma
- Test kapsamının eşikleri karşıladığını doğrulama (%80+)
- Native image uyumluluğunu test etme
- Before opening a pull request for a Quarkus service
- After major refactoring or dependency upgrades
- Pre-deployment verification for staging or production
- Running full build → lint → test → security scan → native compilation pipeline
- Validating test coverage meets thresholds (80%+)
- Testing native image compatibility
## Phase 1: Build
## Faz 1: Build
```bash
# Maven
@@ -29,9 +27,9 @@ mvn clean verify -DskipTests
./gradlew clean assemble -x test
```
If build fails, stop and fix compilation errors.
Build başarısız olursa, durdurun ve derleme hatalarını düzeltin.
## Phase 2: Static Analysis
## Faz 2: Static Analiz
### Checkstyle, PMD, SpotBugs (Maven)
@@ -39,7 +37,7 @@ If build fails, stop and fix compilation errors.
mvn checkstyle:check pmd:check spotbugs:check
```
### SonarQube (if configured)
### SonarQube (yapılandırılmışsa)
```bash
mvn sonar:sonar \
@@ -48,33 +46,33 @@ mvn sonar:sonar \
-Dsonar.login=${SONAR_TOKEN}
```
### Common Issues to Address
### Ele Alınacak Yaygın Sorunlar
- Unused imports or variables
- Complex methods (high cyclomatic complexity)
- Potential null pointer dereferences
- Security issues flagged by SpotBugs
- Kullanılmayan import'lar veya değişkenler
- Karmaşık metodlar (yüksek cyclomatic complexity)
- Potansiyel null pointer dereference'ları
- SpotBugs tarafından işaretlenen güvenlik sorunları
## Phase 3: Tests + Coverage
## Faz 3: Testler + Kapsam
```bash
# Run all tests
# Tüm testleri çalıştır
mvn clean test
# Generate coverage report
# Kapsam raporu oluştur
mvn jacoco:report
# Enforce coverage threshold (80%)
# Kapsam eşiğini zorla (%80)
mvn jacoco:check
# Or with Gradle
# Veya Gradle ile
./gradlew test jacocoTestReport jacocoTestCoverageVerification
```
### Test Categories
### Test Kategorileri
#### Unit Tests
Test service logic with mocked dependencies:
#### Unit Testler
Mock'lanmış bağımlılıklarla servis mantığını test edin:
```java
@ExtendWith(MockitoExtension.class)
@@ -99,8 +97,8 @@ class UserServiceTest {
}
```
#### Integration Tests
Test with real database (Testcontainers):
#### Entegrasyon Testleri
Gerçek veritabanıyla (Testcontainers) test edin:
```java
@QuarkusTest
@@ -126,8 +124,8 @@ class UserRepositoryIntegrationTest {
}
```
#### API Tests
Test REST endpoints with REST Assured:
#### API Testleri
REST Assured ile REST endpoint'lerini test edin:
```java
@QuarkusTest
@@ -160,34 +158,34 @@ class UserResourceTest {
}
```
### Coverage Report
### Kapsam Raporu
Check `target/site/jacoco/index.html` for detailed coverage:
- Overall line coverage (target: 80%+)
- Branch coverage (target: 70%+)
- Identify uncovered critical paths
Ayrıntılı kapsam için `target/site/jacoco/index.html` sayfasını kontrol edin:
- Genel satır kapsamı (hedef: %80+)
- Branch kapsamı (hedef: %70+)
- Kapsanmamış kritik yolları belirleyin
## Phase 4: Security Scanning
## Faz 4: Güvenlik Taraması
### Dependency Vulnerabilities (Maven)
### Bağımlılık Güvenlik Açıkları (Maven)
```bash
mvn org.owasp:dependency-check-maven:check
```
Review `target/dependency-check-report.html` for CVEs.
CVE'ler için `target/dependency-check-report.html` raporunu inceleyin.
### Quarkus Security Audit
### Quarkus Güvenlik Denetimi
```bash
# Check vulnerable extensions
# Güvenlik açığı olan extension'ları kontrol et
mvn quarkus:audit
# List all extensions
# Tüm extension'ları listele
mvn quarkus:list-extensions
```
### OWASP ZAP (API Security Testing)
### OWASP ZAP (API Güvenlik Testi)
```bash
docker run -t owasp/zap2docker-stable zap-api-scan.py \
@@ -195,52 +193,52 @@ docker run -t owasp/zap2docker-stable zap-api-scan.py \
-f openapi
```
### Common Security Checks
### Yaygın Güvenlik Kontrolleri
- [ ] All secrets in environment variables (not in code)
- [ ] Input validation on all endpoints
- [ ] Authentication/authorization configured
- [ ] CORS properly configured
- [ ] Security headers set
- [ ] Passwords hashed with BCrypt
- [ ] SQL injection protection (parameterized queries)
- [ ] Rate limiting on public endpoints
- [ ] Tüm gizli bilgiler ortam değişkenlerinde (kodda değil)
- [ ] Tüm endpoint'lerde girdi doğrulama
- [ ] Kimlik doğrulama/yetkilendirme yapılandırılmış
- [ ] CORS düzgün yapılandırılmış
- [ ] Güvenlik başlıkları ayarlanmış
- [ ] Parolalar BCrypt ile hash'lenmiş
- [ ] SQL injection koruması (parametreli sorgular)
- [ ] Genel endpoint'lerde rate limiting
## Phase 5: Native Compilation
## Faz 5: Native Derleme
Test GraalVM native image compatibility:
GraalVM native image uyumluluğunu test edin:
```bash
# Build native executable
# Native executable oluştur
mvn package -Dnative
# Or with container
# Veya container ile
mvn package -Dnative -Dquarkus.native.container-build=true
# Test native executable
# Native executable'ı test et
./target/*-runner
# Run basic smoke tests
# Temel smoke testleri çalıştır
curl http://localhost:8080/q/health/live
curl http://localhost:8080/q/health/ready
```
### Native Image Troubleshooting
### Native Image Sorun Giderme
Common issues:
- **Reflection**: Add reflection config for dynamic classes
- **Resources**: Include resources with `quarkus.native.resources.includes`
- **JNI**: Register JNI classes if using native libraries
Yaygın sorunlar:
- **Reflection**: Dinamik sınıflar için reflection yapılandırması ekleyin
- **Resources**: `quarkus.native.resources.includes` ile kaynakları dahil edin
- **JNI**: Native kütüphaneler kullanıyorsanız JNI sınıflarını kaydedin
Example reflection config:
Örnek reflection yapılandırması:
```java
@RegisterForReflection(targets = {MyDynamicClass.class})
public class ReflectionConfiguration {}
```
## Phase 6: Performance Testing
## Faz 6: Performans Testi
### Load Testing with K6
### K6 ile Yük Testi
```javascript
// load-test.js
@@ -264,20 +262,20 @@ export default function () {
}
```
Run:
Çalıştırın:
```bash
k6 run load-test.js
```
### Metrics to Monitor
### İzlenecek Metrikler
- Response time (p50, p95, p99)
- Throughput (requests/sec)
- Error rate
- Memory usage
- CPU usage
- Yanıt süresi (p50, p95, p99)
- Throughput (istek/saniye)
- Hata oranı
- Bellek kullanımı
- CPU kullanımı
## Phase 7: Health Checks
## Faz 7: Sağlık Kontrolleri
```bash
# Liveness
@@ -286,14 +284,14 @@ curl http://localhost:8080/q/health/live
# Readiness
curl http://localhost:8080/q/health/ready
# All health checks
# Tüm sağlık kontrolleri
curl http://localhost:8080/q/health
# Metrics (if enabled)
# Metrikler (etkinleştirilmişse)
curl http://localhost:8080/q/metrics
```
Expected responses:
Beklenen yanıtlar:
```json
{
"status": "UP",
@@ -306,24 +304,24 @@ Expected responses:
}
```
## Phase 8: Container Image Build
## Faz 8: Container Image Build
```bash
# Build container image
# Container image oluştur
mvn package -Dquarkus.container-image.build=true
# Or with specific registry
# Veya belirli registry ile
mvn package \
-Dquarkus.container-image.build=true \
-Dquarkus.container-image.registry=docker.io \
-Dquarkus.container-image.group=myorg \
-Dquarkus.container-image.tag=1.0.0
# Test container
# Container'ı test et
docker run -p 8080:8080 myorg/my-quarkus-app:1.0.0
```
### Container Security Scan
### Container Güvenlik Taraması
```bash
# Trivy
@@ -333,103 +331,103 @@ trivy image myorg/my-quarkus-app:1.0.0
grype myorg/my-quarkus-app:1.0.0
```
## Phase 9: Configuration Validation
## Faz 9: Yapılandırma Doğrulama
```bash
# Check all configuration properties
# Tüm yapılandırma özelliklerini kontrol et
mvn quarkus:info
# List all config sources
# Tüm yapılandırma kaynaklarını listele
curl http://localhost:8080/q/dev/io.quarkus.quarkus-vertx-http/config
```
### Environment-Specific Checks
### Ortama Özgü Kontroller
- [ ] Database URLs configured per environment
- [ ] Secrets externalized (Vault, env vars)
- [ ] Logging levels appropriate
- [ ] CORS origins set correctly
- [ ] Rate limiting configured
- [ ] Monitoring/tracing enabled
- [ ] Veritabanı URL'leri ortam başına yapılandırılmış
- [ ] Gizli bilgiler dışsallaştırılmış (Vault, ortam değişkenleri)
- [ ] Loglama seviyeleri uygun
- [ ] CORS origin'leri doğru ayarlanmış
- [ ] Rate limiting yapılandırılmış
- [ ] İzleme/tracing etkinleştirilmiş
## Phase 10: Documentation Review
## Faz 10: Dokümantasyon İncelemesi
- [ ] OpenAPI/Swagger docs up to date (`/q/swagger-ui`)
- [ ] README has setup instructions
- [ ] API changes documented
- [ ] Migration guide for breaking changes
- [ ] Configuration properties documented
- [ ] OpenAPI/Swagger dokümanları güncel (`/q/swagger-ui`)
- [ ] README kurulum talimatlarını içeriyor
- [ ] API değişiklikleri belgelenmiş
- [ ] Breaking change'ler için migration rehberi
- [ ] Yapılandırma özellikleri belgelenmiş
Generate OpenAPI spec:
OpenAPI spec oluşturun:
```bash
curl http://localhost:8080/q/openapi -o openapi.json
```
## Verification Checklist
## Doğrulama Kontrol Listesi
### Code Quality
- [ ] Build passes without warnings
- [ ] Static analysis clean (no high/medium issues)
- [ ] Code follows team conventions
- [ ] No commented-out code or TODOs in PR
### Kod Kalitesi
- [ ] Build uyarısız geçiyor
- [ ] Static analiz temiz (yüksek/orta sorun yok)
- [ ] Kod ekip kurallarını takip ediyor
- [ ] PR'da yorum satırına alınmış kod veya TODO yok
### Testing
- [ ] All tests pass
- [ ] Code coverage ≥ 80%
- [ ] Integration tests with real database
- [ ] Security tests pass
- [ ] Performance within acceptable limits
### Test
- [ ] Tüm testler geçiyor
- [ ] Kod kapsamı%80
- [ ] Gerçek veritabanıyla entegrasyon testleri
- [ ] Güvenlik testleri geçiyor
- [ ] Performans kabul edilebilir sınırlar içinde
### Security
- [ ] No dependency vulnerabilities
- [ ] Authentication/authorization tested
- [ ] Input validation complete
- [ ] Secrets not in source code
- [ ] Security headers configured
### Güvenlik
- [ ] Bağımlılık güvenlik açığı yok
- [ ] Kimlik doğrulama/yetkilendirme test edilmiş
- [ ] Girdi doğrulama tamamlanmış
- [ ] Gizli bilgiler kaynak kodda değil
- [ ] Güvenlik başlıkları yapılandırılmış
### Deployment
- [ ] Native compilation successful
- [ ] Container image builds
- [ ] Health checks respond correctly
- [ ] Configuration valid for target environment
- [ ] Native derleme başarılı
- [ ] Container image oluşturuluyor
- [ ] Sağlık kontrolleri doğru yanıt veriyor
- [ ] Hedef ortam için yapılandırma geçerli
### Native Image
- [ ] Native executable builds
- [ ] Native tests pass
- [ ] Startup time < 100ms
- [ ] Memory footprint acceptable
- [ ] Native executable oluşturuluyor
- [ ] Native testler geçiyor
- [ ] Başlangıç süresi < 100ms
- [ ] Bellek ayak izi kabul edilebilir
## Automated Verification Script
## Otomatik Doğrulama Script'i
```bash
#!/bin/bash
set -e
echo "=== Phase 1: Build ==="
echo "=== Faz 1: Build ==="
mvn clean verify -DskipTests
echo "=== Phase 2: Static Analysis ==="
echo "=== Faz 2: Static Analiz ==="
mvn checkstyle:check pmd:check spotbugs:check
echo "=== Phase 3: Tests + Coverage ==="
echo "=== Faz 3: Testler + Kapsam ==="
mvn test jacoco:report jacoco:check
echo "=== Phase 4: Security Scan ==="
echo "=== Faz 4: Güvenlik Taraması ==="
mvn org.owasp:dependency-check-maven:check
echo "=== Phase 5: Native Compilation ==="
echo "=== Faz 5: Native Derleme ==="
mvn package -Dnative -Dquarkus.native.container-build=true
echo "=== All Phases Complete ==="
echo "Review reports:"
echo " - Coverage: target/site/jacoco/index.html"
echo " - Security: target/dependency-check-report.html"
echo "=== Tüm Fazlar Tamamlandı ==="
echo "Raporları inceleyin:"
echo " - Kapsam: target/site/jacoco/index.html"
echo " - Güvenlik: target/dependency-check-report.html"
echo " - Native: target/*-runner"
```
## CI/CD Integration
## CI/CD Entegrasyonu
### GitHub Actions Example
### GitHub Actions Örneği
```yaml
name: Verification
@@ -469,15 +467,15 @@ jobs:
files: target/site/jacoco/jacoco.xml
```
## Best Practices
## En İyi Uygulamalar
- Run verification loop before every PR
- Automate in CI/CD pipeline
- Fix issues immediately; don't accumulate debt
- Keep coverage above 80%
- Update dependencies regularly
- Test native compilation periodically
- Monitor performance trends
- Document breaking changes
- Review security scan results
- Validate configuration for each environment
- Her PR öncesi doğrulama döngüsünü çalıştırın
- CI/CD pipeline'ında otomatize edin
- Sorunları hemen düzeltin; borç biriktirmeyin
- Kapsamı %80'in üzerinde tutun
- Bağımlılıkları düzenli olarak güncelleyin
- Native derlemeyi periyodik olarak test edin
- Performans trendlerini izleyin
- Breaking change'leri belgeleyin
- Güvenlik tarama sonuçlarını inceleyin
- Her ortam için yapılandırmayı doğrulayın

View File

@@ -1,29 +1,27 @@
---
name: quarkus-patterns
description: Quarkus 3.x LTS architecture patterns with Camel for messaging, RESTful API design, CDI services, data access with Panache, and async processing. Use for Java Quarkus backend work with event-driven architectures.
description: Quarkus 3.x LTS架构模式Camel消息传递、RESTful API设计、CDI服务、Panache数据访问和异步处理。用于具有事件驱动架构的Java Quarkus后端工作。
origin: ECC
---
> **Note / 注意**: 本文件尚未翻译为中文,目前为英文原版。欢迎提交翻译 PR。
# Quarkus 开发模式
# Quarkus Development Patterns
使用Apache Camel的云原生事件驱动服务的Quarkus 3.x架构和API模式。
Quarkus 3.x architecture and API patterns for cloud-native, event-driven services with Apache Camel.
## 何时激活
## When to Activate
- 使用JAX-RS或RESTEasy Reactive构建REST API
- 构建资源 → 服务 → 仓库层结构
- 使用Apache Camel和RabbitMQ实现事件驱动模式
- 配置Hibernate Panache、缓存或响应式流
- 添加验证、异常映射或分页
- 为开发/预发布/生产环境设置配置文件YAML配置
- 使用LogContext和Logback/Logstash编码器进行自定义日志记录
- 使用CompletableFuture进行异步操作
- 实现条件流处理
- 使用GraalVM原生编译
- Building REST APIs with JAX-RS or RESTEasy Reactive
- Structuring resource → service → repository layers
- Implementing event-driven patterns with Apache Camel and RabbitMQ
- Configuring Hibernate Panache, caching, or reactive streams
- Adding validation, exception mapping, or pagination
- Setting up profiles for dev/staging/production environments (YAML config)
- Custom logging with LogContext and Logback/Logstash encoder
- Working with CompletableFuture for async operations
- Implementing conditional flow processing
- Working with GraalVM native compilation
## Service Layer with Multiple Dependencies (Lombok)
## 多依赖服务层Lombok
```java
@Slf4j
@@ -43,7 +41,7 @@ public class As2ProcessingService {
String structureIdPartner = logContext.get(As2Constants.STRUCTURE_ID);
// Conditional flow logic
// 条件流逻辑
boolean isChorusFlow = Boolean.parseBoolean(logContext.get(As2Constants.CHORUS_FLOW));
log.info("Is CHORUS_FLOW message: {}", isChorusFlow);
@@ -62,7 +60,7 @@ public class As2ProcessingService {
log.info("Invoice validation completed. Message is valid");
// CompletableFuture async operation
// CompletableFuture异步操作
try(InputStream inputStream = Files.newInputStream(filePath)) {
CompletableFuture<StoredDocumentInfo> documentInfoCompletableFuture =
fileStorageService.uploadOriginalFile(inputStream,
@@ -85,7 +83,7 @@ public class As2ProcessingService {
documentInfo, originalFileName, structureIdPartner,
flowProfile, invoiceValidationResult.getDocumentHash());
// Async Camel publishing
// 异步Camel发布
businessRulesPublisher.publishAsync(payload);
this.eventService.createSuccessEvent(payload, "BUSINESS_RULES_MESSAGE_SENT");
}
@@ -94,16 +92,16 @@ public class As2ProcessingService {
}
```
**Key Patterns:**
- `@RequiredArgsConstructor` for constructor injection via Lombok
- `@Slf4j` for Logback logging
- Scoped LogContext with try-with-resources
- Conditional flow logic based on runtime parameters
- CompletableFuture with `.join()` for async operations
- Event tracking for success/error scenarios
- Async Camel message publishing
**关键模式:**
- 通过Lombok的`@RequiredArgsConstructor`进行构造函数注入
- 通过`@Slf4j`进行Logback日志记录
- 使用try-with-resources的作用域LogContext
- 基于运行时参数的条件流逻辑
- 使用`.join()`CompletableFuture异步操作
- 成功/错误场景的事件跟踪
- 异步Camel消息发布
## Custom Logging Context Pattern (Logback)
## 自定义日志上下文模式(Logback
```java
@ApplicationScoped
@@ -112,14 +110,14 @@ public class ProcessingService {
public void processDocument(Document doc) {
LogContext logContext = CustomLog.getCurrentContext();
try (SafeAutoCloseable ignored = CustomLog.startScope(logContext)) {
// Add context to all log statements
// 向所有日志语句添加上下文
logContext.put("documentId", doc.getId().toString());
logContext.put("documentType", doc.getType());
logContext.put("userId", SecurityContext.getUserId());
log.info("Starting document processing");
// All logs within this scope inherit the context
// 此作用域内的所有日志都继承上下文
processInternal(doc);
log.info("Document processing completed");
@@ -131,7 +129,7 @@ public class ProcessingService {
}
```
**Logback Configuration (logback.xml):**
**Logback配置(logback.xml:**
```xml
<configuration>
@@ -149,7 +147,7 @@ public class ProcessingService {
</configuration>
```
## Event Service Pattern
## 事件服务模式
```java
@ApplicationScoped
@@ -181,13 +179,13 @@ public class EventService {
}
private String serializePayload(Object payload) {
// JSON serialization
// JSON序列化
return objectMapper.writeValueAsString(payload);
}
}
```
## Camel Message Publishing (RabbitMQ)
## Camel消息发布(RabbitMQ
```java
@ApplicationScoped
@@ -215,7 +213,7 @@ public class BusinessRulesPublisher {
}
```
**Camel Route Configuration:**
**Camel路由配置:**
```java
@ApplicationScoped
@@ -242,7 +240,7 @@ public class BusinessRulesRoute extends RouteBuilder {
}
```
## Camel Direct Routes (In-Memory)
## Camel Direct路由(内存中)
```java
@ApplicationScoped
@@ -250,13 +248,13 @@ public class DocumentProcessingRoute extends RouteBuilder {
@Override
public void configure() {
// Error handling
// 错误处理
onException(ValidationException.class)
.handled(true)
.to("direct:validation-error-handler")
.log("Validation error: ${exception.message}");
// Main processing route
// 主处理路由
from("direct:process-document")
.routeId("document-processing")
.log("Processing document: ${header.documentId}")
@@ -278,7 +276,7 @@ public class DocumentProcessingRoute extends RouteBuilder {
}
```
## Camel File Processing
## Camel文件处理
```java
@ApplicationScoped
@@ -308,27 +306,7 @@ public class FileMonitoringRoute extends RouteBuilder {
}
```
## Camel Bean Invocation
```java
@ApplicationScoped
public class InvoiceRoute extends RouteBuilder {
@Override
public void configure() {
from("direct:invoice-validation")
.bean(InvoiceFlowValidator.class, "validateFlowWithConfig")
.log("Validation result: ${body}");
from("direct:persist-and-publish")
.bean(DocumentJobService.class, "createDocumentAndJobEntities")
.bean(BusinessRulesPublisher.class, "publishAsync")
.bean(EventService.class, "createSuccessEvent(${body}, 'PUBLISHED')");
}
}
```
## REST API Structure
## REST API结构
```java
@Path("/api/documents")
@@ -367,7 +345,7 @@ public class DocumentResource {
}
```
## Repository Pattern (Panache Repository)
## 仓库模式(Panache Repository
```java
@ApplicationScoped
@@ -389,7 +367,7 @@ public class DocumentRepository implements PanacheRepository<Document> {
}
```
## Service Layer with Transactions
## 带事务的服务层
```java
@ApplicationScoped
@@ -416,16 +394,10 @@ public class DocumentService {
public Optional<Document> findById(Long id) {
return repo.findByIdOptional(id);
}
public PaginatedList<Document> list(int page, int size) {
return repo.findAll()
.page(page, size)
.list();
}
}
```
## DTOs and Validation
## DTO和验证
```java
public record CreateDocumentRequest(
@@ -442,7 +414,7 @@ public record DocumentResponse(Long id, String referenceNumber, DocumentStatus s
}
```
## Exception Mapping
## 异常映射
```java
@Provider
@@ -473,7 +445,7 @@ public class GenericExceptionMapper implements ExceptionMapper<Exception> {
}
```
## CompletableFuture Async Operations
## CompletableFuture异步操作
```java
@ApplicationScoped
@@ -512,7 +484,7 @@ public class FileStorageService {
}
```
## Caching
## 缓存
```java
@ApplicationScoped
@@ -533,7 +505,7 @@ public class DocumentCacheService {
}
```
## Configuration as YAML
## YAML配置
```yaml
# application.yml
@@ -580,7 +552,7 @@ public class DocumentCacheService {
username: ${RABBITMQ_USER}
password: ${RABBITMQ_PASSWORD}
# Camel configuration
# Camel配置
camel:
rabbitmq:
queue:
@@ -588,7 +560,7 @@ camel:
invoice-processing: invoice-processing-queue
```
## Health Checks
## 健康检查
```java
@Readiness
@@ -626,7 +598,7 @@ public class CamelHealthCheck implements HealthCheck {
}
```
## Dependencies (Maven)
## 依赖(Maven
```xml
<properties>
@@ -657,7 +629,7 @@ public class CamelHealthCheck implements HealthCheck {
</dependencyManagement>
<dependencies>
<!-- Quarkus Core -->
<!-- Quarkus核心 -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
@@ -667,7 +639,7 @@ public class CamelHealthCheck implements HealthCheck {
<artifactId>quarkus-config-yaml</artifactId>
</dependency>
<!-- Camel Extensions -->
<!-- Camel扩展 -->
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-spring-rabbitmq</artifactId>
@@ -689,7 +661,7 @@ public class CamelHealthCheck implements HealthCheck {
<scope>provided</scope>
</dependency>
<!-- Logging -->
<!-- 日志 -->
<dependency>
<groupId>io.quarkiverse.logging.logback</groupId>
<artifactId>quarkus-logging-logback</artifactId>
@@ -701,56 +673,56 @@ public class CamelHealthCheck implements HealthCheck {
</dependencies>
```
## Best Practices
## 最佳实践
### Architecture
- Use `@RequiredArgsConstructor` with Lombok for constructor injection
- Keep service layer thin; delegate complex logic to specialized classes
- Use Camel routes for message routing and integration patterns
- Prefer Panache Repository pattern for data access
### 架构
- 使用Lombok的`@RequiredArgsConstructor`进行构造函数注入
- 保持服务层精简,将复杂逻辑委托给专门的类
- 使用Camel路由进行消息路由和集成模式
- 数据访问优先使用Panache Repository模式
### Event-Driven
- Always track operations with EventService (success/error events)
- Use Camel `direct:` endpoints for in-memory routing
- Use `spring-rabbitmq` component for RabbitMQ integration
- Implement async publishing with `ProducerTemplate.asyncSendBody()`
### 事件驱动
- 始终使用EventService跟踪操作成功/错误事件)
- 使用Camel`direct:`端点进行内存路由
- 使用`spring-rabbitmq`组件进行RabbitMQ集成
- 使用`ProducerTemplate.asyncSendBody()`实现异步发布
### Logging
- Use Logback with Logstash encoder for structured logging
- Propagate LogContext through service calls with `SafeAutoCloseable`
- Add contextual information to LogContext for request tracing
- Use `@Slf4j` instead of manual logger instantiation
### 日志
- 使用Logstash编码器的Logback进行结构化日志
- 使用`SafeAutoCloseable`在服务调用间传播LogContext
- 向LogContext添加上下文信息以进行请求追踪
- 使用`@Slf4j`代替手动日志实例化
### Async Operations
- Use CompletableFuture for non-blocking I/O operations
- Call `.join()` when you need to wait for completion
- Handle exceptions from CompletableFuture properly
- Pass LogContext to async operations for tracing
### 异步操作
- 使用CompletableFuture进行非阻塞I/O操作
- 需要等待完成时调用`.join()`
- 正确处理CompletableFuture的异常
- 为追踪目的向异步操作传递LogContext
### Configuration
- Use YAML configuration (`quarkus-config-yaml`)
- Profile-aware configuration for dev/test/prod environments
- Externalize sensitive configuration to environment variables
- Use `@ConfigProperty` for type-safe config injection
### 配置
- 使用YAML配置`quarkus-config-yaml`
- dev/test/prod环境的配置文件感知配置
- 将敏感配置外部化到环境变量
- 使用`@ConfigProperty`进行类型安全的配置注入
### Validation
- Validate at resource layer with `@Valid`
- Use Bean Validation annotations on DTOs
- Map exceptions to proper HTTP responses with `@Provider`
### 验证
- 在资源层使用`@Valid`进行验证
- 在DTO上使用Bean Validation注解
- 使用`@Provider`将异常映射到适当的HTTP响应
### Transactions
- Use `@Transactional` on service methods that modify data
- Keep transactions short and focused
- Avoid calling async operations within transactions
### 事务
- 在修改数据的服务方法上使用`@Transactional`
- 保持事务短小且聚焦
- 避免在事务内调用异步操作
### Testing
- Use `camel-quarkus-junit5` for route testing
- Use AssertJ for assertions
- Mock all external dependencies
- Test conditional flow logic thoroughly
### 测试
- 使用`camel-quarkus-junit5`进行路由测试
- 使用AssertJ进行断言
- 模拟所有外部依赖
- 彻底测试条件流逻辑
### Quarkus-Specific
- Stay on latest LTS version (3.x)
- Use Quarkus dev mode for hot reload
- Add health checks for production readiness
- Test native compilation compatibility periodically
### Quarkus特定
- 保持最新的LTS版本3.x
- 使用Quarkus开发模式进行热重载
- 添加健康检查以确保生产就绪
- 定期测试原生编译兼容性

View File

@@ -1,32 +1,29 @@
---
name: quarkus-security
description: Quarkus Security best practices for authentication, authorization, JWT/OIDC, RBAC, input validation, CSRF, secrets management, and dependency security.
description: Quarkus安全最佳实践:认证、授权、JWT/OIDCRBAC、输入验证、CSRF、密钥管理和依赖安全。
origin: ECC
---
> **Note / 注意**: 本文件尚未翻译为中文,目前为英文原版。欢迎提交翻译 PR。
# Quarkus 安全审查
# Quarkus Security Review
使用认证、授权和输入验证保护Quarkus应用程序的最佳实践。
Best practices for securing Quarkus applications with authentication, authorization, and input validation.
## 何时激活
## When to Activate
- 添加认证JWT、OIDC、Basic Auth
- 使用@RolesAllowed或SecurityIdentity实现授权
- 验证用户输入Bean Validation、自定义验证器
- 配置CORS或安全头
- 管理密钥Vault、环境变量、配置源
- 添加速率限制或暴力破解保护
- 扫描依赖CVE
- 使用MicroProfile JWT或SmallRye JWT
- Adding authentication (JWT, OIDC, Basic Auth)
- Implementing authorization with @RolesAllowed or SecurityIdentity
- Validating user input (Bean Validation, custom validators)
- Configuring CORS or security headers
- Managing secrets (Vault, environment variables, config sources)
- Adding rate limiting or brute-force protection
- Scanning dependencies for CVEs
- Working with MicroProfile JWT or SmallRye JWT
## 认证
## Authentication
### JWT Authentication
### JWT认证
```java
// Resource protected with JWT
@Path("/api/protected")
@Authenticated
public class ProtectedResource {
@@ -50,7 +47,7 @@ public class ProtectedResource {
}
```
Configuration (application.properties):
配置(application.properties:
```properties
mp.jwt.verify.publickey.location=publicKey.pem
mp.jwt.verify.issuer=https://auth.example.com
@@ -61,7 +58,7 @@ quarkus.oidc.client-id=backend-service
quarkus.oidc.credentials.secret=${OIDC_SECRET}
```
### Custom Authentication Filter
### 自定义认证过滤器
```java
@Provider
@@ -77,7 +74,6 @@ public class CustomAuthFilter implements ContainerRequestFilter {
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
// Validate token and set SecurityIdentity
if (!validateToken(token)) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
@@ -85,15 +81,15 @@ public class CustomAuthFilter implements ContainerRequestFilter {
}
private boolean validateToken(String token) {
// Token validation logic
// 令牌验证逻辑
return true;
}
}
```
## Authorization
## 授权
### Role-Based Access Control
### 基于角色的访问控制
```java
@Path("/api/admin")
@@ -125,21 +121,17 @@ public class UserResource {
@Path("/{id}")
@RolesAllowed("USER")
public Response getUser(@PathParam("id") Long id) {
// Check ownership
// 所有权检查
if (!securityIdentity.hasRole("ADMIN") &&
!isOwner(id, securityIdentity.getPrincipal().getName())) {
return Response.status(Response.Status.FORBIDDEN).build();
}
return Response.ok(userService.findById(id)).build();
}
private boolean isOwner(Long userId, String username) {
return userService.isOwner(userId, username);
}
}
```
### Programmatic Security
### 编程式安全
```java
@ApplicationScoped
@@ -163,18 +155,18 @@ public class SecurityService {
}
```
## Input Validation
## 输入验证
### Bean Validation
```java
// BAD: No validation
// BAD: 无验证
@POST
public Response createUser(UserDto dto) {
return Response.ok(userService.create(dto)).build();
}
// GOOD: Validated DTO
// GOOD: 验证DTO
public record CreateUserDto(
@NotBlank @Size(max = 100) String name,
@NotBlank @Email String email,
@@ -190,55 +182,28 @@ public Response createUser(@Valid CreateUserDto dto) {
}
```
### Custom Validators
## SQL注入防护
### Panache Active Record默认安全
```java
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UsernameValidator.class)
public @interface ValidUsername {
String message() default "Invalid username format";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class UsernameValidator implements ConstraintValidator<ValidUsername, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) return false;
return value.matches("^[a-zA-Z0-9_-]{3,20}$");
}
}
// Usage
public record CreateUserDto(
@ValidUsername String username,
@NotBlank @Email String email
) {}
```
## SQL Injection Prevention
### Panache Active Record (Safe by Default)
```java
// GOOD: Parameterized queries with Panache
// GOOD: Panache参数化查询
List<User> users = User.list("email = ?1 and active = ?2", email, true);
Optional<User> user = User.find("username", username).firstResultOptional();
// GOOD: Named parameters
// GOOD: 命名参数
List<User> users = User.list("email = :email and age > :minAge",
Parameters.with("email", email).and("minAge", 18));
```
### Native Queries (Use Parameters)
### 原生查询(使用参数)
```java
// BAD: String concatenation
// BAD: 字符串拼接
@Query(value = "SELECT * FROM users WHERE name = '" + name + "'", nativeQuery = true)
// GOOD: Parameterized native query
// GOOD: 参数化原生查询
@Entity
public class User extends PanacheEntity {
public static List<User> findByEmailNative(String email) {
@@ -250,7 +215,7 @@ public class User extends PanacheEntity {
}
```
## Password Hashing
## 密码哈希
```java
@ApplicationScoped
@@ -264,33 +229,9 @@ public class PasswordService {
return BcryptUtil.matches(plainPassword, hashedPassword);
}
}
// In service
@ApplicationScoped
public class UserService {
@Inject
PasswordService passwordService;
@Transactional
public User register(CreateUserDto dto) {
String hashedPassword = passwordService.hash(dto.password());
User user = new User();
user.email = dto.email();
user.password = hashedPassword;
user.persist();
return user;
}
public boolean authenticate(String email, String password) {
return User.find("email", email)
.firstResultOptional()
.map(u -> passwordService.verify(password, u.password))
.orElse(false);
}
}
```
## CORS Configuration
## CORS配置
```properties
# application.properties
@@ -303,37 +244,22 @@ quarkus.http.cors.access-control-max-age=24H
quarkus.http.cors.access-control-allow-credentials=true
```
## Secrets Management
## 密钥管理
```properties
# application.properties - NO SECRETS HERE
# application.properties — 此处不放密钥
# Use environment variables
# 使用环境变量
quarkus.datasource.username=${DB_USER}
quarkus.datasource.password=${DB_PASSWORD}
quarkus.oidc.credentials.secret=${OIDC_CLIENT_SECRET}
# Or use Vault
# 或使用Vault
quarkus.vault.url=https://vault.example.com
quarkus.vault.authentication.kubernetes.role=my-role
```
### HashiCorp Vault Integration
```java
@ApplicationScoped
public class SecretService {
@ConfigProperty(name = "api-key")
String apiKey; // Fetched from Vault
public String getSecret(String key) {
return ConfigProvider.getConfig().getValue(key, String.class);
}
}
```
## Rate Limiting
## 速率限制
```java
@ApplicationScoped
@@ -344,7 +270,7 @@ public class RateLimitFilter implements ContainerRequestFilter {
public void filter(ContainerRequestContext requestContext) {
String clientId = getClientIdentifier(requestContext);
RateLimiter limiter = limiters.computeIfAbsent(clientId,
k -> RateLimiter.create(100.0)); // 100 requests per second
k -> RateLimiter.create(100.0)); // 每秒100个请求
if (!limiter.tryAcquire()) {
requestContext.abortWith(
@@ -354,15 +280,10 @@ public class RateLimitFilter implements ContainerRequestFilter {
);
}
}
private String getClientIdentifier(ContainerRequestContext ctx) {
// Use IP, API key, or user ID
return ctx.getHeaderString("X-Forwarded-For");
}
}
```
## Security Headers
## 安全头
```java
@Provider
@@ -372,10 +293,10 @@ public class SecurityHeadersFilter implements ContainerResponseFilter {
public void filter(ContainerRequestContext request, ContainerResponseContext response) {
MultivaluedMap<String, Object> headers = response.getHeaders();
// Prevent clickjacking
// 防止点击劫持
headers.putSingle("X-Frame-Options", "DENY");
// XSS protection
// XSS保护
headers.putSingle("X-Content-Type-Options", "nosniff");
headers.putSingle("X-XSS-Protection", "1; mode=block");
@@ -389,7 +310,7 @@ public class SecurityHeadersFilter implements ContainerResponseFilter {
}
```
## Audit Logging
## 审计日志
```java
@ApplicationScoped
@@ -408,23 +329,9 @@ public class AuditService {
user, action, resource, Instant.now());
}
}
// Usage in resource
@Path("/api/sensitive")
public class SensitiveResource {
@Inject
AuditService auditService;
@GET
@RolesAllowed("ADMIN")
public Response getData() {
auditService.logAccess("sensitive-data", "READ");
return Response.ok(data).build();
}
}
```
## Dependency Security Scanning
## 依赖安全扫描
```bash
# Maven
@@ -433,23 +340,23 @@ mvn org.owasp:dependency-check-maven:check
# Gradle
./gradlew dependencyCheckAnalyze
# Check Quarkus extensions
# 检查Quarkus扩展
quarkus extension list --installable
```
## Best Practices
## 最佳实践
- Always use HTTPS in production
- Enable JWT or OIDC for stateless authentication
- Use `@RolesAllowed` for declarative authorization
- Validate all input with Bean Validation
- Hash passwords with BCrypt (never plaintext)
- Store secrets in Vault or environment variables
- Use parameterized queries to prevent SQL injection
- Add security headers to all responses
- Implement rate limiting for public endpoints
- Audit sensitive operations
- Keep dependencies updated and scan for CVEs
- Use SecurityIdentity for programmatic checks
- Set appropriate CORS policies
- Test authentication and authorization paths
- 生产环境始终使用HTTPS
- 启用JWT或OIDC进行无状态认证
- 使用`@RolesAllowed`进行声明式授权
- 使用Bean Validation验证所有输入
- 使用BCrypt哈希密码禁止明文
- 将密钥存储在Vault或环境变量中
- 使用参数化查询防止SQL注入
- 为所有响应添加安全头
- 为公共端点实现速率限制
- 审计敏感操作
- 保持依赖更新并扫描CVE
- 使用SecurityIdentity进行编程式检查
- 设置适当的CORS策略
- 测试认证和授权路径

View File

@@ -1,36 +1,34 @@
---
name: quarkus-tdd
description: Test-driven development for Quarkus 3.x LTS using JUnit 5, Mockito, REST Assured, Camel testing, and JaCoCo. Use when adding features, fixing bugs, or refactoring event-driven services.
description: 使用JUnit 5MockitoREST AssuredCamel测试和JaCoCo的Quarkus 3.x LTS测试驱动开发。用于添加功能、修复错误或重构事件驱动服务。
origin: ECC
---
> **Note / 注意**: 本文件尚未翻译为中文,目前为英文原版。欢迎提交翻译 PR。
# Quarkus TDD工作流
# Quarkus TDD Workflow
面向80%以上覆盖率(单元+集成的Quarkus 3.x服务TDD指南。针对Apache Camel的事件驱动架构优化。
TDD guidance for Quarkus 3.x services with 80%+ coverage (unit + integration). Optimized for event-driven architectures with Apache Camel.
## 何时使用
## When to Use
- 新功能或REST端点
- Bug修复或重构
- 添加数据访问逻辑、安全规则或响应式流
- 测试Apache Camel路由和事件处理器
- 测试RabbitMQ事件驱动服务
- 测试条件流逻辑
- 验证CompletableFuture异步操作
- 测试LogContext传播
- New features or REST endpoints
- Bug fixes or refactors
- Adding data access logic, security rules, or reactive streams
- Testing Apache Camel routes and event handlers
- Testing event-driven services with RabbitMQ
- Testing conditional flow logic
- Validating CompletableFuture async operations
- Testing LogContext propagation
## 工作流
## Workflow
1. 先写测试(应该失败)
2. 实现通过测试的最少代码
3. 测试通过后重构
4. 使用JaCoCo强制覆盖率80%以上目标)
1. Write tests first (they should fail)
2. Implement minimal code to pass
3. Refactor with tests green
4. Enforce coverage with JaCoCo (80%+ target)
## 使用@Nested组织的单元测试
## Unit Tests with @Nested Organization
Follow this structured approach for comprehensive, readable tests:
全面、可读测试的结构化方法:
```java
@ExtendWith(MockitoExtension.class)
@@ -62,7 +60,7 @@ class As2ProcessingServiceTest {
@BeforeEach
void setUp() {
// ARRANGE - Common test data
// ARRANGE - 公共测试数据
testFilePath = Path.of("/tmp/test-invoice.xml");
testLogContext = new LogContext();
@@ -119,48 +117,9 @@ class As2ProcessingServiceTest {
verify(eventService).createSuccessEvent(any(StoredDocumentInfo.class),
eq("PERSISTENCE_BLOB_EVENT_TYPE"));
verify(eventService).createSuccessEvent(any(BusinessRulesPayload.class),
eq("BUSINESS_RULES_MESSAGE_SENT"));
verify(businessRulesPublisher).publishAsync(any(BusinessRulesPayload.class));
}
@Test
@DisplayName("Should bypass schematron validation for CHORUS_FLOW")
void givenChorusFlow_whenProcessFile_thenSchematronBypassed() throws Exception {
// ARRANGE
testLogContext.put(As2Constants.CHORUS_FLOW, "true");
CustomLog.setCurrentContext(testLogContext);
when(invoiceFlowValidator.validateFlowWithConfig(
eq(testFilePath),
eq(ValidationFlowConfig.xsdOnly()),
eq(EInvoiceSyntaxFormat.UBL),
any(LogContext.class)))
.thenReturn(validationResult);
when(fileStorageService.uploadOriginalFile(any(), anyLong(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(documentInfo));
when(documentJobService.createDocumentAndJobEntities(any(), any(), any(),
eq(FlowProfile.EXTENDED_CTC_FR), any()))
.thenReturn(new BusinessRulesPayload());
// ACT
assertDoesNotThrow(() -> as2ProcessingService.processFile(testFilePath));
// ASSERT
verify(invoiceFlowValidator).validateFlowWithConfig(
eq(testFilePath),
eq(ValidationFlowConfig.xsdOnly()),
eq(EInvoiceSyntaxFormat.UBL),
any(LogContext.class));
verify(documentJobService).createDocumentAndJobEntities(
any(), any(), any(),
eq(FlowProfile.EXTENDED_CTC_FR),
any());
}
@Test
@DisplayName("Should create error event when file upload fails")
void givenUploadFailure_whenProcessFile_thenErrorEventCreated() throws Exception {
@@ -174,7 +133,7 @@ class As2ProcessingServiceTest {
when(invoiceFlowValidator.computeFlowProfile(any(), any()))
.thenReturn(FlowProfile.BASIC);
documentInfo.setPath(""); // Blank path triggers error
documentInfo.setPath(""); // 空路径触发错误
when(fileStorageService.uploadOriginalFile(any(), anyLong(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(documentInfo));
@@ -187,71 +146,26 @@ class As2ProcessingServiceTest {
assertThat(exception.getMessage())
.contains("File path is empty after upload");
verify(eventService).createErrorEvent(
eq(documentInfo),
eq("FILE_UPLOAD_FAILED"),
contains("File path is empty"));
verify(businessRulesPublisher, never()).publishAsync(any());
}
@Test
@DisplayName("Should handle CompletableFuture.join() failure")
void givenAsyncUploadFailure_whenProcessFile_thenExceptionThrown() throws Exception {
// ARRANGE
testLogContext.put(As2Constants.CHORUS_FLOW, "false");
CustomLog.setCurrentContext(testLogContext);
when(invoiceFlowValidator.validateFlowWithConfig(any(), any(), any(), any()))
.thenReturn(validationResult);
when(invoiceFlowValidator.computeFlowProfile(any(), any()))
.thenReturn(FlowProfile.BASIC);
CompletableFuture<StoredDocumentInfo> failedFuture =
CompletableFuture.failedFuture(new StorageException("S3 connection failed"));
when(fileStorageService.uploadOriginalFile(any(), anyLong(), any(), any()))
.thenReturn(failedFuture);
// ACT & ASSERT
assertThrows(
CompletionException.class,
() -> as2ProcessingService.processFile(testFilePath)
);
}
@Test
@DisplayName("Should throw exception when file path is null")
void givenNullFilePath_whenProcessFile_thenThrowsException() {
// ARRANGE
Path nullPath = null;
// ACT & ASSERT
NullPointerException exception = assertThrows(
NullPointerException.class,
() -> as2ProcessingService.processFile(nullPath)
);
verify(invoiceFlowValidator, never()).validateFlowWithConfig(any(), any(), any(), any());
}
}
}
```
### Key Testing Patterns
### 关键测试模式
1. **@Nested Classes**: Group tests by method being tested
2. **@DisplayName**: Provide readable test descriptions for test reports
3. **Naming Convention**: `givenX_whenY_thenZ` for clarity
4. **AAA Pattern**: Explicit `// ARRANGE`, `// ACT`, `// ASSERT` comments
5. **@BeforeEach**: Setup common test data to reduce duplication
6. **assertDoesNotThrow**: Test success scenarios without catching exceptions
7. **assertThrows**: Test exception scenarios with message validation using AssertJ
8. **Comprehensive Coverage**: Test happy paths, null inputs, edge cases, exceptions
9. **Verify Interactions**: Use Mockito `verify()` to ensure methods are called correctly
10. **Never Verify**: Use `never()` to ensure methods are NOT called in error scenarios
1. **@Nested**: 按被测方法分组测试
2. **@DisplayName**: 为测试报告提供可读描述
3. **命名约定**: 使用`givenX_whenY_thenZ`确保清晰
4. **AAA模式**: 明确的`// ARRANGE``// ACT``// ASSERT`注释
5. **@BeforeEach**: 通用测试数据设置以减少重复
6. **assertDoesNotThrow**: 不捕获异常的成功场景测试
7. **assertThrows**: 带消息验证的异常场景测试
8. **全面覆盖**: 测试正常路径、null输入、边界情况、异常
9. **验证交互**: 使用Mockito`verify()`确保方法被正确调用
10. **Never验证**: 使用`never()`确保错误场景中方法未被调用
## Testing Camel Routes
## 测试Camel路由
```java
@QuarkusTest
@@ -267,338 +181,30 @@ class BusinessRulesRouteTest {
@InjectMock
EventService eventService;
private BusinessRulesPayload testPayload;
@BeforeEach
void setUp() {
// ARRANGE - Test data
testPayload = new BusinessRulesPayload();
testPayload.setDocumentId(1L);
testPayload.setFlowProfile(FlowProfile.BASIC);
}
@Nested
@DisplayName("Tests for business-rules-publisher route")
class BusinessRulesPublisher {
@Test
@DisplayName("Should successfully publish message to RabbitMQ")
void givenValidPayload_whenPublish_thenMessageSentToQueue() throws Exception {
// ARRANGE
MockEndpoint mockRabbitMQ = camelContext.getEndpoint("mock:rabbitmq", MockEndpoint.class);
mockRabbitMQ.expectedMessageCount(1);
mockRabbitMQ.expectedBodiesReceived(testPayload);
// Replace real endpoint with mock for testing
camelContext.getRouteController().stopRoute("business-rules-publisher");
AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {
advice.replaceFromWith("direct:business-rules-publisher");
advice.weaveByToString(".*spring-rabbitmq.*").replace().to("mock:rabbitmq");
});
camelContext.getRouteController().startRoute("business-rules-publisher");
// ACT
producerTemplate.sendBody("direct:business-rules-publisher", testPayload);
// ASSERT
mockRabbitMQ.assertIsSatisfied(5000);
assertThat(mockRabbitMQ.getExchanges()).hasSize(1);
assertThat(mockRabbitMQ.getExchanges().get(0).getIn().getBody(BusinessRulesPayload.class))
.isEqualTo(testPayload);
}
@Test
@DisplayName("Should handle marshalling to JSON")
void givenPayload_whenPublish_thenMarshalledToJson() throws Exception {
// ARRANGE
MockEndpoint mockMarshal = new MockEndpoint("mock:marshal");
camelContext.addEndpoint("mock:marshal", mockMarshal);
mockMarshal.expectedMessageCount(1);
camelContext.getRouteController().stopRoute("business-rules-publisher");
AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {
advice.weaveAddLast().to("mock:marshal");
});
camelContext.getRouteController().startRoute("business-rules-publisher");
// ACT
producerTemplate.sendBody("direct:business-rules-publisher", testPayload);
// ASSERT
mockMarshal.assertIsSatisfied(5000);
String body = mockMarshal.getExchanges().get(0).getIn().getBody(String.class);
assertThat(body).contains("\"documentId\":1");
assertThat(body).contains("\"flowProfile\":\"BASIC\"");
}
}
@Nested
@DisplayName("Tests for document-processing route")
class DocumentProcessing {
@Test
@DisplayName("Should route invoice to correct processor")
void givenInvoiceType_whenProcess_thenRoutesToInvoiceProcessor() throws Exception {
// ARRANGE
MockEndpoint mockInvoice = camelContext.getEndpoint("mock:invoice", MockEndpoint.class);
mockInvoice.expectedMessageCount(1);
camelContext.getRouteController().stopRoute("document-processing");
AdviceWith.adviceWith(camelContext, "document-processing", advice -> {
advice.weaveByToString(".*direct:process-invoice.*").replace().to("mock:invoice");
});
camelContext.getRouteController().startRoute("document-processing");
// ACT
producerTemplate.sendBodyAndHeader("direct:process-document",
testPayload, "documentType", "INVOICE");
// ASSERT
mockInvoice.assertIsSatisfied(5000);
}
@Test
@DisplayName("Should handle validation errors gracefully")
void givenValidationError_whenProcess_thenRoutesToErrorHandler() throws Exception {
// ARRANGE
MockEndpoint mockError = camelContext.getEndpoint("mock:error", MockEndpoint.class);
mockError.expectedMessageCount(1);
camelContext.getRouteController().stopRoute("document-processing");
AdviceWith.adviceWith(camelContext, "document-processing", advice -> {
advice.weaveByToString(".*direct:validation-error-handler.*")
.replace().to("mock:error");
});
camelContext.getRouteController().startRoute("document-processing");
// Mock validator to throw exception
when(eventService.validate(any())).thenThrow(new ValidationException("Invalid document"));
// ACT
producerTemplate.sendBody("direct:process-document", testPayload);
// ASSERT
mockError.assertIsSatisfied(5000);
Exception exception = mockError.getExchanges().get(0).getException();
assertThat(exception).isInstanceOf(ValidationException.class);
assertThat(exception.getMessage()).contains("Invalid document");
}
}
}
```
## Testing Event Services
```java
@ExtendWith(MockitoExtension.class)
@DisplayName("EventService Unit Tests")
class EventServiceTest {
@Mock
private EventRepository eventRepository;
@Mock
private ObjectMapper objectMapper;
@InjectMocks
private EventService eventService;
private BusinessRulesPayload testPayload;
@BeforeEach
void setUp() {
@Test
@DisplayName("Should successfully publish message to RabbitMQ")
void givenValidPayload_whenPublish_thenMessageSentToQueue() throws Exception {
// ARRANGE
testPayload = new BusinessRulesPayload();
testPayload.setDocumentId(1L);
}
@Nested
@DisplayName("Tests for createSuccessEvent")
class CreateSuccessEvent {
MockEndpoint mockRabbitMQ = camelContext.getEndpoint("mock:rabbitmq", MockEndpoint.class);
mockRabbitMQ.expectedMessageCount(1);
@Test
@DisplayName("Should create success event with correct attributes")
void givenValidPayload_whenCreateSuccessEvent_thenEventPersisted() throws Exception {
// ARRANGE
when(objectMapper.writeValueAsString(testPayload)).thenReturn("{\"documentId\":1}");
// ACT
assertDoesNotThrow(() ->
eventService.createSuccessEvent(testPayload, "DOCUMENT_PROCESSED"));
// ASSERT
verify(eventRepository).persist(argThat(event ->
event.getType().equals("DOCUMENT_PROCESSED") &&
event.getStatus() == EventStatus.SUCCESS &&
event.getPayload().equals("{\"documentId\":1}") &&
event.getTimestamp() != null
));
}
@Test
@DisplayName("Should throw exception when payload is null")
void givenNullPayload_whenCreateSuccessEvent_thenThrowsException() {
// ARRANGE
Object nullPayload = null;
// ACT & ASSERT
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> eventService.createSuccessEvent(nullPayload, "EVENT_TYPE")
);
assertThat(exception.getMessage()).isEqualTo("Payload cannot be null");
verify(eventRepository, never()).persist(any());
}
}
@Nested
@DisplayName("Tests for createErrorEvent")
class CreateErrorEvent {
camelContext.getRouteController().stopRoute("business-rules-publisher");
AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {
advice.replaceFromWith("direct:business-rules-publisher");
advice.weaveByToString(".*spring-rabbitmq.*").replace().to("mock:rabbitmq");
});
camelContext.getRouteController().startRoute("business-rules-publisher");
@Test
@DisplayName("Should create error event with error message")
void givenError_whenCreateErrorEvent_thenEventPersistedWithMessage() throws Exception {
// ARRANGE
String errorMessage = "Processing failed";
when(objectMapper.writeValueAsString(testPayload)).thenReturn("{\"documentId\":1}");
// ACT
assertDoesNotThrow(() ->
eventService.createErrorEvent(testPayload, "PROCESSING_ERROR", errorMessage));
// ASSERT
verify(eventRepository).persist(argThat(event ->
event.getType().equals("PROCESSING_ERROR") &&
event.getStatus() == EventStatus.ERROR &&
event.getErrorMessage().equals(errorMessage) &&
event.getPayload().equals("{\"documentId\":1}")
));
}
@ParameterizedTest
@DisplayName("Should reject invalid error messages")
@ValueSource(strings = {"", " "})
void givenBlankErrorMessage_whenCreateErrorEvent_thenThrowsException(String blankMessage) {
// ACT & ASSERT
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> eventService.createErrorEvent(testPayload, "ERROR", blankMessage)
);
assertThat(exception.getMessage()).contains("Error message cannot be blank");
}
// ACT
producerTemplate.sendBody("direct:business-rules-publisher", testPayload);
// ASSERT
mockRabbitMQ.assertIsSatisfied(5000);
}
}
```
## Testing CompletableFuture
```java
@ExtendWith(MockitoExtension.class)
@DisplayName("FileStorageService Unit Tests")
class FileStorageServiceTest {
@Mock
private S3Client s3Client;
@Mock
private ExecutorService executorService;
@InjectMocks
private FileStorageService fileStorageService;
private InputStream testInputStream;
private LogContext testLogContext;
@BeforeEach
void setUp() {
// ARRANGE
testInputStream = new ByteArrayInputStream("test content".getBytes());
testLogContext = new LogContext();
testLogContext.put("traceId", "trace-123");
}
@Nested
@DisplayName("Tests for uploadOriginalFile")
class UploadOriginalFile {
@Test
@DisplayName("Should successfully upload file and return document info")
void givenValidFile_whenUpload_thenReturnsDocumentInfo() throws Exception {
// ARRANGE
when(executorService.submit(any(Callable.class))).thenAnswer(invocation -> {
Callable<?> callable = invocation.getArgument(0);
return CompletableFuture.completedFuture(callable.call());
});
when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class)))
.thenReturn(PutObjectResponse.builder().build());
// ACT
CompletableFuture<StoredDocumentInfo> future =
fileStorageService.uploadOriginalFile(testInputStream, 1024L,
testLogContext, InvoiceFormat.UBL);
StoredDocumentInfo result = future.join();
// ASSERT
assertThat(result).isNotNull();
assertThat(result.getPath()).isNotBlank();
assertThat(result.getSize()).isEqualTo(1024L);
assertThat(result.getUploadedAt()).isNotNull();
verify(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class));
}
@Test
@DisplayName("Should handle S3 upload failure")
void givenS3Failure_whenUpload_thenCompletableFutureFails() {
// ARRANGE
when(executorService.submit(any(Callable.class))).thenAnswer(invocation -> {
return CompletableFuture.failedFuture(new StorageException("S3 unavailable"));
});
// ACT
CompletableFuture<StoredDocumentInfo> future =
fileStorageService.uploadOriginalFile(testInputStream, 1024L,
testLogContext, InvoiceFormat.UBL);
// ASSERT
assertThatThrownBy(() -> future.join())
.isInstanceOf(CompletionException.class)
.hasCauseInstanceOf(StorageException.class)
.hasMessageContaining("S3 unavailable");
}
@Test
@DisplayName("Should propagate LogContext to async operation")
void givenLogContext_whenUpload_thenContextPropagated() throws Exception {
// ARRANGE
AtomicReference<LogContext> capturedContext = new AtomicReference<>();
when(executorService.submit(any(Callable.class))).thenAnswer(invocation -> {
Callable<?> callable = invocation.getArgument(0);
capturedContext.set(CustomLog.getCurrentContext());
return CompletableFuture.completedFuture(callable.call());
});
// ACT
fileStorageService.uploadOriginalFile(testInputStream, 1024L,
testLogContext, InvoiceFormat.UBL).join();
// ASSERT
assertThat(capturedContext.get()).isNotNull();
assertThat(capturedContext.get().get("traceId")).isEqualTo("trace-123");
}
}
}
```
## Resource Layer Tests (REST Assured)
## 资源层测试REST Assured
```java
@QuarkusTest
@@ -608,27 +214,6 @@ class DocumentResourceTest {
@InjectMock
DocumentService documentService;
@Nested
@DisplayName("Tests for GET /api/documents")
class ListDocuments {
@Test
@DisplayName("Should return list of documents")
void givenDocumentsExist_whenList_thenReturnsOk() {
// ARRANGE
List<Document> documents = List.of(createDocument(1L, "DOC-001"));
when(documentService.list(0, 20)).thenReturn(documents);
// ACT & ASSERT
given()
.when().get("/api/documents")
.then()
.statusCode(200)
.body("$.size()", is(1))
.body("[0].referenceNumber", equalTo("DOC-001"));
}
}
@Nested
@DisplayName("Tests for POST /api/documents")
class CreateDocument {
@@ -654,14 +239,12 @@ class DocumentResourceTest {
.when().post("/api/documents")
.then()
.statusCode(201)
.header("Location", containsString("/api/documents/1"))
.body("referenceNumber", equalTo("DOC-001"));
}
@Test
@DisplayName("Should return 400 for invalid input")
void givenInvalidRequest_whenCreate_thenReturns400() {
// ACT & ASSERT
given()
.contentType(ContentType.JSON)
.body("""
@@ -675,58 +258,12 @@ class DocumentResourceTest {
.statusCode(400);
}
}
private Document createDocument(Long id, String referenceNumber) {
Document document = new Document();
document.setId(id);
document.setReferenceNumber(referenceNumber);
document.setStatus(DocumentStatus.PENDING);
return document;
}
}
```
## Integration Tests with Real Database
## JaCoCo覆盖率
```java
@QuarkusTest
@TestProfile(IntegrationTestProfile.class)
@DisplayName("Document Integration Tests")
class DocumentIntegrationTest {
@Test
@Transactional
@DisplayName("Should create and retrieve document via API")
void givenNewDocument_whenCreateAndRetrieve_thenSuccessful() {
// ACT - Create via API
Long id = given()
.contentType(ContentType.JSON)
.body("""
{
"referenceNumber": "INT-001",
"description": "Integration test",
"validUntil": "2030-01-01T00:00:00Z",
"categories": ["test"]
}
""")
.when().post("/api/documents")
.then()
.statusCode(201)
.extract().path("id");
// ASSERT - Retrieve via API
given()
.when().get("/api/documents/" + id)
.then()
.statusCode(200)
.body("referenceNumber", equalTo("INT-001"));
}
}
```
## Coverage with JaCoCo
### Maven Configuration (Complete)
### Maven配置
```xml
<plugin>
@@ -734,29 +271,18 @@ class DocumentIntegrationTest {
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.13</version>
<executions>
<!-- Prepare agent for test execution -->
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<goals><goal>prepare-agent</goal></goals>
</execution>
<!-- Generate coverage report -->
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
<goals><goal>report</goal></goals>
</execution>
<!-- Enforce coverage thresholds -->
<execution>
<id>check</id>
<goals>
<goal>check</goal>
</goals>
<goals><goal>check</goal></goals>
<configuration>
<rules>
<rule>
@@ -767,11 +293,6 @@ class DocumentIntegrationTest {
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.70</minimum>
</limit>
</limits>
</rule>
</rules>
@@ -781,20 +302,19 @@ class DocumentIntegrationTest {
</plugin>
```
Run tests with coverage:
运行带覆盖率的测试:
```bash
mvn clean test
mvn jacoco:report
mvn jacoco:check
# Report at: target/site/jacoco/index.html
# 报告位于: target/site/jacoco/index.html
```
## Test Dependencies
## 测试依赖
```xml
<dependencies>
<!-- Quarkus Testing -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
@@ -805,30 +325,17 @@ mvn jacoco:check
<artifactId>quarkus-junit5-mockito</artifactId>
<scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<!-- AssertJ (preferred over JUnit assertions) -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.2</version>
<scope>test</scope>
</dependency>
<!-- REST Assured -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<!-- Camel Testing -->
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-junit5</artifactId>
@@ -837,74 +344,32 @@ mvn jacoco:check
</dependencies>
```
## Best Practices
## 最佳实践
### Test Organization
- Use `@Nested` classes to group tests by method being tested
- Use `@DisplayName` for readable test descriptions visible in reports
- Follow `givenX_whenY_thenZ` naming convention for test methods
- Use `@BeforeEach` for common test data setup to reduce duplication
### 测试组织
- 使用`@Nested`类按被测方法分组
- 使用`@DisplayName`提供可读的测试描述
- 遵循`givenX_whenY_thenZ`命名约定
### Test Structure
- Follow AAA pattern with explicit comments (`// ARRANGE`, `// ACT`, `// ASSERT`)
- Use `assertDoesNotThrow` for success scenarios
- Use `assertThrows` for exception scenarios with message validation
- Verify exception messages match expected values using AssertJ `contains()` or `isEqualTo()`
### 测试结构
- 遵循带明确注释的AAA模式`// ARRANGE``// ACT``// ASSERT`
- 成功场景使用`assertDoesNotThrow`
- 异常场景使用`assertThrows`并验证消息
### Test Coverage
- Test happy paths for all public methods
- Test null input handling
- Test edge cases (empty collections, boundary values, negative IDs, blank strings)
- Test exception scenarios comprehensively
- Mock all external dependencies (repositories, services, Camel endpoints)
- Aim for 80%+ line coverage, 70%+ branch coverage
### 断言
- **始终使用AssertJ**`assertThat`代替JUnit断言
- 使用流式AssertJ API提高可读性
- 异常断言: `assertThatThrownBy(() -> ...).isInstanceOf(...).hasMessageContaining(...)`
### Assertions
- **Always use AssertJ** (`assertThat`) instead of JUnit assertions
- Use fluent AssertJ API for readability: `assertThat(list).hasSize(3).contains(item)`
- For exceptions: `assertThatThrownBy(() -> ...).isInstanceOf(...).hasMessageContaining(...)`
- For collections: `extracting()`, `filteredOn()`, `containsExactly()`
### 事件驱动测试
- 使用`AdviceWith``MockEndpoint`测试Camel路由
- 验证消息内容、头部和路由逻辑
- 单独测试错误处理路由
- 单元测试中模拟外部系统RabbitMQ、S3、数据库
### Testing Integration
- Use `@QuarkusTest` for integration tests
- Use `@InjectMock` to mock dependencies in Quarkus tests
- Prefer REST Assured for API testing
- Use `@TestProfile` for test-specific configuration
### Quarkus特定
- 保持最新的LTS版本Quarkus 3.x
- 使用Quarkus测试配置文件处理不同场景
- 使用`@InjectMock`代替`@MockBean`Quarkus特定
### Event-Driven Testing
- Test Camel routes with `AdviceWith` and `MockEndpoint`
- Use `@CamelQuarkusTest` annotation (if using standalone Camel tests)
- Verify message content, headers, and routing logic
- Test error handling routes separately
- Mock external systems (RabbitMQ, S3, databases) in unit tests
### Camel Route Testing
- Use `MockEndpoint` for asserting message flow
- Use `AdviceWith` to modify routes for testing (replace endpoints with mocks)
- Test message transformation and marshalling
- Test exception handling and dead letter queues
### Testing Async Operations
- Test CompletableFuture success and failure scenarios
- Use `.join()` in tests to wait for async completion
- Test exception propagation from CompletableFuture
- Verify LogContext propagation to async operations
### Performance
- Keep tests fast and isolated
- Run tests in continuous mode: `mvn quarkus:test`
- Use parameterized tests (`@ParameterizedTest`) for input variations
- Build reusable test data builders or factory methods
### Quarkus-Specific
- Stay on latest LTS version (Quarkus 3.x)
- Test native compilation compatibility periodically
- Use Quarkus test profiles for different scenarios
- Leverage Quarkus dev services for local testing
- Use `@InjectMock` instead of `@MockBean` (Quarkus-specific)
### Verification Best Practices
- Always verify interactions on mocked dependencies
- Use `verify(mock, never())` to ensure methods are NOT called in error scenarios
- Use `argThat()` for complex argument matching
- Verify the order of calls when it matters: `InOrder` from Mockito
**请记住**: 保持测试快速、隔离和确定性。测试行为而非实现细节。

View File

@@ -1,25 +1,23 @@
---
name: quarkus-verification
description: "Verification loop for Quarkus projects: build, static analysis, tests with coverage, security scans, native compilation, and diff review before release or PR."
description: "Quarkus项目验证循环构建、静态分析、带覆盖率的测试、安全扫描、原生编译以及发布或PR前的diff审查。"
origin: ECC
---
> **Note / 注意**: 本文件尚未翻译为中文,目前为英文原版。欢迎提交翻译 PR。
# Quarkus 验证循环
# Quarkus Verification Loop
在PR前、重大变更后和部署前运行。
Run before PRs, after major changes, and pre-deploy.
## 何时激活
## When to Activate
- 为Quarkus服务打开PR前
- 大规模重构或依赖升级后
- 预发布或生产的部署前验证
- 运行完整的构建 → lint → 测试 → 安全扫描 → 原生编译流水线
- 验证测试覆盖率达到阈值80%+
- 测试原生镜像兼容性
- Before opening a pull request for a Quarkus service
- After major refactoring or dependency upgrades
- Pre-deployment verification for staging or production
- Running full build → lint → test → security scan → native compilation pipeline
- Validating test coverage meets thresholds (80%+)
- Testing native image compatibility
## Phase 1: Build
## 阶段1: 构建
```bash
# Maven
@@ -29,17 +27,17 @@ mvn clean verify -DskipTests
./gradlew clean assemble -x test
```
If build fails, stop and fix compilation errors.
构建失败时,停止并修复编译错误。
## Phase 2: Static Analysis
## 阶段2: 静态分析
### Checkstyle, PMD, SpotBugs (Maven)
### CheckstylePMDSpotBugsMaven
```bash
mvn checkstyle:check pmd:check spotbugs:check
```
### SonarQube (if configured)
### SonarQube(如已配置)
```bash
mvn sonar:sonar \
@@ -48,33 +46,33 @@ mvn sonar:sonar \
-Dsonar.login=${SONAR_TOKEN}
```
### Common Issues to Address
### 需要解决的常见问题
- Unused imports or variables
- Complex methods (high cyclomatic complexity)
- Potential null pointer dereferences
- Security issues flagged by SpotBugs
- 未使用的导入或变量
- 复杂方法(高圈复杂度)
- 潜在的空指针解引用
- SpotBugs标记的安全问题
## Phase 3: Tests + Coverage
## 阶段3: 测试 + 覆盖率
```bash
# Run all tests
# 运行所有测试
mvn clean test
# Generate coverage report
# 生成覆盖率报告
mvn jacoco:report
# Enforce coverage threshold (80%)
# 强制覆盖率阈值(80%
mvn jacoco:check
# Or with Gradle
# 或使用Gradle
./gradlew test jacocoTestReport jacocoTestCoverageVerification
```
### Test Categories
### 测试类别
#### Unit Tests
Test service logic with mocked dependencies:
#### 单元测试
使用模拟依赖测试服务逻辑:
```java
@ExtendWith(MockitoExtension.class)
@@ -99,8 +97,8 @@ class UserServiceTest {
}
```
#### Integration Tests
Test with real database (Testcontainers):
#### 集成测试
使用真实数据库(Testcontainers)测试:
```java
@QuarkusTest
@@ -126,8 +124,8 @@ class UserRepositoryIntegrationTest {
}
```
#### API Tests
Test REST endpoints with REST Assured:
#### API测试
使用REST Assured测试REST端点:
```java
@QuarkusTest
@@ -160,324 +158,157 @@ class UserResourceTest {
}
```
### Coverage Report
### 覆盖率报告
Check `target/site/jacoco/index.html` for detailed coverage:
- Overall line coverage (target: 80%+)
- Branch coverage (target: 70%+)
- Identify uncovered critical paths
检查`target/site/jacoco/index.html`获取详细覆盖率:
- 总体行覆盖率(目标: 80%+
- 分支覆盖率(目标: 70%+
- 识别未覆盖的关键路径
## Phase 4: Security Scanning
## 阶段4: 安全扫描
### Dependency Vulnerabilities (Maven)
### 依赖漏洞(Maven
```bash
mvn org.owasp:dependency-check-maven:check
```
Review `target/dependency-check-report.html` for CVEs.
查看`target/dependency-check-report.html`中的CVE
### Quarkus Security Audit
### Quarkus安全审计
```bash
# Check vulnerable extensions
# 检查有漏洞的扩展
mvn quarkus:audit
# List all extensions
# 列出所有扩展
mvn quarkus:list-extensions
```
### OWASP ZAP (API Security Testing)
### 常见安全检查
- [ ] 所有密钥在环境变量中(不在代码中)
- [ ] 所有端点有输入验证
- [ ] 认证/授权已配置
- [ ] CORS正确配置
- [ ] 安全头已设置
- [ ] 密码使用BCrypt哈希
- [ ] SQL注入保护参数化查询
- [ ] 公共端点有速率限制
## 阶段5: 原生编译
测试GraalVM原生镜像兼容性:
```bash
docker run -t owasp/zap2docker-stable zap-api-scan.py \
-t http://localhost:8080/q/openapi \
-f openapi
```
### Common Security Checks
- [ ] All secrets in environment variables (not in code)
- [ ] Input validation on all endpoints
- [ ] Authentication/authorization configured
- [ ] CORS properly configured
- [ ] Security headers set
- [ ] Passwords hashed with BCrypt
- [ ] SQL injection protection (parameterized queries)
- [ ] Rate limiting on public endpoints
## Phase 5: Native Compilation
Test GraalVM native image compatibility:
```bash
# Build native executable
# 构建原生可执行文件
mvn package -Dnative
# Or with container
# 或使用容器
mvn package -Dnative -Dquarkus.native.container-build=true
# Test native executable
# 测试原生可执行文件
./target/*-runner
# Run basic smoke tests
# 运行基本冒烟测试
curl http://localhost:8080/q/health/live
curl http://localhost:8080/q/health/ready
```
### Native Image Troubleshooting
### 原生镜像故障排除
Common issues:
- **Reflection**: Add reflection config for dynamic classes
- **Resources**: Include resources with `quarkus.native.resources.includes`
- **JNI**: Register JNI classes if using native libraries
常见问题:
- **Reflection**: 为动态类添加反射配置
- **Resources**: 使用`quarkus.native.resources.includes`包含资源
- **JNI**: 使用原生库时注册JNI类
Example reflection config:
反射配置示例:
```java
@RegisterForReflection(targets = {MyDynamicClass.class})
public class ReflectionConfiguration {}
```
## Phase 6: Performance Testing
### Load Testing with K6
```javascript
// load-test.js
import http from 'k6/http';
import { check } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 50 },
{ duration: '1m', target: 100 },
{ duration: '30s', target: 0 },
],
};
export default function () {
const res = http.get('http://localhost:8080/api/markets');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 200ms': (r) => r.timings.duration < 200,
});
}
```
Run:
```bash
k6 run load-test.js
```
### Metrics to Monitor
- Response time (p50, p95, p99)
- Throughput (requests/sec)
- Error rate
- Memory usage
- CPU usage
## Phase 7: Health Checks
## 阶段6: 健康检查
```bash
# Liveness
# 存活检查
curl http://localhost:8080/q/health/live
# Readiness
# 就绪检查
curl http://localhost:8080/q/health/ready
# All health checks
# 所有健康检查
curl http://localhost:8080/q/health
# Metrics (if enabled)
# 指标(如启用)
curl http://localhost:8080/q/metrics
```
Expected responses:
```json
{
"status": "UP",
"checks": [
{
"name": "Database connection",
"status": "UP"
}
]
}
```
## 验证清单
## Phase 8: Container Image Build
### 代码质量
- [ ] 构建无警告通过
- [ ] 静态分析干净(无高/中问题)
- [ ] 代码遵循团队规范
- [ ] PR中无注释代码或TODO
```bash
# Build container image
mvn package -Dquarkus.container-image.build=true
### 测试
- [ ] 所有测试通过
- [ ] 代码覆盖率 ≥ 80%
- [ ] 使用真实数据库的集成测试
- [ ] 安全测试通过
- [ ] 性能在可接受范围内
# Or with specific registry
mvn package \
-Dquarkus.container-image.build=true \
-Dquarkus.container-image.registry=docker.io \
-Dquarkus.container-image.group=myorg \
-Dquarkus.container-image.tag=1.0.0
### 安全
- [ ] 无依赖漏洞
- [ ] 认证/授权已测试
- [ ] 输入验证完成
- [ ] 源代码中无密钥
- [ ] 安全头已配置
# Test container
docker run -p 8080:8080 myorg/my-quarkus-app:1.0.0
```
### 部署
- [ ] 原生编译成功
- [ ] 容器镜像可构建
- [ ] 健康检查正确响应
- [ ] 目标环境配置有效
### Container Security Scan
```bash
# Trivy
trivy image myorg/my-quarkus-app:1.0.0
# Grype
grype myorg/my-quarkus-app:1.0.0
```
## Phase 9: Configuration Validation
```bash
# Check all configuration properties
mvn quarkus:info
# List all config sources
curl http://localhost:8080/q/dev/io.quarkus.quarkus-vertx-http/config
```
### Environment-Specific Checks
- [ ] Database URLs configured per environment
- [ ] Secrets externalized (Vault, env vars)
- [ ] Logging levels appropriate
- [ ] CORS origins set correctly
- [ ] Rate limiting configured
- [ ] Monitoring/tracing enabled
## Phase 10: Documentation Review
- [ ] OpenAPI/Swagger docs up to date (`/q/swagger-ui`)
- [ ] README has setup instructions
- [ ] API changes documented
- [ ] Migration guide for breaking changes
- [ ] Configuration properties documented
Generate OpenAPI spec:
```bash
curl http://localhost:8080/q/openapi -o openapi.json
```
## Verification Checklist
### Code Quality
- [ ] Build passes without warnings
- [ ] Static analysis clean (no high/medium issues)
- [ ] Code follows team conventions
- [ ] No commented-out code or TODOs in PR
### Testing
- [ ] All tests pass
- [ ] Code coverage ≥ 80%
- [ ] Integration tests with real database
- [ ] Security tests pass
- [ ] Performance within acceptable limits
### Security
- [ ] No dependency vulnerabilities
- [ ] Authentication/authorization tested
- [ ] Input validation complete
- [ ] Secrets not in source code
- [ ] Security headers configured
### Deployment
- [ ] Native compilation successful
- [ ] Container image builds
- [ ] Health checks respond correctly
- [ ] Configuration valid for target environment
### Native Image
- [ ] Native executable builds
- [ ] Native tests pass
- [ ] Startup time < 100ms
- [ ] Memory footprint acceptable
## Automated Verification Script
## 自动化验证脚本
```bash
#!/bin/bash
set -e
echo "=== Phase 1: Build ==="
echo "=== 阶段1: 构建 ==="
mvn clean verify -DskipTests
echo "=== Phase 2: Static Analysis ==="
echo "=== 阶段2: 静态分析 ==="
mvn checkstyle:check pmd:check spotbugs:check
echo "=== Phase 3: Tests + Coverage ==="
echo "=== 阶段3: 测试 + 覆盖率 ==="
mvn test jacoco:report jacoco:check
echo "=== Phase 4: Security Scan ==="
echo "=== 阶段4: 安全扫描 ==="
mvn org.owasp:dependency-check-maven:check
echo "=== Phase 5: Native Compilation ==="
echo "=== 阶段5: 原生编译 ==="
mvn package -Dnative -Dquarkus.native.container-build=true
echo "=== All Phases Complete ==="
echo "Review reports:"
echo " - Coverage: target/site/jacoco/index.html"
echo " - Security: target/dependency-check-report.html"
echo " - Native: target/*-runner"
echo "=== 所有阶段完成 ==="
echo "查看报告:"
echo " - 覆盖率: target/site/jacoco/index.html"
echo " - 安全: target/dependency-check-report.html"
echo " - 原生: target/*-runner"
```
## CI/CD Integration
## 最佳实践
### GitHub Actions Example
```yaml
name: Verification
on: [push, pull_request]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
- name: Build
run: mvn clean verify -DskipTests
- name: Test with Coverage
run: mvn test jacoco:report jacoco:check
- name: Security Scan
run: mvn org.owasp:dependency-check-maven:check
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
files: target/site/jacoco/jacoco.xml
```
## Best Practices
- Run verification loop before every PR
- Automate in CI/CD pipeline
- Fix issues immediately; don't accumulate debt
- Keep coverage above 80%
- Update dependencies regularly
- Test native compilation periodically
- Monitor performance trends
- Document breaking changes
- Review security scan results
- Validate configuration for each environment
- 每次PR前运行验证循环
- 在CI/CD流水线中自动化
- 立即修复问题,不积累技术债务
- 保持覆盖率在80%以上
- 定期更新依赖
- 定期测试原生编译
- 监控性能趋势
- 记录破坏性变更
- 审查安全扫描结果
- 验证每个环境的配置