diff --git a/docs/ja-JP/skills/quarkus-patterns/SKILL.md b/docs/ja-JP/skills/quarkus-patterns/SKILL.md index a3f393ed..3e521aa8 100644 --- a/docs/ja-JP/skills/quarkus-patterns/SKILL.md +++ b/docs/ja-JP/skills/quarkus-patterns/SKILL.md @@ -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 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 @@ -149,7 +147,7 @@ public class ProcessingService { ``` -## 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 { } ``` -## 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 { } ``` -## 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 @@ -657,7 +655,7 @@ public class CamelHealthCheck implements HealthCheck { - + io.quarkus quarkus-arc @@ -667,7 +665,7 @@ public class CamelHealthCheck implements HealthCheck { quarkus-config-yaml - + org.apache.camel.quarkus camel-quarkus-spring-rabbitmq @@ -689,7 +687,7 @@ public class CamelHealthCheck implements HealthCheck { provided - + io.quarkiverse.logging.logback quarkus-logging-logback @@ -701,56 +699,56 @@ public class CamelHealthCheck implements HealthCheck { ``` -## 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モードを使用 +- 本番準備のためにヘルスチェックを追加 +- ネイティブコンパイル互換性を定期的にテスト diff --git a/docs/ja-JP/skills/quarkus-security/SKILL.md b/docs/ja-JP/skills/quarkus-security/SKILL.md index 6061bf72..6d3248b7 100644 --- a/docs/ja-JP/skills/quarkus-security/SKILL.md +++ b/docs/ja-JP/skills/quarkus-security/SKILL.md @@ -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/OIDC、RBAC、入力バリデーション、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 users = User.list("email = ?1 and active = ?2", email, true); Optional user = User.find("username", username).firstResultOptional(); -// GOOD: Named parameters +// GOOD: 名前付きパラメータ List 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 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 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ポリシーを設定 +- 認証・認可パスをテスト diff --git a/docs/ja-JP/skills/quarkus-tdd/SKILL.md b/docs/ja-JP/skills/quarkus-tdd/SKILL.md index 47f55508..8bfacb40 100644 --- a/docs/ja-JP/skills/quarkus-tdd/SKILL.md +++ b/docs/ja-JP/skills/quarkus-tdd/SKILL.md @@ -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 5、Mockito、REST Assured、Camelテスト、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 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 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 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 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 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 @@ -734,29 +271,18 @@ class DocumentIntegrationTest { jacoco-maven-plugin 0.8.13 - prepare-agent - - prepare-agent - + prepare-agent - - report verify - - report - + report - - check - - check - + check @@ -767,11 +293,6 @@ class DocumentIntegrationTest { COVEREDRATIO 0.80 - - BRANCH - COVEREDRATIO - 0.70 - @@ -781,20 +302,19 @@ class DocumentIntegrationTest { ``` -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 - io.quarkus quarkus-junit5 @@ -805,30 +325,17 @@ mvn jacoco:check quarkus-junit5-mockito test - - - - org.mockito - mockito-core - test - - - org.assertj assertj-core 3.24.2 test - - io.rest-assured rest-assured test - - org.apache.camel.quarkus camel-quarkus-junit5 @@ -837,74 +344,33 @@ mvn jacoco:check ``` -## 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 +**覚えておいてください**: テストは高速、分離、決定的に保ちます。実装の詳細ではなく動作をテストしてください。 diff --git a/docs/ja-JP/skills/quarkus-verification/SKILL.md b/docs/ja-JP/skills/quarkus-verification/SKILL.md index 2f98744d..f6330388 100644 --- a/docs/ja-JP/skills/quarkus-verification/SKILL.md +++ b/docs/ja-JP/skills/quarkus-verification/SKILL.md @@ -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) +### Checkstyle、PMD、SpotBugs(Maven) ```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%以上に維持 +- 依存関係を定期的に更新 +- ネイティブコンパイルを定期的にテスト +- パフォーマンストレンドを監視 +- 破壊的変更を文書化 +- セキュリティスキャン結果をレビュー +- 各環境の構成を検証 diff --git a/docs/tr/skills/quarkus-patterns/SKILL.md b/docs/tr/skills/quarkus-patterns/SKILL.md index 92a0a182..a209680c 100644 --- a/docs/tr/skills/quarkus-patterns/SKILL.md +++ b/docs/tr/skills/quarkus-patterns/SKILL.md @@ -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 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 @@ -149,7 +147,7 @@ public class ProcessingService { ``` -## 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 { } ``` -## 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 { } ``` -## 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 @@ -657,7 +655,7 @@ public class CamelHealthCheck implements HealthCheck { - + io.quarkus quarkus-arc @@ -667,7 +665,7 @@ public class CamelHealthCheck implements HealthCheck { quarkus-config-yaml - + org.apache.camel.quarkus camel-quarkus-spring-rabbitmq @@ -689,7 +687,7 @@ public class CamelHealthCheck implements HealthCheck { provided - + io.quarkiverse.logging.logback quarkus-logging-logback @@ -701,56 +699,56 @@ public class CamelHealthCheck implements HealthCheck { ``` -## 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 diff --git a/docs/tr/skills/quarkus-security/SKILL.md b/docs/tr/skills/quarkus-security/SKILL.md index 4f3f87b0..67325b41 100644 --- a/docs/tr/skills/quarkus-security/SKILL.md +++ b/docs/tr/skills/quarkus-security/SKILL.md @@ -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 users = User.list("email = ?1 and active = ?2", email, true); Optional user = User.find("username", username).firstResultOptional(); -// GOOD: Named parameters +// İYİ: İsimlendirilmiş parametreler List 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 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 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 diff --git a/docs/tr/skills/quarkus-tdd/SKILL.md b/docs/tr/skills/quarkus-tdd/SKILL.md index dd1661e0..d50c1876 100644 --- a/docs/tr/skills/quarkus-tdd/SKILL.md +++ b/docs/tr/skills/quarkus-tdd/SKILL.md @@ -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(""); // Boş 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 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 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 @@ -734,7 +732,7 @@ class DocumentIntegrationTest { jacoco-maven-plugin 0.8.13 - + prepare-agent @@ -742,7 +740,7 @@ class DocumentIntegrationTest { - + report verify @@ -751,7 +749,7 @@ class DocumentIntegrationTest { - + check @@ -781,20 +779,20 @@ class DocumentIntegrationTest { ``` -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 - + io.quarkus quarkus-junit5 @@ -813,7 +811,7 @@ mvn jacoco:check test - + org.assertj assertj-core @@ -828,7 +826,7 @@ mvn jacoco:check test - + org.apache.camel.quarkus camel-quarkus-junit5 @@ -837,74 +835,74 @@ mvn jacoco:check ``` -## 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ı +- Açı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` diff --git a/docs/tr/skills/quarkus-verification/SKILL.md b/docs/tr/skills/quarkus-verification/SKILL.md index e30833b8..1a840745 100644 --- a/docs/tr/skills/quarkus-verification/SKILL.md +++ b/docs/tr/skills/quarkus-verification/SKILL.md @@ -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 diff --git a/docs/zh-CN/skills/quarkus-patterns/SKILL.md b/docs/zh-CN/skills/quarkus-patterns/SKILL.md index 1ce2a9a4..735cded0 100644 --- a/docs/zh-CN/skills/quarkus-patterns/SKILL.md +++ b/docs/zh-CN/skills/quarkus-patterns/SKILL.md @@ -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 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 @@ -149,7 +147,7 @@ public class ProcessingService { ``` -## 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 { } ``` -## Service Layer with Transactions +## 带事务的服务层 ```java @ApplicationScoped @@ -416,16 +394,10 @@ public class DocumentService { public Optional findById(Long id) { return repo.findByIdOptional(id); } - - public PaginatedList 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 { } ``` -## 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 @@ -657,7 +629,7 @@ public class CamelHealthCheck implements HealthCheck { - + io.quarkus quarkus-arc @@ -667,7 +639,7 @@ public class CamelHealthCheck implements HealthCheck { quarkus-config-yaml - + org.apache.camel.quarkus camel-quarkus-spring-rabbitmq @@ -689,7 +661,7 @@ public class CamelHealthCheck implements HealthCheck { provided - + io.quarkiverse.logging.logback quarkus-logging-logback @@ -701,56 +673,56 @@ public class CamelHealthCheck implements HealthCheck { ``` -## 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开发模式进行热重载 +- 添加健康检查以确保生产就绪 +- 定期测试原生编译兼容性 diff --git a/docs/zh-CN/skills/quarkus-security/SKILL.md b/docs/zh-CN/skills/quarkus-security/SKILL.md index 8d4e2792..ce90e424 100644 --- a/docs/zh-CN/skills/quarkus-security/SKILL.md +++ b/docs/zh-CN/skills/quarkus-security/SKILL.md @@ -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/OIDC、RBAC、输入验证、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[] payload() default {}; -} - -public class UsernameValidator implements ConstraintValidator { - @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 users = User.list("email = ?1 and active = ?2", email, true); Optional user = User.find("username", username).firstResultOptional(); -// GOOD: Named parameters +// GOOD: 命名参数 List 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 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 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策略 +- 测试认证和授权路径 diff --git a/docs/zh-CN/skills/quarkus-tdd/SKILL.md b/docs/zh-CN/skills/quarkus-tdd/SKILL.md index 3ece140a..4afc3bb2 100644 --- a/docs/zh-CN/skills/quarkus-tdd/SKILL.md +++ b/docs/zh-CN/skills/quarkus-tdd/SKILL.md @@ -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 5、Mockito、REST Assured、Camel测试和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 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 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 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 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 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 @@ -734,29 +271,18 @@ class DocumentIntegrationTest { jacoco-maven-plugin 0.8.13 - prepare-agent - - prepare-agent - + prepare-agent - - report verify - - report - + report - - check - - check - + check @@ -767,11 +293,6 @@ class DocumentIntegrationTest { COVEREDRATIO 0.80 - - BRANCH - COVEREDRATIO - 0.70 - @@ -781,20 +302,19 @@ class DocumentIntegrationTest { ``` -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 - io.quarkus quarkus-junit5 @@ -805,30 +325,17 @@ mvn jacoco:check quarkus-junit5-mockito test - - - - org.mockito - mockito-core - test - - - org.assertj assertj-core 3.24.2 test - - io.rest-assured rest-assured test - - org.apache.camel.quarkus camel-quarkus-junit5 @@ -837,74 +344,32 @@ mvn jacoco:check ``` -## 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 +**请记住**: 保持测试快速、隔离和确定性。测试行为而非实现细节。 diff --git a/docs/zh-CN/skills/quarkus-verification/SKILL.md b/docs/zh-CN/skills/quarkus-verification/SKILL.md index 02e20e9e..60ed9945 100644 --- a/docs/zh-CN/skills/quarkus-verification/SKILL.md +++ b/docs/zh-CN/skills/quarkus-verification/SKILL.md @@ -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) +### Checkstyle、PMD、SpotBugs(Maven) ```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%以上 +- 定期更新依赖 +- 定期测试原生编译 +- 监控性能趋势 +- 记录破坏性变更 +- 审查安全扫描结果 +- 验证每个环境的配置