@heytherevibin/skillforge 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/CODE_OF_CONDUCT.md +34 -0
- package/CONTRIBUTING.md +38 -0
- package/LICENSE +21 -0
- package/README.md +337 -0
- package/RELEASING.md +93 -0
- package/SECURITY.md +31 -0
- package/STRATEGY.md +26 -0
- package/bin/cli.js +547 -0
- package/lib/packs.js +184 -0
- package/package.json +38 -0
- package/python/app/__init__.py +0 -0
- package/python/app/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/app/__pycache__/auth.cpython-312.pyc +0 -0
- package/python/app/__pycache__/main.cpython-312.pyc +0 -0
- package/python/app/auth.py +63 -0
- package/python/app/cli.py +78 -0
- package/python/app/db_paths.py +26 -0
- package/python/app/events_cli.py +175 -0
- package/python/app/main.py +647 -0
- package/python/app/materialize.py +138 -0
- package/python/app/mcp_server.py +610 -0
- package/python/app/route_cli.py +117 -0
- package/python/requirements-dev.txt +1 -0
- package/python/requirements.txt +7 -0
- package/python/tests/test_db_paths.py +41 -0
- package/skills/accessibility/SKILL.md +145 -0
- package/skills/agent-architecture-audit/SKILL.md +256 -0
- package/skills/agent-eval/SKILL.md +144 -0
- package/skills/agent-harness-construction/SKILL.md +72 -0
- package/skills/agent-introspection-debugging/SKILL.md +152 -0
- package/skills/agent-payment-x402/SKILL.md +224 -0
- package/skills/agent-sort/SKILL.md +214 -0
- package/skills/agentic-engineering/SKILL.md +62 -0
- package/skills/agentic-os/SKILL.md +386 -0
- package/skills/ai-first-engineering/SKILL.md +50 -0
- package/skills/ai-regression-testing/SKILL.md +384 -0
- package/skills/android-clean-architecture/SKILL.md +338 -0
- package/skills/angular-developer/SKILL.md +153 -0
- package/skills/angular-developer/references/angular-animations.md +160 -0
- package/skills/angular-developer/references/angular-aria.md +410 -0
- package/skills/angular-developer/references/cli.md +86 -0
- package/skills/angular-developer/references/component-harnesses.md +59 -0
- package/skills/angular-developer/references/component-styling.md +91 -0
- package/skills/angular-developer/references/components.md +117 -0
- package/skills/angular-developer/references/creating-services.md +97 -0
- package/skills/angular-developer/references/data-resolvers.md +69 -0
- package/skills/angular-developer/references/define-routes.md +67 -0
- package/skills/angular-developer/references/defining-providers.md +72 -0
- package/skills/angular-developer/references/di-fundamentals.md +120 -0
- package/skills/angular-developer/references/e2e-testing.md +56 -0
- package/skills/angular-developer/references/effects.md +83 -0
- package/skills/angular-developer/references/hierarchical-injectors.md +43 -0
- package/skills/angular-developer/references/host-elements.md +80 -0
- package/skills/angular-developer/references/injection-context.md +63 -0
- package/skills/angular-developer/references/inputs.md +101 -0
- package/skills/angular-developer/references/linked-signal.md +59 -0
- package/skills/angular-developer/references/loading-strategies.md +61 -0
- package/skills/angular-developer/references/mcp.md +108 -0
- package/skills/angular-developer/references/navigate-to-routes.md +69 -0
- package/skills/angular-developer/references/outputs.md +86 -0
- package/skills/angular-developer/references/reactive-forms.md +122 -0
- package/skills/angular-developer/references/rendering-strategies.md +44 -0
- package/skills/angular-developer/references/resource.md +77 -0
- package/skills/angular-developer/references/route-animations.md +56 -0
- package/skills/angular-developer/references/route-guards.md +52 -0
- package/skills/angular-developer/references/router-lifecycle.md +45 -0
- package/skills/angular-developer/references/router-testing.md +87 -0
- package/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
- package/skills/angular-developer/references/signal-forms.md +795 -0
- package/skills/angular-developer/references/signals-overview.md +94 -0
- package/skills/angular-developer/references/tailwind-css.md +69 -0
- package/skills/angular-developer/references/template-driven-forms.md +114 -0
- package/skills/angular-developer/references/testing-fundamentals.md +65 -0
- package/skills/api-connector-builder/SKILL.md +120 -0
- package/skills/api-design/SKILL.md +522 -0
- package/skills/architecture-decision-records/SKILL.md +178 -0
- package/skills/article-writing/SKILL.md +78 -0
- package/skills/automation-audit-ops/SKILL.md +141 -0
- package/skills/autonomous-agent-harness/SKILL.md +272 -0
- package/skills/autonomous-loops/SKILL.md +609 -0
- package/skills/backend-patterns/SKILL.md +560 -0
- package/skills/benchmark/SKILL.md +92 -0
- package/skills/blueprint/SKILL.md +104 -0
- package/skills/browser-qa/SKILL.md +86 -0
- package/skills/bun-runtime/SKILL.md +83 -0
- package/skills/canary-watch/SKILL.md +98 -0
- package/skills/carrier-relationship-management/SKILL.md +211 -0
- package/skills/cisco-ios-patterns/SKILL.md +163 -0
- package/skills/ck/SKILL.md +147 -0
- package/skills/ck/commands/forget.mjs +44 -0
- package/skills/ck/commands/info.mjs +24 -0
- package/skills/ck/commands/init.mjs +143 -0
- package/skills/ck/commands/list.mjs +40 -0
- package/skills/ck/commands/migrate.mjs +202 -0
- package/skills/ck/commands/resume.mjs +36 -0
- package/skills/ck/commands/save.mjs +210 -0
- package/skills/ck/commands/shared.mjs +387 -0
- package/skills/ck/hooks/session-start.mjs +224 -0
- package/skills/claude-devfleet/SKILL.md +103 -0
- package/skills/click-path-audit/SKILL.md +244 -0
- package/skills/clickhouse-io/SKILL.md +438 -0
- package/skills/code-tour/SKILL.md +235 -0
- package/skills/codebase-onboarding/SKILL.md +232 -0
- package/skills/coding-standards/SKILL.md +548 -0
- package/skills/compose-multiplatform-patterns/SKILL.md +298 -0
- package/skills/connections-optimizer/SKILL.md +188 -0
- package/skills/content-engine/SKILL.md +126 -0
- package/skills/content-hash-cache-pattern/SKILL.md +160 -0
- package/skills/context-budget/SKILL.md +134 -0
- package/skills/continuous-agent-loop/SKILL.md +44 -0
- package/skills/continuous-learning/SKILL.md +129 -0
- package/skills/continuous-learning/config.json +18 -0
- package/skills/continuous-learning/evaluate-session.sh +69 -0
- package/skills/continuous-learning-v2/SKILL.md +358 -0
- package/skills/continuous-learning-v2/agents/observer-loop.sh +322 -0
- package/skills/continuous-learning-v2/agents/observer.md +198 -0
- package/skills/continuous-learning-v2/agents/session-guardian.sh +150 -0
- package/skills/continuous-learning-v2/agents/start-observer.sh +248 -0
- package/skills/continuous-learning-v2/config.json +8 -0
- package/skills/continuous-learning-v2/hooks/observe.sh +476 -0
- package/skills/continuous-learning-v2/scripts/detect-project.sh +288 -0
- package/skills/continuous-learning-v2/scripts/instinct-cli.py +1519 -0
- package/skills/continuous-learning-v2/scripts/lib/homunculus-dir.sh +31 -0
- package/skills/continuous-learning-v2/scripts/migrate-homunculus.sh +62 -0
- package/skills/continuous-learning-v2/scripts/test_parse_instinct.py +1018 -0
- package/skills/cost-aware-llm-pipeline/SKILL.md +182 -0
- package/skills/cost-tracking/SKILL.md +147 -0
- package/skills/council/SKILL.md +202 -0
- package/skills/cpp-coding-standards/SKILL.md +722 -0
- package/skills/cpp-testing/SKILL.md +323 -0
- package/skills/crosspost/SKILL.md +110 -0
- package/skills/csharp-testing/SKILL.md +320 -0
- package/skills/customer-billing-ops/SKILL.md +139 -0
- package/skills/customs-trade-compliance/SKILL.md +262 -0
- package/skills/dart-flutter-patterns/SKILL.md +562 -0
- package/skills/dashboard-builder/SKILL.md +108 -0
- package/skills/data-scraper-agent/SKILL.md +764 -0
- package/skills/database-migrations/SKILL.md +428 -0
- package/skills/deep-research/SKILL.md +158 -0
- package/skills/defi-amm-security/SKILL.md +166 -0
- package/skills/deployment-patterns/SKILL.md +426 -0
- package/skills/design-system/SKILL.md +81 -0
- package/skills/django-celery/SKILL.md +456 -0
- package/skills/django-patterns/SKILL.md +733 -0
- package/skills/django-security/SKILL.md +592 -0
- package/skills/django-tdd/SKILL.md +728 -0
- package/skills/django-verification/SKILL.md +468 -0
- package/skills/dmux-workflows/SKILL.md +190 -0
- package/skills/docker-patterns/SKILL.md +363 -0
- package/skills/documentation-lookup/SKILL.md +89 -0
- package/skills/dotnet-patterns/SKILL.md +320 -0
- package/skills/e2e-testing/SKILL.md +325 -0
- package/skills/email-ops/SKILL.md +120 -0
- package/skills/energy-procurement/SKILL.md +227 -0
- package/skills/enterprise-agent-ops/SKILL.md +49 -0
- package/skills/error-handling/SKILL.md +375 -0
- package/skills/eval-harness/SKILL.md +269 -0
- package/skills/evm-token-decimals/SKILL.md +130 -0
- package/skills/exa-search/SKILL.md +106 -0
- package/skills/fal-ai-media/SKILL.md +287 -0
- package/skills/fastapi-patterns/SKILL.md +327 -0
- package/skills/finance-billing-ops/SKILL.md +126 -0
- package/skills/flox-environments/SKILL.md +496 -0
- package/skills/flutter-dart-code-review/SKILL.md +434 -0
- package/skills/foundation-models-on-device/SKILL.md +243 -0
- package/skills/frontend-design-direction/SKILL.md +92 -0
- package/skills/frontend-patterns/SKILL.md +641 -0
- package/skills/frontend-slides/SKILL.md +183 -0
- package/skills/frontend-slides/STYLE_PRESETS.md +330 -0
- package/skills/frontend-slides/animation-patterns.md +122 -0
- package/skills/frontend-slides/html-template.md +419 -0
- package/skills/frontend-slides/scripts/export-pdf.sh +418 -0
- package/skills/frontend-slides/scripts/extract-pptx.py +96 -0
- package/skills/frontend-slides/viewport-base.css +153 -0
- package/skills/fsharp-testing/SKILL.md +279 -0
- package/skills/gan-style-harness/SKILL.md +278 -0
- package/skills/gateguard/SKILL.md +125 -0
- package/skills/git-workflow/SKILL.md +714 -0
- package/skills/github-ops/SKILL.md +143 -0
- package/skills/golang-patterns/SKILL.md +673 -0
- package/skills/golang-testing/SKILL.md +719 -0
- package/skills/google-workspace-ops/SKILL.md +94 -0
- package/skills/healthcare-cdss-patterns/SKILL.md +245 -0
- package/skills/healthcare-emr-patterns/SKILL.md +159 -0
- package/skills/healthcare-eval-harness/SKILL.md +207 -0
- package/skills/healthcare-phi-compliance/SKILL.md +145 -0
- package/skills/hermes-imports/SKILL.md +87 -0
- package/skills/hexagonal-architecture/SKILL.md +275 -0
- package/skills/hipaa-compliance/SKILL.md +78 -0
- package/skills/homelab-network-readiness/SKILL.md +169 -0
- package/skills/homelab-network-setup/SKILL.md +129 -0
- package/skills/homelab-pihole-dns/SKILL.md +274 -0
- package/skills/homelab-vlan-segmentation/SKILL.md +311 -0
- package/skills/homelab-wireguard-vpn/SKILL.md +305 -0
- package/skills/hookify-rules/SKILL.md +128 -0
- package/skills/inventory-demand-planning/SKILL.md +246 -0
- package/skills/investor-materials/SKILL.md +95 -0
- package/skills/investor-outreach/SKILL.md +90 -0
- package/skills/ios-icon-gen/SKILL.md +157 -0
- package/skills/ios-icon-gen/scripts/generate_icons.swift +258 -0
- package/skills/ios-icon-gen/scripts/iconify_gen.sh +235 -0
- package/skills/iterative-retrieval/SKILL.md +209 -0
- package/skills/java-coding-standards/SKILL.md +382 -0
- package/skills/jira-integration/SKILL.md +292 -0
- package/skills/jpa-patterns/SKILL.md +150 -0
- package/skills/knowledge-ops/SKILL.md +153 -0
- package/skills/kotlin-coroutines-flows/SKILL.md +283 -0
- package/skills/kotlin-exposed-patterns/SKILL.md +718 -0
- package/skills/kotlin-ktor-patterns/SKILL.md +688 -0
- package/skills/kotlin-patterns/SKILL.md +710 -0
- package/skills/kotlin-testing/SKILL.md +823 -0
- package/skills/laravel-patterns/SKILL.md +414 -0
- package/skills/laravel-plugin-discovery/SKILL.md +228 -0
- package/skills/laravel-security/SKILL.md +284 -0
- package/skills/laravel-tdd/SKILL.md +282 -0
- package/skills/laravel-verification/SKILL.md +178 -0
- package/skills/lead-intelligence/SKILL.md +320 -0
- package/skills/lead-intelligence/agents/enrichment-agent.md +85 -0
- package/skills/lead-intelligence/agents/mutual-mapper.md +75 -0
- package/skills/lead-intelligence/agents/outreach-drafter.md +98 -0
- package/skills/lead-intelligence/agents/signal-scorer.md +60 -0
- package/skills/liquid-glass-design/SKILL.md +279 -0
- package/skills/llm-trading-agent-security/SKILL.md +146 -0
- package/skills/logistics-exception-management/SKILL.md +221 -0
- package/skills/make-interfaces-feel-better/SKILL.md +151 -0
- package/skills/manim-video/SKILL.md +88 -0
- package/skills/manim-video/assets/network_graph_scene.py +52 -0
- package/skills/market-research/SKILL.md +74 -0
- package/skills/mcp-server-patterns/SKILL.md +68 -0
- package/skills/messages-ops/SKILL.md +103 -0
- package/skills/mle-workflow/SKILL.md +345 -0
- package/skills/motion-advanced/SKILL.md +596 -0
- package/skills/motion-foundations/SKILL.md +299 -0
- package/skills/motion-patterns/SKILL.md +435 -0
- package/skills/motion-ui/SKILL.md +574 -0
- package/skills/mysql-patterns/SKILL.md +411 -0
- package/skills/nanoclaw-repl/SKILL.md +32 -0
- package/skills/nestjs-patterns/SKILL.md +229 -0
- package/skills/netmiko-ssh-automation/SKILL.md +173 -0
- package/skills/network-bgp-diagnostics/SKILL.md +167 -0
- package/skills/network-config-validation/SKILL.md +210 -0
- package/skills/network-interface-health/SKILL.md +152 -0
- package/skills/nextjs-turbopack/SKILL.md +43 -0
- package/skills/nodejs-keccak256/SKILL.md +102 -0
- package/skills/nutrient-document-processing/SKILL.md +166 -0
- package/skills/nuxt4-patterns/SKILL.md +99 -0
- package/skills/openclaw-persona-forge/SKILL.md +288 -0
- package/skills/openclaw-persona-forge/gacha.py +224 -0
- package/skills/openclaw-persona-forge/gacha.sh +5 -0
- package/skills/openclaw-persona-forge/references/avatar-style.md +124 -0
- package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -0
- package/skills/openclaw-persona-forge/references/error-handling.md +53 -0
- package/skills/openclaw-persona-forge/references/identity-tension.md +48 -0
- package/skills/openclaw-persona-forge/references/naming-system.md +39 -0
- package/skills/openclaw-persona-forge/references/output-template.md +166 -0
- package/skills/opensource-pipeline/SKILL.md +254 -0
- package/skills/perl-patterns/SKILL.md +503 -0
- package/skills/perl-security/SKILL.md +502 -0
- package/skills/perl-testing/SKILL.md +474 -0
- package/skills/plan-orchestrate/SKILL.md +253 -0
- package/skills/plankton-code-quality/SKILL.md +236 -0
- package/skills/postgres-patterns/SKILL.md +146 -0
- package/skills/product-capability/SKILL.md +140 -0
- package/skills/product-lens/SKILL.md +91 -0
- package/skills/production-audit/SKILL.md +206 -0
- package/skills/production-scheduling/SKILL.md +237 -0
- package/skills/project-flow-ops/SKILL.md +110 -0
- package/skills/prompt-optimizer/SKILL.md +398 -0
- package/skills/python-patterns/SKILL.md +749 -0
- package/skills/python-testing/SKILL.md +815 -0
- package/skills/pytorch-patterns/SKILL.md +395 -0
- package/skills/quality-nonconformance/SKILL.md +259 -0
- package/skills/quarkus-patterns/SKILL.md +721 -0
- package/skills/quarkus-security/SKILL.md +466 -0
- package/skills/quarkus-tdd/SKILL.md +810 -0
- package/skills/quarkus-verification/SKILL.md +478 -0
- package/skills/ralphinho-rfc-pipeline/SKILL.md +66 -0
- package/skills/redis-patterns/SKILL.md +402 -0
- package/skills/regex-vs-llm-structured-text/SKILL.md +219 -0
- package/skills/remotion-video-creation/SKILL.md +43 -0
- package/skills/remotion-video-creation/rules/3d.md +86 -0
- package/skills/remotion-video-creation/rules/animations.md +29 -0
- package/skills/remotion-video-creation/rules/assets/charts-bar-chart.tsx +173 -0
- package/skills/remotion-video-creation/rules/assets/text-animations-typewriter.tsx +100 -0
- package/skills/remotion-video-creation/rules/assets/text-animations-word-highlight.tsx +108 -0
- package/skills/remotion-video-creation/rules/assets.md +78 -0
- package/skills/remotion-video-creation/rules/audio.md +172 -0
- package/skills/remotion-video-creation/rules/calculate-metadata.md +104 -0
- package/skills/remotion-video-creation/rules/can-decode.md +75 -0
- package/skills/remotion-video-creation/rules/charts.md +58 -0
- package/skills/remotion-video-creation/rules/compositions.md +146 -0
- package/skills/remotion-video-creation/rules/display-captions.md +126 -0
- package/skills/remotion-video-creation/rules/extract-frames.md +229 -0
- package/skills/remotion-video-creation/rules/fonts.md +152 -0
- package/skills/remotion-video-creation/rules/get-audio-duration.md +58 -0
- package/skills/remotion-video-creation/rules/get-video-dimensions.md +68 -0
- package/skills/remotion-video-creation/rules/get-video-duration.md +58 -0
- package/skills/remotion-video-creation/rules/gifs.md +138 -0
- package/skills/remotion-video-creation/rules/images.md +130 -0
- package/skills/remotion-video-creation/rules/import-srt-captions.md +67 -0
- package/skills/remotion-video-creation/rules/lottie.md +67 -0
- package/skills/remotion-video-creation/rules/measuring-dom-nodes.md +34 -0
- package/skills/remotion-video-creation/rules/measuring-text.md +143 -0
- package/skills/remotion-video-creation/rules/sequencing.md +106 -0
- package/skills/remotion-video-creation/rules/tailwind.md +11 -0
- package/skills/remotion-video-creation/rules/text-animations.md +20 -0
- package/skills/remotion-video-creation/rules/timing.md +179 -0
- package/skills/remotion-video-creation/rules/transcribe-captions.md +19 -0
- package/skills/remotion-video-creation/rules/transitions.md +122 -0
- package/skills/remotion-video-creation/rules/trimming.md +52 -0
- package/skills/remotion-video-creation/rules/videos.md +171 -0
- package/skills/repo-scan/SKILL.md +78 -0
- package/skills/research-ops/SKILL.md +111 -0
- package/skills/returns-reverse-logistics/SKILL.md +239 -0
- package/skills/rules-distill/SKILL.md +263 -0
- package/skills/rules-distill/scripts/scan-rules.sh +58 -0
- package/skills/rules-distill/scripts/scan-skills.sh +129 -0
- package/skills/rust-patterns/SKILL.md +498 -0
- package/skills/rust-testing/SKILL.md +499 -0
- package/skills/safety-guard/SKILL.md +74 -0
- package/skills/santa-method/SKILL.md +306 -0
- package/skills/scientific-db-pubmed-database/SKILL.md +175 -0
- package/skills/scientific-db-uspto-database/SKILL.md +177 -0
- package/skills/scientific-pkg-gget/SKILL.md +166 -0
- package/skills/scientific-thinking-literature-review/SKILL.md +192 -0
- package/skills/scientific-thinking-scholar-evaluation/SKILL.md +160 -0
- package/skills/search-first/SKILL.md +181 -0
- package/skills/security-bounty-hunter/SKILL.md +99 -0
- package/skills/security-review/SKILL.md +502 -0
- package/skills/security-review/cloud-infrastructure-security.md +361 -0
- package/skills/seo/SKILL.md +153 -0
- package/skills/skill-comply/SKILL.md +57 -0
- package/skills/skill-comply/fixtures/compliant_trace.jsonl +5 -0
- package/skills/skill-comply/fixtures/noncompliant_trace.jsonl +3 -0
- package/skills/skill-comply/fixtures/tdd_spec.yaml +44 -0
- package/skills/skill-comply/prompts/classifier.md +24 -0
- package/skills/skill-comply/prompts/scenario_generator.md +62 -0
- package/skills/skill-comply/prompts/spec_generator.md +42 -0
- package/skills/skill-comply/pyproject.toml +15 -0
- package/skills/skill-comply/scripts/__init__.py +0 -0
- package/skills/skill-comply/scripts/classifier.py +85 -0
- package/skills/skill-comply/scripts/grader.py +124 -0
- package/skills/skill-comply/scripts/parser.py +107 -0
- package/skills/skill-comply/scripts/report.py +170 -0
- package/skills/skill-comply/scripts/run.py +127 -0
- package/skills/skill-comply/scripts/runner.py +186 -0
- package/skills/skill-comply/scripts/scenario_generator.py +70 -0
- package/skills/skill-comply/scripts/spec_generator.py +72 -0
- package/skills/skill-comply/scripts/utils.py +13 -0
- package/skills/skill-comply/tests/test_grader.py +197 -0
- package/skills/skill-comply/tests/test_parser.py +90 -0
- package/skills/skill-comply/tests/test_runner.py +172 -0
- package/skills/skill-scout/SKILL.md +139 -0
- package/skills/skill-stocktake/SKILL.md +193 -0
- package/skills/skill-stocktake/scripts/quick-diff.sh +87 -0
- package/skills/skill-stocktake/scripts/save-results.sh +56 -0
- package/skills/skill-stocktake/scripts/scan.sh +170 -0
- package/skills/social-graph-ranker/SKILL.md +153 -0
- package/skills/springboot-patterns/SKILL.md +313 -0
- package/skills/springboot-security/SKILL.md +271 -0
- package/skills/springboot-tdd/SKILL.md +157 -0
- package/skills/springboot-verification/SKILL.md +230 -0
- package/skills/strategic-compact/SKILL.md +129 -0
- package/skills/strategic-compact/suggest-compact.sh +54 -0
- package/skills/swift-actor-persistence/SKILL.md +142 -0
- package/skills/swift-concurrency-6-2/SKILL.md +216 -0
- package/skills/swift-protocol-di-testing/SKILL.md +189 -0
- package/skills/swiftui-patterns/SKILL.md +259 -0
- package/skills/tdd-workflow/SKILL.md +462 -0
- package/skills/team-builder/SKILL.md +166 -0
- package/skills/terminal-ops/SKILL.md +108 -0
- package/skills/tinystruct-patterns/SKILL.md +130 -0
- package/skills/tinystruct-patterns/references/architecture.md +77 -0
- package/skills/tinystruct-patterns/references/data-handling.md +35 -0
- package/skills/tinystruct-patterns/references/routing.md +57 -0
- package/skills/tinystruct-patterns/references/system-usage.md +74 -0
- package/skills/tinystruct-patterns/references/testing.md +59 -0
- package/skills/token-budget-advisor/SKILL.md +133 -0
- package/skills/ui-demo/SKILL.md +464 -0
- package/skills/ui-to-vue/SKILL.md +134 -0
- package/skills/unified-notifications-ops/SKILL.md +186 -0
- package/skills/verification-loop/SKILL.md +125 -0
- package/skills/video-editing/SKILL.md +309 -0
- package/skills/videodb/SKILL.md +373 -0
- package/skills/videodb/reference/api-reference.md +550 -0
- package/skills/videodb/reference/capture-reference.md +407 -0
- package/skills/videodb/reference/capture.md +101 -0
- package/skills/videodb/reference/editor.md +443 -0
- package/skills/videodb/reference/generative.md +331 -0
- package/skills/videodb/reference/rtstream-reference.md +564 -0
- package/skills/videodb/reference/rtstream.md +65 -0
- package/skills/videodb/reference/search.md +230 -0
- package/skills/videodb/reference/streaming.md +406 -0
- package/skills/videodb/reference/use-cases.md +118 -0
- package/skills/videodb/scripts/ws_listener.py +282 -0
- package/skills/visa-doc-translate/README.md +86 -0
- package/skills/visa-doc-translate/SKILL.md +117 -0
- package/skills/vite-patterns/SKILL.md +448 -0
- package/skills/windows-desktop-e2e/SKILL.md +787 -0
- package/skills/workspace-surface-audit/SKILL.md +124 -0
- package/skills/x-api/SKILL.md +233 -0
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: quarkus-tdd
|
|
3
|
+
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.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Quarkus TDD Workflow
|
|
7
|
+
|
|
8
|
+
TDD guidance for Quarkus 3.x services with 80%+ coverage (unit + integration). Optimized for event-driven architectures with Apache Camel.
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
- New features or REST endpoints
|
|
13
|
+
- Bug fixes or refactors
|
|
14
|
+
- Adding data access logic, security rules, or reactive streams
|
|
15
|
+
- Testing Apache Camel routes and event handlers
|
|
16
|
+
- Testing event-driven services with RabbitMQ
|
|
17
|
+
- Testing conditional flow logic
|
|
18
|
+
- Validating CompletableFuture async operations
|
|
19
|
+
- Testing LogContext propagation
|
|
20
|
+
|
|
21
|
+
## Workflow
|
|
22
|
+
|
|
23
|
+
1. Write tests first (they should fail)
|
|
24
|
+
2. Implement minimal code to pass
|
|
25
|
+
3. Refactor with tests green
|
|
26
|
+
4. Enforce coverage with JaCoCo (80%+ target)
|
|
27
|
+
|
|
28
|
+
## Unit Tests with @Nested Organization
|
|
29
|
+
|
|
30
|
+
Follow this structured approach for comprehensive, readable tests:
|
|
31
|
+
|
|
32
|
+
```java
|
|
33
|
+
@ExtendWith(MockitoExtension.class)
|
|
34
|
+
@DisplayName("OrderService Unit Tests")
|
|
35
|
+
class OrderServiceTest {
|
|
36
|
+
|
|
37
|
+
@Mock
|
|
38
|
+
private OrderRepository orderRepository;
|
|
39
|
+
|
|
40
|
+
@Mock
|
|
41
|
+
private EventService eventService;
|
|
42
|
+
|
|
43
|
+
@Mock
|
|
44
|
+
private FulfillmentPublisher fulfillmentPublisher;
|
|
45
|
+
|
|
46
|
+
@InjectMocks
|
|
47
|
+
private OrderService orderService;
|
|
48
|
+
|
|
49
|
+
private CreateOrderCommand validCommand;
|
|
50
|
+
|
|
51
|
+
@BeforeEach
|
|
52
|
+
void setUp() {
|
|
53
|
+
validCommand = new CreateOrderCommand(
|
|
54
|
+
"customer-123",
|
|
55
|
+
List.of(new OrderLine("sku-123", 2))
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@Nested
|
|
60
|
+
@DisplayName("Tests for createOrder")
|
|
61
|
+
class CreateOrder {
|
|
62
|
+
|
|
63
|
+
@Test
|
|
64
|
+
@DisplayName("Should persist order and publish fulfillment event")
|
|
65
|
+
void givenValidCommand_whenCreateOrder_thenPersistsAndPublishes() {
|
|
66
|
+
// ARRANGE
|
|
67
|
+
doNothing().when(orderRepository).persist(any(Order.class));
|
|
68
|
+
|
|
69
|
+
// ACT
|
|
70
|
+
OrderReceipt receipt = orderService.createOrder(validCommand);
|
|
71
|
+
|
|
72
|
+
// ASSERT
|
|
73
|
+
assertThat(receipt).isNotNull();
|
|
74
|
+
assertThat(receipt.customerId()).isEqualTo("customer-123");
|
|
75
|
+
verify(orderRepository).persist(any(Order.class));
|
|
76
|
+
verify(fulfillmentPublisher).publishAsync(receipt);
|
|
77
|
+
verify(eventService).createSuccessEvent(receipt, "ORDER_CREATED");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@Test
|
|
81
|
+
@DisplayName("Should reject missing customer id")
|
|
82
|
+
void givenMissingCustomerId_whenCreateOrder_thenThrowsBadRequest() {
|
|
83
|
+
// ARRANGE
|
|
84
|
+
CreateOrderCommand invalid = new CreateOrderCommand("", validCommand.lines());
|
|
85
|
+
|
|
86
|
+
// ACT & ASSERT
|
|
87
|
+
WebApplicationException exception = assertThrows(
|
|
88
|
+
WebApplicationException.class,
|
|
89
|
+
() -> orderService.createOrder(invalid)
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
assertThat(exception.getResponse().getStatus()).isEqualTo(400);
|
|
93
|
+
verify(orderRepository, never()).persist(any(Order.class));
|
|
94
|
+
verify(fulfillmentPublisher, never()).publishAsync(any());
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@Test
|
|
98
|
+
@DisplayName("Should record error event when persistence fails")
|
|
99
|
+
void givenPersistenceFailure_whenCreateOrder_thenRecordsErrorEvent() {
|
|
100
|
+
// ARRANGE
|
|
101
|
+
doThrow(new PersistenceException("database unavailable"))
|
|
102
|
+
.when(orderRepository).persist(any(Order.class));
|
|
103
|
+
|
|
104
|
+
// ACT & ASSERT
|
|
105
|
+
PersistenceException exception = assertThrows(
|
|
106
|
+
PersistenceException.class,
|
|
107
|
+
() -> orderService.createOrder(validCommand)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
assertThat(exception.getMessage()).contains("database unavailable");
|
|
111
|
+
verify(eventService).createErrorEvent(
|
|
112
|
+
eq(validCommand),
|
|
113
|
+
eq("ORDER_CREATE_FAILED"),
|
|
114
|
+
contains("database unavailable")
|
|
115
|
+
);
|
|
116
|
+
verify(fulfillmentPublisher, never()).publishAsync(any());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@Test
|
|
120
|
+
@DisplayName("Should reject null commands")
|
|
121
|
+
void givenNullCommand_whenCreateOrder_thenThrowsNullPointerException() {
|
|
122
|
+
// ACT & ASSERT
|
|
123
|
+
assertThrows(
|
|
124
|
+
NullPointerException.class,
|
|
125
|
+
() -> orderService.createOrder(null)
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
verify(orderRepository, never()).persist(any(Order.class));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Key Testing Patterns
|
|
135
|
+
|
|
136
|
+
1. **@Nested Classes**: Group tests by method being tested
|
|
137
|
+
2. **@DisplayName**: Provide readable test descriptions for test reports
|
|
138
|
+
3. **Naming Convention**: `givenX_whenY_thenZ` for clarity
|
|
139
|
+
4. **AAA Pattern**: Explicit `// ARRANGE`, `// ACT`, `// ASSERT` comments
|
|
140
|
+
5. **@BeforeEach**: Setup common test data to reduce duplication
|
|
141
|
+
6. **assertDoesNotThrow**: Test success scenarios without catching exceptions
|
|
142
|
+
7. **assertThrows**: Test exception scenarios with message validation using AssertJ
|
|
143
|
+
8. **Comprehensive Coverage**: Test happy paths, null inputs, edge cases, exceptions
|
|
144
|
+
9. **Verify Interactions**: Use Mockito `verify()` to ensure methods are called correctly
|
|
145
|
+
10. **Never Verify**: Use `never()` to ensure methods are NOT called in error scenarios
|
|
146
|
+
|
|
147
|
+
## Testing Camel Routes
|
|
148
|
+
|
|
149
|
+
```java
|
|
150
|
+
@QuarkusTest
|
|
151
|
+
@DisplayName("Business Rules Camel Route Tests")
|
|
152
|
+
class BusinessRulesRouteTest {
|
|
153
|
+
|
|
154
|
+
@Inject
|
|
155
|
+
CamelContext camelContext;
|
|
156
|
+
|
|
157
|
+
@Inject
|
|
158
|
+
ProducerTemplate producerTemplate;
|
|
159
|
+
|
|
160
|
+
@InjectMock
|
|
161
|
+
EventService eventService;
|
|
162
|
+
|
|
163
|
+
@InjectMock
|
|
164
|
+
DocumentValidator documentValidator;
|
|
165
|
+
|
|
166
|
+
private BusinessRulesPayload testPayload;
|
|
167
|
+
|
|
168
|
+
@BeforeEach
|
|
169
|
+
void setUp() {
|
|
170
|
+
// ARRANGE - Test data
|
|
171
|
+
testPayload = new BusinessRulesPayload();
|
|
172
|
+
testPayload.setDocumentId(1L);
|
|
173
|
+
testPayload.setFlowProfile(FlowProfile.BASIC);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@Nested
|
|
177
|
+
@DisplayName("Tests for business-rules-publisher route")
|
|
178
|
+
class BusinessRulesPublisher {
|
|
179
|
+
|
|
180
|
+
@Test
|
|
181
|
+
@DisplayName("Should successfully publish message to RabbitMQ")
|
|
182
|
+
void givenValidPayload_whenPublish_thenMessageSentToQueue() throws Exception {
|
|
183
|
+
// ARRANGE
|
|
184
|
+
MockEndpoint mockRabbitMQ = camelContext.getEndpoint("mock:rabbitmq", MockEndpoint.class);
|
|
185
|
+
mockRabbitMQ.expectedMessageCount(1);
|
|
186
|
+
|
|
187
|
+
// Replace real endpoint with mock for testing
|
|
188
|
+
camelContext.getRouteController().stopRoute("business-rules-publisher");
|
|
189
|
+
AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {
|
|
190
|
+
advice.replaceFromWith("direct:business-rules-publisher");
|
|
191
|
+
advice.weaveByToString(".*spring-rabbitmq.*").replace().to("mock:rabbitmq");
|
|
192
|
+
});
|
|
193
|
+
camelContext.getRouteController().startRoute("business-rules-publisher");
|
|
194
|
+
|
|
195
|
+
// ACT
|
|
196
|
+
producerTemplate.sendBody("direct:business-rules-publisher", testPayload);
|
|
197
|
+
|
|
198
|
+
// ASSERT — body is a JSON String after .marshal().json(JsonLibrary.Jackson)
|
|
199
|
+
mockRabbitMQ.assertIsSatisfied(5000);
|
|
200
|
+
|
|
201
|
+
assertThat(mockRabbitMQ.getExchanges()).hasSize(1);
|
|
202
|
+
String body = mockRabbitMQ.getExchanges().get(0).getIn().getBody(String.class);
|
|
203
|
+
assertThat(body).contains("\"documentId\":1");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@Test
|
|
207
|
+
@DisplayName("Should handle marshalling to JSON")
|
|
208
|
+
void givenPayload_whenPublish_thenMarshalledToJson() throws Exception {
|
|
209
|
+
// ARRANGE
|
|
210
|
+
MockEndpoint mockMarshal = new MockEndpoint("mock:marshal");
|
|
211
|
+
camelContext.addEndpoint("mock:marshal", mockMarshal);
|
|
212
|
+
mockMarshal.expectedMessageCount(1);
|
|
213
|
+
|
|
214
|
+
camelContext.getRouteController().stopRoute("business-rules-publisher");
|
|
215
|
+
AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {
|
|
216
|
+
advice.weaveAddLast().to("mock:marshal");
|
|
217
|
+
});
|
|
218
|
+
camelContext.getRouteController().startRoute("business-rules-publisher");
|
|
219
|
+
|
|
220
|
+
// ACT
|
|
221
|
+
producerTemplate.sendBody("direct:business-rules-publisher", testPayload);
|
|
222
|
+
|
|
223
|
+
// ASSERT
|
|
224
|
+
mockMarshal.assertIsSatisfied(5000);
|
|
225
|
+
|
|
226
|
+
String body = mockMarshal.getExchanges().get(0).getIn().getBody(String.class);
|
|
227
|
+
assertThat(body).contains("\"documentId\":1");
|
|
228
|
+
assertThat(body).contains("\"flowProfile\":\"BASIC\"");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@Nested
|
|
233
|
+
@DisplayName("Tests for document-processing route")
|
|
234
|
+
class DocumentProcessing {
|
|
235
|
+
|
|
236
|
+
@Test
|
|
237
|
+
@DisplayName("Should route invoice to correct processor")
|
|
238
|
+
void givenInvoiceType_whenProcess_thenRoutesToInvoiceProcessor() throws Exception {
|
|
239
|
+
// ARRANGE
|
|
240
|
+
MockEndpoint mockInvoice = camelContext.getEndpoint("mock:invoice", MockEndpoint.class);
|
|
241
|
+
mockInvoice.expectedMessageCount(1);
|
|
242
|
+
|
|
243
|
+
camelContext.getRouteController().stopRoute("document-processing");
|
|
244
|
+
AdviceWith.adviceWith(camelContext, "document-processing", advice -> {
|
|
245
|
+
advice.weaveByToString(".*direct:process-invoice.*").replace().to("mock:invoice");
|
|
246
|
+
});
|
|
247
|
+
camelContext.getRouteController().startRoute("document-processing");
|
|
248
|
+
|
|
249
|
+
// ACT
|
|
250
|
+
producerTemplate.sendBodyAndHeader("direct:process-document",
|
|
251
|
+
testPayload, "documentType", "INVOICE");
|
|
252
|
+
|
|
253
|
+
// ASSERT
|
|
254
|
+
mockInvoice.assertIsSatisfied(5000);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
@Test
|
|
258
|
+
@DisplayName("Should handle validation errors gracefully")
|
|
259
|
+
void givenValidationError_whenProcess_thenRoutesToErrorHandler() throws Exception {
|
|
260
|
+
// ARRANGE
|
|
261
|
+
MockEndpoint mockError = camelContext.getEndpoint("mock:error", MockEndpoint.class);
|
|
262
|
+
mockError.expectedMessageCount(1);
|
|
263
|
+
|
|
264
|
+
camelContext.getRouteController().stopRoute("document-processing");
|
|
265
|
+
AdviceWith.adviceWith(camelContext, "document-processing", advice -> {
|
|
266
|
+
advice.weaveByToString(".*direct:validation-error-handler.*")
|
|
267
|
+
.replace().to("mock:error");
|
|
268
|
+
});
|
|
269
|
+
camelContext.getRouteController().startRoute("document-processing");
|
|
270
|
+
|
|
271
|
+
// Mock validator bean to throw exception
|
|
272
|
+
when(documentValidator.validate(any())).thenThrow(new ValidationException("Invalid document"));
|
|
273
|
+
|
|
274
|
+
// ACT
|
|
275
|
+
producerTemplate.sendBody("direct:process-document", testPayload);
|
|
276
|
+
|
|
277
|
+
// ASSERT
|
|
278
|
+
mockError.assertIsSatisfied(5000);
|
|
279
|
+
|
|
280
|
+
Exception exception = mockError.getExchanges().get(0).getException();
|
|
281
|
+
assertThat(exception).isInstanceOf(ValidationException.class);
|
|
282
|
+
assertThat(exception.getMessage()).contains("Invalid document");
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Testing Event Services
|
|
289
|
+
|
|
290
|
+
```java
|
|
291
|
+
@ExtendWith(MockitoExtension.class)
|
|
292
|
+
@DisplayName("EventService Unit Tests")
|
|
293
|
+
class EventServiceTest {
|
|
294
|
+
|
|
295
|
+
@Mock
|
|
296
|
+
private EventRepository eventRepository;
|
|
297
|
+
|
|
298
|
+
@Mock
|
|
299
|
+
private ObjectMapper objectMapper;
|
|
300
|
+
|
|
301
|
+
@InjectMocks
|
|
302
|
+
private EventService eventService;
|
|
303
|
+
|
|
304
|
+
private BusinessRulesPayload testPayload;
|
|
305
|
+
|
|
306
|
+
@BeforeEach
|
|
307
|
+
void setUp() {
|
|
308
|
+
// ARRANGE
|
|
309
|
+
testPayload = new BusinessRulesPayload();
|
|
310
|
+
testPayload.setDocumentId(1L);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
@Nested
|
|
314
|
+
@DisplayName("Tests for createSuccessEvent")
|
|
315
|
+
class CreateSuccessEvent {
|
|
316
|
+
|
|
317
|
+
@Test
|
|
318
|
+
@DisplayName("Should create success event with correct attributes")
|
|
319
|
+
void givenValidPayload_whenCreateSuccessEvent_thenEventPersisted() throws Exception {
|
|
320
|
+
// ARRANGE
|
|
321
|
+
when(objectMapper.writeValueAsString(testPayload)).thenReturn("{\"documentId\":1}");
|
|
322
|
+
|
|
323
|
+
// ACT
|
|
324
|
+
assertDoesNotThrow(() ->
|
|
325
|
+
eventService.createSuccessEvent(testPayload, "DOCUMENT_PROCESSED"));
|
|
326
|
+
|
|
327
|
+
// ASSERT
|
|
328
|
+
verify(eventRepository).persist(argThat(event ->
|
|
329
|
+
event.getType().equals("DOCUMENT_PROCESSED") &&
|
|
330
|
+
event.getStatus() == EventStatus.SUCCESS &&
|
|
331
|
+
event.getPayload().equals("{\"documentId\":1}") &&
|
|
332
|
+
event.getTimestamp() != null
|
|
333
|
+
));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
@Test
|
|
337
|
+
@DisplayName("Should throw exception when payload is null")
|
|
338
|
+
void givenNullPayload_whenCreateSuccessEvent_thenThrowsException() {
|
|
339
|
+
// ARRANGE
|
|
340
|
+
Object nullPayload = null;
|
|
341
|
+
|
|
342
|
+
// ACT & ASSERT
|
|
343
|
+
NullPointerException exception = assertThrows(
|
|
344
|
+
NullPointerException.class,
|
|
345
|
+
() -> eventService.createSuccessEvent(nullPayload, "EVENT_TYPE")
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
assertThat(exception.getMessage()).isEqualTo("Payload cannot be null");
|
|
349
|
+
verify(eventRepository, never()).persist(any());
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
@Nested
|
|
354
|
+
@DisplayName("Tests for createErrorEvent")
|
|
355
|
+
class CreateErrorEvent {
|
|
356
|
+
|
|
357
|
+
@Test
|
|
358
|
+
@DisplayName("Should create error event with error message")
|
|
359
|
+
void givenError_whenCreateErrorEvent_thenEventPersistedWithMessage() throws Exception {
|
|
360
|
+
// ARRANGE
|
|
361
|
+
String errorMessage = "Processing failed";
|
|
362
|
+
when(objectMapper.writeValueAsString(testPayload)).thenReturn("{\"documentId\":1}");
|
|
363
|
+
|
|
364
|
+
// ACT
|
|
365
|
+
assertDoesNotThrow(() ->
|
|
366
|
+
eventService.createErrorEvent(testPayload, "PROCESSING_ERROR", errorMessage));
|
|
367
|
+
|
|
368
|
+
// ASSERT
|
|
369
|
+
verify(eventRepository).persist(argThat(event ->
|
|
370
|
+
event.getType().equals("PROCESSING_ERROR") &&
|
|
371
|
+
event.getStatus() == EventStatus.ERROR &&
|
|
372
|
+
event.getErrorMessage().equals(errorMessage) &&
|
|
373
|
+
event.getPayload().equals("{\"documentId\":1}")
|
|
374
|
+
));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
@ParameterizedTest
|
|
378
|
+
@DisplayName("Should reject invalid error messages")
|
|
379
|
+
@ValueSource(strings = {"", " "})
|
|
380
|
+
void givenBlankErrorMessage_whenCreateErrorEvent_thenThrowsException(String blankMessage) {
|
|
381
|
+
// ACT & ASSERT
|
|
382
|
+
IllegalArgumentException exception = assertThrows(
|
|
383
|
+
IllegalArgumentException.class,
|
|
384
|
+
() -> eventService.createErrorEvent(testPayload, "ERROR", blankMessage)
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
assertThat(exception.getMessage()).contains("Error message cannot be blank");
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## Testing CompletableFuture
|
|
394
|
+
|
|
395
|
+
```java
|
|
396
|
+
@ExtendWith(MockitoExtension.class)
|
|
397
|
+
@DisplayName("FileStorageService Unit Tests")
|
|
398
|
+
class FileStorageServiceTest {
|
|
399
|
+
|
|
400
|
+
@Mock
|
|
401
|
+
private S3Client s3Client;
|
|
402
|
+
|
|
403
|
+
@Mock
|
|
404
|
+
private ExecutorService executorService;
|
|
405
|
+
|
|
406
|
+
@InjectMocks
|
|
407
|
+
private FileStorageService fileStorageService;
|
|
408
|
+
|
|
409
|
+
private InputStream testInputStream;
|
|
410
|
+
private LogContext testLogContext;
|
|
411
|
+
|
|
412
|
+
@BeforeEach
|
|
413
|
+
void setUp() {
|
|
414
|
+
// ARRANGE
|
|
415
|
+
testInputStream = new ByteArrayInputStream("test content".getBytes());
|
|
416
|
+
testLogContext = new LogContext();
|
|
417
|
+
testLogContext.put("traceId", "trace-123");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
@Nested
|
|
421
|
+
@DisplayName("Tests for uploadOriginalFile")
|
|
422
|
+
class UploadOriginalFile {
|
|
423
|
+
|
|
424
|
+
@Test
|
|
425
|
+
@DisplayName("Should successfully upload file and return document info")
|
|
426
|
+
void givenValidFile_whenUpload_thenReturnsDocumentInfo() throws Exception {
|
|
427
|
+
// ARRANGE
|
|
428
|
+
doAnswer(invocation -> {
|
|
429
|
+
((Runnable) invocation.getArgument(0)).run();
|
|
430
|
+
return null;
|
|
431
|
+
}).when(executorService).execute(any(Runnable.class));
|
|
432
|
+
|
|
433
|
+
when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class)))
|
|
434
|
+
.thenReturn(PutObjectResponse.builder().build());
|
|
435
|
+
|
|
436
|
+
// ACT
|
|
437
|
+
CompletableFuture<StoredDocumentInfo> future =
|
|
438
|
+
fileStorageService.uploadOriginalFile(testInputStream, 1024L,
|
|
439
|
+
testLogContext, InvoiceFormat.UBL);
|
|
440
|
+
|
|
441
|
+
StoredDocumentInfo result = future.join();
|
|
442
|
+
|
|
443
|
+
// ASSERT
|
|
444
|
+
assertThat(result).isNotNull();
|
|
445
|
+
assertThat(result.getPath()).isNotBlank();
|
|
446
|
+
assertThat(result.getSize()).isEqualTo(1024L);
|
|
447
|
+
assertThat(result.getUploadedAt()).isNotNull();
|
|
448
|
+
|
|
449
|
+
verify(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class));
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
@Test
|
|
453
|
+
@DisplayName("Should handle S3 upload failure")
|
|
454
|
+
void givenS3Failure_whenUpload_thenCompletableFutureFails() {
|
|
455
|
+
// ARRANGE — run synchronously so exception propagates through the future
|
|
456
|
+
doAnswer(invocation -> {
|
|
457
|
+
((Runnable) invocation.getArgument(0)).run();
|
|
458
|
+
return null;
|
|
459
|
+
}).when(executorService).execute(any(Runnable.class));
|
|
460
|
+
|
|
461
|
+
when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class)))
|
|
462
|
+
.thenThrow(new StorageException("S3 unavailable"));
|
|
463
|
+
|
|
464
|
+
// ACT
|
|
465
|
+
CompletableFuture<StoredDocumentInfo> future =
|
|
466
|
+
fileStorageService.uploadOriginalFile(testInputStream, 1024L,
|
|
467
|
+
testLogContext, InvoiceFormat.UBL);
|
|
468
|
+
|
|
469
|
+
// ASSERT
|
|
470
|
+
assertThatThrownBy(() -> future.join())
|
|
471
|
+
.isInstanceOf(CompletionException.class)
|
|
472
|
+
.hasCauseInstanceOf(StorageException.class)
|
|
473
|
+
.hasMessageContaining("S3 unavailable");
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
@Test
|
|
477
|
+
@DisplayName("Should propagate LogContext to async operation")
|
|
478
|
+
void givenLogContext_whenUpload_thenContextPropagated() throws Exception {
|
|
479
|
+
// ARRANGE
|
|
480
|
+
AtomicReference<LogContext> capturedContext = new AtomicReference<>();
|
|
481
|
+
|
|
482
|
+
doAnswer(invocation -> {
|
|
483
|
+
capturedContext.set(CustomLog.getCurrentContext());
|
|
484
|
+
((Runnable) invocation.getArgument(0)).run();
|
|
485
|
+
return null;
|
|
486
|
+
}).when(executorService).execute(any(Runnable.class));
|
|
487
|
+
|
|
488
|
+
// ACT
|
|
489
|
+
fileStorageService.uploadOriginalFile(testInputStream, 1024L,
|
|
490
|
+
testLogContext, InvoiceFormat.UBL).join();
|
|
491
|
+
|
|
492
|
+
// ASSERT
|
|
493
|
+
assertThat(capturedContext.get()).isNotNull();
|
|
494
|
+
assertThat(capturedContext.get().get("traceId")).isEqualTo("trace-123");
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
## Resource Layer Tests (REST Assured)
|
|
501
|
+
|
|
502
|
+
```java
|
|
503
|
+
@QuarkusTest
|
|
504
|
+
@DisplayName("DocumentResource API Tests")
|
|
505
|
+
class DocumentResourceTest {
|
|
506
|
+
|
|
507
|
+
@InjectMock
|
|
508
|
+
DocumentService documentService;
|
|
509
|
+
|
|
510
|
+
@Nested
|
|
511
|
+
@DisplayName("Tests for GET /api/documents")
|
|
512
|
+
class ListDocuments {
|
|
513
|
+
|
|
514
|
+
@Test
|
|
515
|
+
@DisplayName("Should return list of documents")
|
|
516
|
+
void givenDocumentsExist_whenList_thenReturnsOk() {
|
|
517
|
+
// ARRANGE
|
|
518
|
+
List<Document> documents = List.of(createDocument(1L, "DOC-001"));
|
|
519
|
+
when(documentService.list(0, 20)).thenReturn(documents);
|
|
520
|
+
|
|
521
|
+
// ACT & ASSERT
|
|
522
|
+
given()
|
|
523
|
+
.when().get("/api/documents")
|
|
524
|
+
.then()
|
|
525
|
+
.statusCode(200)
|
|
526
|
+
.body("$.size()", is(1))
|
|
527
|
+
.body("[0].referenceNumber", equalTo("DOC-001"));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
@Nested
|
|
532
|
+
@DisplayName("Tests for POST /api/documents")
|
|
533
|
+
class CreateDocument {
|
|
534
|
+
|
|
535
|
+
@Test
|
|
536
|
+
@DisplayName("Should create document and return 201")
|
|
537
|
+
void givenValidRequest_whenCreate_thenReturns201() {
|
|
538
|
+
// ARRANGE
|
|
539
|
+
Document document = createDocument(1L, "DOC-001");
|
|
540
|
+
when(documentService.create(any())).thenReturn(document);
|
|
541
|
+
|
|
542
|
+
// ACT & ASSERT
|
|
543
|
+
given()
|
|
544
|
+
.contentType(ContentType.JSON)
|
|
545
|
+
.body("""
|
|
546
|
+
{
|
|
547
|
+
"referenceNumber": "DOC-001",
|
|
548
|
+
"description": "Test document",
|
|
549
|
+
"validUntil": "2030-01-01T00:00:00Z",
|
|
550
|
+
"categories": ["test"]
|
|
551
|
+
}
|
|
552
|
+
""")
|
|
553
|
+
.when().post("/api/documents")
|
|
554
|
+
.then()
|
|
555
|
+
.statusCode(201)
|
|
556
|
+
.header("Location", containsString("/api/documents/1"))
|
|
557
|
+
.body("referenceNumber", equalTo("DOC-001"));
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
@Test
|
|
561
|
+
@DisplayName("Should return 400 for invalid input")
|
|
562
|
+
void givenInvalidRequest_whenCreate_thenReturns400() {
|
|
563
|
+
// ACT & ASSERT
|
|
564
|
+
given()
|
|
565
|
+
.contentType(ContentType.JSON)
|
|
566
|
+
.body("""
|
|
567
|
+
{
|
|
568
|
+
"referenceNumber": "",
|
|
569
|
+
"description": "Test"
|
|
570
|
+
}
|
|
571
|
+
""")
|
|
572
|
+
.when().post("/api/documents")
|
|
573
|
+
.then()
|
|
574
|
+
.statusCode(400);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
private Document createDocument(Long id, String referenceNumber) {
|
|
579
|
+
Document document = new Document();
|
|
580
|
+
document.setId(id);
|
|
581
|
+
document.setReferenceNumber(referenceNumber);
|
|
582
|
+
document.setStatus(DocumentStatus.PENDING);
|
|
583
|
+
return document;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
## Integration Tests with Real Database
|
|
589
|
+
|
|
590
|
+
```java
|
|
591
|
+
@QuarkusTest
|
|
592
|
+
@TestProfile(IntegrationTestProfile.class)
|
|
593
|
+
@DisplayName("Document Integration Tests")
|
|
594
|
+
class DocumentIntegrationTest {
|
|
595
|
+
|
|
596
|
+
@Test
|
|
597
|
+
@Transactional
|
|
598
|
+
@DisplayName("Should create and retrieve document via API")
|
|
599
|
+
void givenNewDocument_whenCreateAndRetrieve_thenSuccessful() {
|
|
600
|
+
// ACT - Create via API
|
|
601
|
+
Long id = given()
|
|
602
|
+
.contentType(ContentType.JSON)
|
|
603
|
+
.body("""
|
|
604
|
+
{
|
|
605
|
+
"referenceNumber": "INT-001",
|
|
606
|
+
"description": "Integration test",
|
|
607
|
+
"validUntil": "2030-01-01T00:00:00Z",
|
|
608
|
+
"categories": ["test"]
|
|
609
|
+
}
|
|
610
|
+
""")
|
|
611
|
+
.when().post("/api/documents")
|
|
612
|
+
.then()
|
|
613
|
+
.statusCode(201)
|
|
614
|
+
.extract().path("id");
|
|
615
|
+
|
|
616
|
+
// ASSERT - Retrieve via API
|
|
617
|
+
given()
|
|
618
|
+
.when().get("/api/documents/" + id)
|
|
619
|
+
.then()
|
|
620
|
+
.statusCode(200)
|
|
621
|
+
.body("referenceNumber", equalTo("INT-001"));
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
## Coverage with JaCoCo
|
|
627
|
+
|
|
628
|
+
### Maven Configuration (Complete)
|
|
629
|
+
|
|
630
|
+
```xml
|
|
631
|
+
<plugin>
|
|
632
|
+
<groupId>org.jacoco</groupId>
|
|
633
|
+
<artifactId>jacoco-maven-plugin</artifactId>
|
|
634
|
+
<version>0.8.13</version>
|
|
635
|
+
<executions>
|
|
636
|
+
<!-- Prepare agent for test execution -->
|
|
637
|
+
<execution>
|
|
638
|
+
<id>prepare-agent</id>
|
|
639
|
+
<goals>
|
|
640
|
+
<goal>prepare-agent</goal>
|
|
641
|
+
</goals>
|
|
642
|
+
</execution>
|
|
643
|
+
|
|
644
|
+
<!-- Generate coverage report -->
|
|
645
|
+
<execution>
|
|
646
|
+
<id>report</id>
|
|
647
|
+
<phase>verify</phase>
|
|
648
|
+
<goals>
|
|
649
|
+
<goal>report</goal>
|
|
650
|
+
</goals>
|
|
651
|
+
</execution>
|
|
652
|
+
|
|
653
|
+
<!-- Enforce coverage thresholds -->
|
|
654
|
+
<execution>
|
|
655
|
+
<id>check</id>
|
|
656
|
+
<goals>
|
|
657
|
+
<goal>check</goal>
|
|
658
|
+
</goals>
|
|
659
|
+
<configuration>
|
|
660
|
+
<rules>
|
|
661
|
+
<rule>
|
|
662
|
+
<element>BUNDLE</element>
|
|
663
|
+
<limits>
|
|
664
|
+
<limit>
|
|
665
|
+
<counter>LINE</counter>
|
|
666
|
+
<value>COVEREDRATIO</value>
|
|
667
|
+
<minimum>0.80</minimum>
|
|
668
|
+
</limit>
|
|
669
|
+
<limit>
|
|
670
|
+
<counter>BRANCH</counter>
|
|
671
|
+
<value>COVEREDRATIO</value>
|
|
672
|
+
<minimum>0.70</minimum>
|
|
673
|
+
</limit>
|
|
674
|
+
</limits>
|
|
675
|
+
</rule>
|
|
676
|
+
</rules>
|
|
677
|
+
</configuration>
|
|
678
|
+
</execution>
|
|
679
|
+
</executions>
|
|
680
|
+
</plugin>
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
Run tests with coverage:
|
|
684
|
+
```bash
|
|
685
|
+
mvn clean test
|
|
686
|
+
mvn jacoco:report
|
|
687
|
+
mvn jacoco:check
|
|
688
|
+
|
|
689
|
+
# Report at: target/site/jacoco/index.html
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
## Test Dependencies
|
|
693
|
+
|
|
694
|
+
```xml
|
|
695
|
+
<dependencies>
|
|
696
|
+
<!-- Quarkus Testing -->
|
|
697
|
+
<dependency>
|
|
698
|
+
<groupId>io.quarkus</groupId>
|
|
699
|
+
<artifactId>quarkus-junit5</artifactId>
|
|
700
|
+
<scope>test</scope>
|
|
701
|
+
</dependency>
|
|
702
|
+
<dependency>
|
|
703
|
+
<groupId>io.quarkus</groupId>
|
|
704
|
+
<artifactId>quarkus-junit5-mockito</artifactId>
|
|
705
|
+
<scope>test</scope>
|
|
706
|
+
</dependency>
|
|
707
|
+
|
|
708
|
+
<!-- Mockito -->
|
|
709
|
+
<dependency>
|
|
710
|
+
<groupId>org.mockito</groupId>
|
|
711
|
+
<artifactId>mockito-core</artifactId>
|
|
712
|
+
<scope>test</scope>
|
|
713
|
+
</dependency>
|
|
714
|
+
|
|
715
|
+
<!-- AssertJ (preferred over JUnit assertions) -->
|
|
716
|
+
<dependency>
|
|
717
|
+
<groupId>org.assertj</groupId>
|
|
718
|
+
<artifactId>assertj-core</artifactId>
|
|
719
|
+
<version>3.24.2</version>
|
|
720
|
+
<scope>test</scope>
|
|
721
|
+
</dependency>
|
|
722
|
+
|
|
723
|
+
<!-- REST Assured -->
|
|
724
|
+
<dependency>
|
|
725
|
+
<groupId>io.rest-assured</groupId>
|
|
726
|
+
<artifactId>rest-assured</artifactId>
|
|
727
|
+
<scope>test</scope>
|
|
728
|
+
</dependency>
|
|
729
|
+
|
|
730
|
+
<!-- Camel Testing -->
|
|
731
|
+
<dependency>
|
|
732
|
+
<groupId>org.apache.camel.quarkus</groupId>
|
|
733
|
+
<artifactId>camel-quarkus-junit5</artifactId>
|
|
734
|
+
<scope>test</scope>
|
|
735
|
+
</dependency>
|
|
736
|
+
</dependencies>
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
## Best Practices
|
|
740
|
+
|
|
741
|
+
### Test Organization
|
|
742
|
+
- Use `@Nested` classes to group tests by method being tested
|
|
743
|
+
- Use `@DisplayName` for readable test descriptions visible in reports
|
|
744
|
+
- Follow `givenX_whenY_thenZ` naming convention for test methods
|
|
745
|
+
- Use `@BeforeEach` for common test data setup to reduce duplication
|
|
746
|
+
|
|
747
|
+
### Test Structure
|
|
748
|
+
- Follow AAA pattern with explicit comments (`// ARRANGE`, `// ACT`, `// ASSERT`)
|
|
749
|
+
- Use `assertDoesNotThrow` for success scenarios
|
|
750
|
+
- Use `assertThrows` for exception scenarios with message validation
|
|
751
|
+
- Verify exception messages match expected values using AssertJ `contains()` or `isEqualTo()`
|
|
752
|
+
|
|
753
|
+
### Test Coverage
|
|
754
|
+
- Test happy paths for all public methods
|
|
755
|
+
- Test null input handling
|
|
756
|
+
- Test edge cases (empty collections, boundary values, negative IDs, blank strings)
|
|
757
|
+
- Test exception scenarios comprehensively
|
|
758
|
+
- Mock all external dependencies (repositories, services, Camel endpoints)
|
|
759
|
+
- Aim for 80%+ line coverage, 70%+ branch coverage
|
|
760
|
+
|
|
761
|
+
### Assertions
|
|
762
|
+
- **Prefer AssertJ** (`assertThat`) over JUnit assertions for value checks
|
|
763
|
+
- Use fluent AssertJ API for readability: `assertThat(list).hasSize(3).contains(item)`
|
|
764
|
+
- For exceptions: use JUnit `assertThrows` to capture, then AssertJ to validate the message
|
|
765
|
+
- For non-throwing success paths: use JUnit `assertDoesNotThrow`
|
|
766
|
+
- For collections: `extracting()`, `filteredOn()`, `containsExactly()`
|
|
767
|
+
|
|
768
|
+
### Testing Integration
|
|
769
|
+
- Use `@QuarkusTest` for integration tests
|
|
770
|
+
- Use `@InjectMock` to mock dependencies in Quarkus tests
|
|
771
|
+
- Prefer REST Assured for API testing
|
|
772
|
+
- Use `@TestProfile` for test-specific configuration
|
|
773
|
+
|
|
774
|
+
### Event-Driven Testing
|
|
775
|
+
- Test Camel routes with `AdviceWith` and `MockEndpoint`
|
|
776
|
+
- Use `@CamelQuarkusTest` annotation (if using standalone Camel tests)
|
|
777
|
+
- Verify message content, headers, and routing logic
|
|
778
|
+
- Test error handling routes separately
|
|
779
|
+
- Mock external systems (RabbitMQ, S3, databases) in unit tests
|
|
780
|
+
|
|
781
|
+
### Camel Route Testing
|
|
782
|
+
- Use `MockEndpoint` for asserting message flow
|
|
783
|
+
- Use `AdviceWith` to modify routes for testing (replace endpoints with mocks)
|
|
784
|
+
- Test message transformation and marshalling
|
|
785
|
+
- Test exception handling and dead letter queues
|
|
786
|
+
|
|
787
|
+
### Testing Async Operations
|
|
788
|
+
- Test CompletableFuture success and failure scenarios
|
|
789
|
+
- Use `.join()` in tests to wait for async completion
|
|
790
|
+
- Test exception propagation from CompletableFuture
|
|
791
|
+
- Verify LogContext propagation to async operations
|
|
792
|
+
|
|
793
|
+
### Performance
|
|
794
|
+
- Keep tests fast and isolated
|
|
795
|
+
- Run tests in continuous mode: `mvn quarkus:test`
|
|
796
|
+
- Use parameterized tests (`@ParameterizedTest`) for input variations
|
|
797
|
+
- Build reusable test data builders or factory methods
|
|
798
|
+
|
|
799
|
+
### Quarkus-Specific
|
|
800
|
+
- Stay on latest LTS version (Quarkus 3.x)
|
|
801
|
+
- Test native compilation compatibility periodically
|
|
802
|
+
- Use Quarkus test profiles for different scenarios
|
|
803
|
+
- Leverage Quarkus dev services for local testing
|
|
804
|
+
- Use `@InjectMock` instead of `@MockBean` (Quarkus-specific)
|
|
805
|
+
|
|
806
|
+
### Verification Best Practices
|
|
807
|
+
- Always verify interactions on mocked dependencies
|
|
808
|
+
- Use `verify(mock, never())` to ensure methods are NOT called in error scenarios
|
|
809
|
+
- Use `argThat()` for complex argument matching
|
|
810
|
+
- Verify the order of calls when it matters: `InOrder` from Mockito
|