octocode-cli 1.2.5 → 1.2.7
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/LICENSE +21 -63
- package/README.md +86 -109
- package/out/octocode-cli.js +7027 -7014
- package/package.json +8 -6
- package/skills/README.md +97 -120
- package/skills/octocode-code-engineer/.claude/settings.local.json +18 -0
- package/skills/octocode-code-engineer/.octocode/rfc/RFC-code-engineer-weakness-fixes.md +255 -0
- package/skills/octocode-code-engineer/.plan/VALIDATED_PLAN.md +223 -0
- package/skills/octocode-code-engineer/README.md +178 -0
- package/skills/octocode-code-engineer/SKILL.md +418 -0
- package/skills/octocode-code-engineer/coverage/architecture.ts.html +7828 -0
- package/skills/octocode-code-engineer/coverage/ast-helpers.ts.html +211 -0
- package/skills/octocode-code-engineer/coverage/ast-search.ts.html +1795 -0
- package/skills/octocode-code-engineer/coverage/base.css +224 -0
- package/skills/octocode-code-engineer/coverage/block-navigation.js +87 -0
- package/skills/octocode-code-engineer/coverage/cache.ts.html +376 -0
- package/skills/octocode-code-engineer/coverage/cli.ts.html +982 -0
- package/skills/octocode-code-engineer/coverage/clover.xml +3217 -0
- package/skills/octocode-code-engineer/coverage/collect-effects.ts.html +664 -0
- package/skills/octocode-code-engineer/coverage/collect-input-sources.ts.html +577 -0
- package/skills/octocode-code-engineer/coverage/collect-performance.ts.html +331 -0
- package/skills/octocode-code-engineer/coverage/collect-prototype-pollution.ts.html +421 -0
- package/skills/octocode-code-engineer/coverage/collect-security.ts.html +604 -0
- package/skills/octocode-code-engineer/coverage/collect-test-profile.ts.html +589 -0
- package/skills/octocode-code-engineer/coverage/coverage-final.json +30 -0
- package/skills/octocode-code-engineer/coverage/dependencies.ts.html +997 -0
- package/skills/octocode-code-engineer/coverage/dependency-summary.ts.html +688 -0
- package/skills/octocode-code-engineer/coverage/discovery.ts.html +322 -0
- package/skills/octocode-code-engineer/coverage/favicon.png +0 -0
- package/skills/octocode-code-engineer/coverage/graph-analytics.ts.html +1510 -0
- package/skills/octocode-code-engineer/coverage/index.html +536 -0
- package/skills/octocode-code-engineer/coverage/index.ts.html +826 -0
- package/skills/octocode-code-engineer/coverage/metrics.ts.html +553 -0
- package/skills/octocode-code-engineer/coverage/pipeline.ts.html +2044 -0
- package/skills/octocode-code-engineer/coverage/prettify.css +1 -0
- package/skills/octocode-code-engineer/coverage/prettify.js +2 -0
- package/skills/octocode-code-engineer/coverage/report-analysis.ts.html +1570 -0
- package/skills/octocode-code-engineer/coverage/report-writer.ts.html +1102 -0
- package/skills/octocode-code-engineer/coverage/security-detectors.ts.html +1747 -0
- package/skills/octocode-code-engineer/coverage/semantic-detectors.ts.html +2152 -0
- package/skills/octocode-code-engineer/coverage/semantic.ts.html +1897 -0
- package/skills/octocode-code-engineer/coverage/sort-arrow-sprite.png +0 -0
- package/skills/octocode-code-engineer/coverage/sorter.js +210 -0
- package/skills/octocode-code-engineer/coverage/summary-md.ts.html +1222 -0
- package/skills/octocode-code-engineer/coverage/test-quality-detectors.ts.html +1039 -0
- package/skills/octocode-code-engineer/coverage/tree-sitter-analyzer.ts.html +955 -0
- package/skills/octocode-code-engineer/coverage/ts-analyzer.ts.html +1213 -0
- package/skills/octocode-code-engineer/coverage/types.ts.html +2473 -0
- package/skills/octocode-code-engineer/coverage/utils.ts.html +820 -0
- package/skills/octocode-code-engineer/eslint.config.mjs +54 -0
- package/skills/octocode-code-engineer/minify-scripts.mjs +32 -0
- package/skills/octocode-code-engineer/package.json +54 -0
- package/skills/octocode-code-engineer/references/agent-ast-reading-rfc.md +95 -0
- package/skills/octocode-code-engineer/references/architecture-techniques.md +121 -0
- package/skills/octocode-code-engineer/references/ast-search.md +210 -0
- package/skills/octocode-code-engineer/references/ast-tree-search.md +151 -0
- package/skills/octocode-code-engineer/references/cli-reference.md +167 -0
- package/skills/octocode-code-engineer/references/concepts.md +107 -0
- package/skills/octocode-code-engineer/references/finding-categories.md +128 -0
- package/skills/octocode-code-engineer/references/improvement-roadmap.md +304 -0
- package/skills/octocode-code-engineer/references/output-files.md +144 -0
- package/skills/octocode-code-engineer/references/playbooks.md +204 -0
- package/skills/octocode-code-engineer/references/present-results.md +136 -0
- package/skills/octocode-code-engineer/references/tool-workflows.md +566 -0
- package/skills/octocode-code-engineer/references/validate-investigate.md +225 -0
- package/skills/octocode-code-engineer/scripts/analysis/dependencies.js +1 -0
- package/skills/octocode-code-engineer/scripts/analysis/dependency-summary.js +1 -0
- package/skills/octocode-code-engineer/scripts/analysis/discovery.js +1 -0
- package/skills/octocode-code-engineer/scripts/analysis/graph-analytics.js +1 -0
- package/skills/octocode-code-engineer/scripts/analysis/semantic.js +1 -0
- package/skills/octocode-code-engineer/scripts/ast/helpers.js +1 -0
- package/skills/octocode-code-engineer/scripts/ast/metrics.js +1 -0
- package/skills/octocode-code-engineer/scripts/ast/search.js +2 -0
- package/skills/octocode-code-engineer/scripts/ast/tree-search.js +2 -0
- package/skills/octocode-code-engineer/scripts/ast/tree-sitter.js +1 -0
- package/skills/octocode-code-engineer/scripts/ast/ts-analyzer.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/chains.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/effects.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/input-sources.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/performance.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/prototype-pollution.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/security.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/test-profile.js +1 -0
- package/skills/octocode-code-engineer/scripts/common/is-direct-run.js +1 -0
- package/skills/octocode-code-engineer/scripts/common/utils.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/code-quality.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/cohesion.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/coupling.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/cycle.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/dead-code.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/import-style.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/index.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/security.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/semantic.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/shared.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/test-quality.js +1 -0
- package/skills/octocode-code-engineer/scripts/index.js +1 -0
- package/skills/octocode-code-engineer/scripts/pipeline/cache.js +1 -0
- package/skills/octocode-code-engineer/scripts/pipeline/cli.js +1 -0
- package/skills/octocode-code-engineer/scripts/pipeline/main.js +2 -0
- package/skills/octocode-code-engineer/scripts/reporting/analysis.js +1 -0
- package/skills/octocode-code-engineer/scripts/reporting/summary-md.js +1 -0
- package/skills/octocode-code-engineer/scripts/reporting/writer.js +1 -0
- package/skills/octocode-code-engineer/scripts/types/constants.js +1 -0
- package/skills/octocode-code-engineer/scripts/types/index.js +1 -0
- package/skills/octocode-code-engineer/scripts/types/interfaces.js +1 -0
- package/skills/octocode-code-engineer/src/analysis/dependencies.test.ts +545 -0
- package/skills/octocode-code-engineer/src/analysis/dependencies.ts +406 -0
- package/skills/octocode-code-engineer/src/analysis/dependency-summary.test.ts +566 -0
- package/skills/octocode-code-engineer/src/analysis/dependency-summary.ts +257 -0
- package/skills/octocode-code-engineer/src/analysis/discovery.test.ts +420 -0
- package/skills/octocode-code-engineer/src/analysis/discovery.ts +87 -0
- package/skills/octocode-code-engineer/src/analysis/graph-analytics.test.ts +449 -0
- package/skills/octocode-code-engineer/src/analysis/graph-analytics.ts +534 -0
- package/skills/octocode-code-engineer/src/analysis/semantic.test.ts +1533 -0
- package/skills/octocode-code-engineer/src/analysis/semantic.ts +830 -0
- package/skills/octocode-code-engineer/src/ast/helpers.test.ts +185 -0
- package/skills/octocode-code-engineer/src/ast/helpers.ts +62 -0
- package/skills/octocode-code-engineer/src/ast/metrics.test.ts +304 -0
- package/skills/octocode-code-engineer/src/ast/metrics.ts +204 -0
- package/skills/octocode-code-engineer/src/ast/search.test.ts +647 -0
- package/skills/octocode-code-engineer/src/ast/search.ts +648 -0
- package/skills/octocode-code-engineer/src/ast/tree-search.test.ts +199 -0
- package/skills/octocode-code-engineer/src/ast/tree-search.ts +392 -0
- package/skills/octocode-code-engineer/src/ast/tree-sitter.test.ts +407 -0
- package/skills/octocode-code-engineer/src/ast/tree-sitter.ts +402 -0
- package/skills/octocode-code-engineer/src/ast/ts-analyzer.test.ts +1864 -0
- package/skills/octocode-code-engineer/src/ast/ts-analyzer.ts +509 -0
- package/skills/octocode-code-engineer/src/collectors/chains.ts +74 -0
- package/skills/octocode-code-engineer/src/collectors/effects.test.ts +490 -0
- package/skills/octocode-code-engineer/src/collectors/effects.ts +332 -0
- package/skills/octocode-code-engineer/src/collectors/input-sources.test.ts +144 -0
- package/skills/octocode-code-engineer/src/collectors/input-sources.ts +196 -0
- package/skills/octocode-code-engineer/src/collectors/performance.test.ts +82 -0
- package/skills/octocode-code-engineer/src/collectors/performance.ts +141 -0
- package/skills/octocode-code-engineer/src/collectors/prototype-pollution.test.ts +55 -0
- package/skills/octocode-code-engineer/src/collectors/prototype-pollution.ts +162 -0
- package/skills/octocode-code-engineer/src/collectors/security.test.ts +124 -0
- package/skills/octocode-code-engineer/src/collectors/security.ts +309 -0
- package/skills/octocode-code-engineer/src/collectors/test-profile.test.ts +97 -0
- package/skills/octocode-code-engineer/src/collectors/test-profile.ts +269 -0
- package/skills/octocode-code-engineer/src/common/is-direct-run.test.ts +32 -0
- package/skills/octocode-code-engineer/src/common/is-direct-run.ts +13 -0
- package/skills/octocode-code-engineer/src/common/utils.test.ts +463 -0
- package/skills/octocode-code-engineer/src/common/utils.ts +304 -0
- package/skills/octocode-code-engineer/src/detectors/code-quality.ts +966 -0
- package/skills/octocode-code-engineer/src/detectors/cohesion.ts +539 -0
- package/skills/octocode-code-engineer/src/detectors/coupling.ts +323 -0
- package/skills/octocode-code-engineer/src/detectors/cycle.ts +349 -0
- package/skills/octocode-code-engineer/src/detectors/dead-code.ts +320 -0
- package/skills/octocode-code-engineer/src/detectors/import-style.ts +376 -0
- package/skills/octocode-code-engineer/src/detectors/index.test.ts +3061 -0
- package/skills/octocode-code-engineer/src/detectors/index.ts +88 -0
- package/skills/octocode-code-engineer/src/detectors/security.test.ts +882 -0
- package/skills/octocode-code-engineer/src/detectors/security.ts +821 -0
- package/skills/octocode-code-engineer/src/detectors/semantic.ts +758 -0
- package/skills/octocode-code-engineer/src/detectors/shared.ts +49 -0
- package/skills/octocode-code-engineer/src/detectors/test-quality.test.ts +388 -0
- package/skills/octocode-code-engineer/src/detectors/test-quality.ts +367 -0
- package/skills/octocode-code-engineer/src/index.test.ts +4425 -0
- package/skills/octocode-code-engineer/src/index.ts +403 -0
- package/skills/octocode-code-engineer/src/pipeline/cache.test.ts +199 -0
- package/skills/octocode-code-engineer/src/pipeline/cache.ts +130 -0
- package/skills/octocode-code-engineer/src/pipeline/cli.test.ts +493 -0
- package/skills/octocode-code-engineer/src/pipeline/cli.ts +344 -0
- package/skills/octocode-code-engineer/src/pipeline/main.test.ts +174 -0
- package/skills/octocode-code-engineer/src/pipeline/main.ts +1074 -0
- package/skills/octocode-code-engineer/src/pipeline.test.ts +84 -0
- package/skills/octocode-code-engineer/src/reporting/analysis.test.ts +782 -0
- package/skills/octocode-code-engineer/src/reporting/analysis.ts +688 -0
- package/skills/octocode-code-engineer/src/reporting/output-contract.test.ts +463 -0
- package/skills/octocode-code-engineer/src/reporting/summary-md.test.ts +421 -0
- package/skills/octocode-code-engineer/src/reporting/summary-md.ts +714 -0
- package/skills/octocode-code-engineer/src/reporting/writer.ts +430 -0
- package/skills/octocode-code-engineer/src/sanity.test.ts +47 -0
- package/skills/octocode-code-engineer/src/types/constants.ts +248 -0
- package/skills/octocode-code-engineer/src/types/index.ts +80 -0
- package/skills/octocode-code-engineer/src/types/interfaces.ts +682 -0
- package/skills/octocode-code-engineer/tsconfig.json +17 -0
- package/skills/octocode-code-engineer/vitest.config.ts +8 -0
- package/skills/octocode-documentation-writer/README.md +113 -0
- package/skills/octocode-documentation-writer/SKILL.md +886 -0
- package/skills/octocode-documentation-writer/references/agent-discovery-analysis.md +453 -0
- package/skills/octocode-documentation-writer/references/agent-documentation-writer.md +255 -0
- package/skills/octocode-documentation-writer/references/agent-engineer-questions.md +247 -0
- package/skills/octocode-documentation-writer/references/agent-orchestrator.md +370 -0
- package/skills/octocode-documentation-writer/references/agent-qa-validator.md +227 -0
- package/skills/octocode-documentation-writer/references/agent-researcher.md +250 -0
- package/skills/octocode-documentation-writer/schemas/analysis-schema.json +886 -0
- package/skills/octocode-documentation-writer/schemas/discovery-tasks.json +96 -0
- package/skills/octocode-documentation-writer/schemas/documentation-structure.json +373 -0
- package/skills/octocode-documentation-writer/schemas/partial-discovery-schema.json +102 -0
- package/skills/octocode-documentation-writer/schemas/partial-research-schema.json +98 -0
- package/skills/octocode-documentation-writer/schemas/qa-results-schema.json +113 -0
- package/skills/octocode-documentation-writer/schemas/questions-schema.json +228 -0
- package/skills/octocode-documentation-writer/schemas/research-schema.json +104 -0
- package/skills/octocode-documentation-writer/schemas/state-schema.json +222 -0
- package/skills/octocode-documentation-writer/schemas/work-assignments-schema.json +74 -0
- package/skills/octocode-plan/SKILL.md +122 -116
- package/skills/octocode-prompt-optimizer/SKILL.md +617 -0
- package/skills/octocode-pull-request-reviewer/README.md +249 -0
- package/skills/octocode-pull-request-reviewer/SKILL.md +479 -0
- package/skills/octocode-pull-request-reviewer/references/dependency-check.md +74 -0
- package/skills/octocode-pull-request-reviewer/references/domain-reviewers.md +24 -0
- package/skills/octocode-pull-request-reviewer/references/execution-lifecycle.md +441 -0
- package/skills/octocode-pull-request-reviewer/references/flow-analysis-protocol.md +64 -0
- package/skills/octocode-pull-request-reviewer/references/output-template.md +174 -0
- package/skills/octocode-pull-request-reviewer/references/parallel-agent-protocol.md +182 -0
- package/skills/octocode-pull-request-reviewer/references/review-guidelines.md +26 -0
- package/skills/octocode-pull-request-reviewer/references/verification-checklist.md +40 -0
- package/skills/octocode-research/.claude/settings.local.json +46 -0
- package/skills/octocode-research/.octocode/plan/code-review-fixes/plan.md +312 -0
- package/skills/octocode-research/.octocode/plan/code-review-fixes/research.md +212 -0
- package/skills/octocode-research/.octocode/plans/NODE_SERVER_START_PLAN.md +755 -0
- package/skills/octocode-research/.octocode/research/code-review/research.md +371 -0
- package/skills/octocode-research/.octocode/review/IMPROVEMENTS.md +391 -0
- package/skills/octocode-research/.octocode/review/REVIEW_PLAN.md +289 -0
- package/skills/octocode-research/.octocode/review/REVIEW_REPORT.md +356 -0
- package/skills/octocode-research/AGENTS.md +349 -0
- package/skills/octocode-research/README.md +494 -0
- package/skills/octocode-research/SKILL.md +652 -274
- package/skills/octocode-research/docs/API_REFERENCE.md +562 -0
- package/skills/octocode-research/docs/ARCHITECTURE.md +554 -0
- package/skills/octocode-research/docs/FLOWS.md +577 -0
- package/skills/octocode-research/docs/OVERVIEW.md +564 -0
- package/skills/octocode-research/docs/SERVER_FLOWS.md +631 -0
- package/skills/octocode-research/ecosystem.config.cjs +88 -0
- package/skills/octocode-research/eslint.config.mjs +27 -0
- package/skills/octocode-research/package.json +84 -0
- package/skills/octocode-research/references/GUARDRAILS.md +40 -0
- package/skills/octocode-research/references/PARALLEL_AGENT_PROTOCOL.md +178 -0
- package/skills/octocode-research/references/roast-prompt.md +149 -0
- package/skills/octocode-research/scripts/server-init.d.ts +2 -0
- package/skills/octocode-research/scripts/server-init.js +2 -0
- package/skills/octocode-research/scripts/server.d.ts +8 -0
- package/skills/octocode-research/scripts/server.js +445 -0
- package/skills/octocode-research/src/__tests__/integration/circuitBreaker.test.ts +205 -0
- package/skills/octocode-research/src/__tests__/integration/routes.test.ts +374 -0
- package/skills/octocode-research/src/__tests__/unit/circuitBreaker.test.ts +245 -0
- package/skills/octocode-research/src/__tests__/unit/errorHandler.test.ts +183 -0
- package/skills/octocode-research/src/__tests__/unit/httpPreprocess.test.ts +157 -0
- package/skills/octocode-research/src/__tests__/unit/logger.test.ts +143 -0
- package/skills/octocode-research/src/__tests__/unit/queryParser.test.ts +130 -0
- package/skills/octocode-research/src/__tests__/unit/responseBuilder.test.ts +469 -0
- package/skills/octocode-research/src/__tests__/unit/retry.test.ts +205 -0
- package/skills/octocode-research/src/index.ts +186 -0
- package/skills/octocode-research/src/mcpCache.ts +49 -0
- package/skills/octocode-research/src/middleware/errorHandler.ts +65 -0
- package/skills/octocode-research/src/middleware/logger.ts +61 -0
- package/skills/octocode-research/src/middleware/queryParser.ts +115 -0
- package/skills/octocode-research/src/middleware/readiness.ts +17 -0
- package/skills/octocode-research/src/routes/github.ts +197 -0
- package/skills/octocode-research/src/routes/local.ts +175 -0
- package/skills/octocode-research/src/routes/lsp.ts +177 -0
- package/skills/octocode-research/src/routes/package.ts +127 -0
- package/skills/octocode-research/src/routes/prompts.ts +138 -0
- package/skills/octocode-research/src/routes/tools.ts +677 -0
- package/skills/octocode-research/src/server-init.ts +363 -0
- package/skills/octocode-research/src/server.ts +285 -0
- package/skills/octocode-research/src/types/errorGuards.ts +151 -0
- package/skills/octocode-research/src/types/express.d.ts +76 -0
- package/skills/octocode-research/src/types/guards.ts +98 -0
- package/skills/octocode-research/src/types/mcp.ts +119 -0
- package/skills/octocode-research/src/types/responses.ts +199 -0
- package/skills/octocode-research/src/types/toolTypes.ts +33 -0
- package/skills/octocode-research/src/utils/asyncTimeout.ts +116 -0
- package/skills/octocode-research/src/utils/circuitBreaker.ts +492 -0
- package/skills/octocode-research/src/utils/colors.ts +53 -0
- package/skills/octocode-research/src/utils/errorQueue.ts +71 -0
- package/skills/octocode-research/src/utils/logEmoji.ts +103 -0
- package/skills/octocode-research/src/utils/logger.ts +413 -0
- package/skills/octocode-research/src/utils/resilience.ts +169 -0
- package/skills/octocode-research/src/utils/responseBuilder.ts +495 -0
- package/skills/octocode-research/src/utils/responseFactory.ts +100 -0
- package/skills/octocode-research/src/utils/responseParser.ts +272 -0
- package/skills/octocode-research/src/utils/retry.ts +280 -0
- package/skills/octocode-research/src/utils/routeFactory.ts +117 -0
- package/skills/octocode-research/src/utils/url.ts +20 -0
- package/skills/octocode-research/src/validation/httpPreprocess.ts +155 -0
- package/skills/octocode-research/src/validation/index.ts +2 -0
- package/skills/octocode-research/src/validation/schemas.ts +578 -0
- package/skills/octocode-research/src/validation/toolCallSchema.ts +132 -0
- package/skills/octocode-research/tsconfig.json +21 -0
- package/skills/octocode-research/tsdown.config.ts +42 -0
- package/skills/octocode-research/vitest.config.ts +20 -0
- package/skills/octocode-researcher/SKILL.md +461 -0
- package/skills/octocode-researcher/references/fallbacks.md +120 -0
- package/skills/{octocode-local-search → octocode-researcher}/references/tool-reference.md +132 -49
- package/skills/{octocode-local-search → octocode-researcher}/references/workflow-patterns.md +204 -4
- package/skills/octocode-rfc-generator/SKILL.md +223 -0
- package/skills/octocode-rfc-generator/references/rfc-template.md +193 -0
- package/skills/octocode-roast/SKILL.md +63 -21
- package/skills/octocode-implement/SKILL.md +0 -293
- package/skills/octocode-implement/references/execution-phases.md +0 -317
- package/skills/octocode-implement/references/tool-reference.md +0 -403
- package/skills/octocode-implement/references/workflow-patterns.md +0 -385
- package/skills/octocode-local-search/SKILL.md +0 -449
- package/skills/octocode-pr-review/SKILL.md +0 -391
- package/skills/octocode-pr-review/references/domain-reviewers.md +0 -105
- package/skills/octocode-pr-review/references/execution-lifecycle.md +0 -116
- package/skills/octocode-pr-review/references/research-flows.md +0 -75
- package/skills/octocode-research/references/tool-reference.md +0 -304
- package/skills/octocode-research/references/workflow-patterns.md +0 -325
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for circuit breaker utilities.
|
|
3
|
+
*
|
|
4
|
+
* @module tests/unit/circuitBreaker
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
8
|
+
import {
|
|
9
|
+
withCircuitBreaker,
|
|
10
|
+
getCircuitState,
|
|
11
|
+
resetCircuit,
|
|
12
|
+
configureCircuit,
|
|
13
|
+
CircuitOpenError,
|
|
14
|
+
getAllCircuitStates,
|
|
15
|
+
} from '../../utils/circuitBreaker.js';
|
|
16
|
+
|
|
17
|
+
describe('withCircuitBreaker', () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
// Reset test circuit before each test
|
|
20
|
+
resetCircuit('test');
|
|
21
|
+
vi.useFakeTimers();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
vi.useRealTimers();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('executes operation successfully when circuit is closed', async () => {
|
|
29
|
+
const operation = vi.fn().mockResolvedValue('success');
|
|
30
|
+
|
|
31
|
+
const result = await withCircuitBreaker('test', operation);
|
|
32
|
+
|
|
33
|
+
expect(result).toBe('success');
|
|
34
|
+
expect(operation).toHaveBeenCalledTimes(1);
|
|
35
|
+
expect(getCircuitState('test').state).toBe('closed');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('opens circuit after failure threshold', async () => {
|
|
39
|
+
configureCircuit('test', { failureThreshold: 3 });
|
|
40
|
+
const operation = vi.fn().mockRejectedValue(new Error('fail'));
|
|
41
|
+
|
|
42
|
+
// First 3 failures should open the circuit
|
|
43
|
+
for (let i = 0; i < 3; i++) {
|
|
44
|
+
await expect(withCircuitBreaker('test', operation)).rejects.toThrow('fail');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
expect(getCircuitState('test').state).toBe('open');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('uses fallback when circuit is open', async () => {
|
|
51
|
+
configureCircuit('test', { failureThreshold: 1, resetTimeoutMs: 10000 });
|
|
52
|
+
const operation = vi.fn().mockRejectedValue(new Error('fail'));
|
|
53
|
+
const fallback = vi.fn().mockReturnValue('fallback');
|
|
54
|
+
|
|
55
|
+
// Open the circuit
|
|
56
|
+
await expect(withCircuitBreaker('test', operation)).rejects.toThrow();
|
|
57
|
+
|
|
58
|
+
// Should use fallback
|
|
59
|
+
const result = await withCircuitBreaker('test', operation, fallback);
|
|
60
|
+
|
|
61
|
+
expect(result).toBe('fallback');
|
|
62
|
+
expect(fallback).toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('throws CircuitOpenError when open and no fallback', async () => {
|
|
66
|
+
configureCircuit('test', { failureThreshold: 1, resetTimeoutMs: 10000 });
|
|
67
|
+
const operation = vi.fn().mockRejectedValue(new Error('fail'));
|
|
68
|
+
|
|
69
|
+
// Open the circuit
|
|
70
|
+
await expect(withCircuitBreaker('test', operation)).rejects.toThrow();
|
|
71
|
+
|
|
72
|
+
// Should throw CircuitOpenError
|
|
73
|
+
await expect(withCircuitBreaker('test', operation)).rejects.toBeInstanceOf(
|
|
74
|
+
CircuitOpenError
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('enters half-open state after reset timeout', async () => {
|
|
79
|
+
configureCircuit('test', { failureThreshold: 1, resetTimeoutMs: 1000 });
|
|
80
|
+
const operation = vi
|
|
81
|
+
.fn()
|
|
82
|
+
.mockRejectedValueOnce(new Error('fail'))
|
|
83
|
+
.mockResolvedValue('success');
|
|
84
|
+
|
|
85
|
+
// Open the circuit
|
|
86
|
+
await expect(withCircuitBreaker('test', operation)).rejects.toThrow();
|
|
87
|
+
expect(getCircuitState('test').state).toBe('open');
|
|
88
|
+
|
|
89
|
+
// Advance time past reset timeout
|
|
90
|
+
vi.advanceTimersByTime(1001);
|
|
91
|
+
|
|
92
|
+
// Next attempt should try (half-open)
|
|
93
|
+
const result = await withCircuitBreaker('test', operation);
|
|
94
|
+
expect(result).toBe('success');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('closes circuit after success threshold in half-open', async () => {
|
|
98
|
+
configureCircuit('test', {
|
|
99
|
+
failureThreshold: 1,
|
|
100
|
+
successThreshold: 2,
|
|
101
|
+
resetTimeoutMs: 1000,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const failOp = vi.fn().mockRejectedValue(new Error('fail'));
|
|
105
|
+
const successOp = vi.fn().mockResolvedValue('success');
|
|
106
|
+
|
|
107
|
+
// Open the circuit
|
|
108
|
+
await expect(withCircuitBreaker('test', failOp)).rejects.toThrow();
|
|
109
|
+
|
|
110
|
+
// Advance time to half-open
|
|
111
|
+
vi.advanceTimersByTime(1001);
|
|
112
|
+
|
|
113
|
+
// First success - still half-open
|
|
114
|
+
await withCircuitBreaker('test', successOp);
|
|
115
|
+
expect(getCircuitState('test').state).toBe('half-open');
|
|
116
|
+
|
|
117
|
+
// Second success - should close
|
|
118
|
+
await withCircuitBreaker('test', successOp);
|
|
119
|
+
expect(getCircuitState('test').state).toBe('closed');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('reopens circuit if half-open attempt fails', async () => {
|
|
123
|
+
configureCircuit('test', { failureThreshold: 1, resetTimeoutMs: 1000 });
|
|
124
|
+
|
|
125
|
+
const operation = vi.fn().mockRejectedValue(new Error('fail'));
|
|
126
|
+
|
|
127
|
+
// Open the circuit
|
|
128
|
+
await expect(withCircuitBreaker('test', operation)).rejects.toThrow();
|
|
129
|
+
|
|
130
|
+
// Advance time to half-open
|
|
131
|
+
vi.advanceTimersByTime(1001);
|
|
132
|
+
|
|
133
|
+
// Fail in half-open - should go back to open
|
|
134
|
+
await expect(withCircuitBreaker('test', operation)).rejects.toThrow();
|
|
135
|
+
expect(getCircuitState('test').state).toBe('open');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('resets failures on success in closed state', async () => {
|
|
139
|
+
configureCircuit('test', { failureThreshold: 3 });
|
|
140
|
+
|
|
141
|
+
const failOp = vi.fn().mockRejectedValue(new Error('fail'));
|
|
142
|
+
const successOp = vi.fn().mockResolvedValue('success');
|
|
143
|
+
|
|
144
|
+
// 2 failures
|
|
145
|
+
await expect(withCircuitBreaker('test', failOp)).rejects.toThrow();
|
|
146
|
+
await expect(withCircuitBreaker('test', failOp)).rejects.toThrow();
|
|
147
|
+
expect(getCircuitState('test').failures).toBe(2);
|
|
148
|
+
|
|
149
|
+
// 1 success resets failures
|
|
150
|
+
await withCircuitBreaker('test', successOp);
|
|
151
|
+
expect(getCircuitState('test').failures).toBe(0);
|
|
152
|
+
|
|
153
|
+
// Still closed
|
|
154
|
+
expect(getCircuitState('test').state).toBe('closed');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('getCircuitState', () => {
|
|
159
|
+
beforeEach(() => {
|
|
160
|
+
resetCircuit('test');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('returns initial state for new circuit', () => {
|
|
164
|
+
const state = getCircuitState('new-circuit');
|
|
165
|
+
|
|
166
|
+
expect(state.state).toBe('closed');
|
|
167
|
+
expect(state.failures).toBe(0);
|
|
168
|
+
expect(state.isHealthy).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('returns accurate failure count', async () => {
|
|
172
|
+
configureCircuit('test', { failureThreshold: 5 });
|
|
173
|
+
const operation = vi.fn().mockRejectedValue(new Error('fail'));
|
|
174
|
+
|
|
175
|
+
await expect(withCircuitBreaker('test', operation)).rejects.toThrow();
|
|
176
|
+
await expect(withCircuitBreaker('test', operation)).rejects.toThrow();
|
|
177
|
+
|
|
178
|
+
const state = getCircuitState('test');
|
|
179
|
+
expect(state.failures).toBe(2);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('reports unhealthy when circuit is open', async () => {
|
|
183
|
+
configureCircuit('test', { failureThreshold: 1 });
|
|
184
|
+
const operation = vi.fn().mockRejectedValue(new Error('fail'));
|
|
185
|
+
|
|
186
|
+
await expect(withCircuitBreaker('test', operation)).rejects.toThrow();
|
|
187
|
+
|
|
188
|
+
const state = getCircuitState('test');
|
|
189
|
+
expect(state.isHealthy).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('resetCircuit', () => {
|
|
194
|
+
it('resets circuit to closed state', async () => {
|
|
195
|
+
configureCircuit('test', { failureThreshold: 1 });
|
|
196
|
+
const operation = vi.fn().mockRejectedValue(new Error('fail'));
|
|
197
|
+
|
|
198
|
+
// Open the circuit
|
|
199
|
+
await expect(withCircuitBreaker('test', operation)).rejects.toThrow();
|
|
200
|
+
expect(getCircuitState('test').state).toBe('open');
|
|
201
|
+
|
|
202
|
+
// Reset
|
|
203
|
+
resetCircuit('test');
|
|
204
|
+
|
|
205
|
+
expect(getCircuitState('test').state).toBe('closed');
|
|
206
|
+
expect(getCircuitState('test').failures).toBe(0);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('getAllCircuitStates', () => {
|
|
211
|
+
beforeEach(() => {
|
|
212
|
+
resetCircuit('circuit-a');
|
|
213
|
+
resetCircuit('circuit-b');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('returns states for all known circuits', async () => {
|
|
217
|
+
configureCircuit('circuit-a', { failureThreshold: 1 });
|
|
218
|
+
const failOp = vi.fn().mockRejectedValue(new Error('fail'));
|
|
219
|
+
const successOp = vi.fn().mockResolvedValue('success');
|
|
220
|
+
|
|
221
|
+
// Open circuit-a
|
|
222
|
+
await expect(withCircuitBreaker('circuit-a', failOp)).rejects.toThrow();
|
|
223
|
+
|
|
224
|
+
// Keep circuit-b closed
|
|
225
|
+
await withCircuitBreaker('circuit-b', successOp);
|
|
226
|
+
|
|
227
|
+
const states = getAllCircuitStates();
|
|
228
|
+
|
|
229
|
+
expect(states['circuit-a'].state).toBe('open');
|
|
230
|
+
expect(states['circuit-a'].isHealthy).toBe(false);
|
|
231
|
+
expect(states['circuit-b'].state).toBe('closed');
|
|
232
|
+
expect(states['circuit-b'].isHealthy).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe('CircuitOpenError', () => {
|
|
237
|
+
it('contains circuit name and retry info', () => {
|
|
238
|
+
const error = new CircuitOpenError('test', 5000);
|
|
239
|
+
|
|
240
|
+
expect(error.circuitName).toBe('test');
|
|
241
|
+
expect(error.retryAfterMs).toBe(5000);
|
|
242
|
+
expect(error.message).toContain('test');
|
|
243
|
+
expect(error.message).toContain('5s');
|
|
244
|
+
});
|
|
245
|
+
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for error handler middleware.
|
|
3
|
+
*
|
|
4
|
+
* @module tests/unit/errorHandler
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
8
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
9
|
+
import { errorHandler, type ApiError } from '../../middleware/errorHandler.js';
|
|
10
|
+
import type { z } from 'zod/v4';
|
|
11
|
+
import { fireAndForgetWithTimeout } from '../../utils/asyncTimeout.js';
|
|
12
|
+
|
|
13
|
+
// Mock the logger
|
|
14
|
+
vi.mock('../../utils/logger.js', () => ({
|
|
15
|
+
logError: vi.fn(),
|
|
16
|
+
logWarn: vi.fn(),
|
|
17
|
+
sanitizeQueryParams: vi.fn((q) => q),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
// Mock the session error logging
|
|
21
|
+
vi.mock('../../index.js', () => ({
|
|
22
|
+
logSessionError: vi.fn().mockResolvedValue(undefined),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
// Mock the async timeout
|
|
26
|
+
vi.mock('../../utils/asyncTimeout.js', () => ({
|
|
27
|
+
fireAndForgetWithTimeout: vi.fn(),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
describe('errorHandler', () => {
|
|
31
|
+
let mockReq: Partial<Request>;
|
|
32
|
+
let mockRes: Partial<Response>;
|
|
33
|
+
let mockNext: NextFunction;
|
|
34
|
+
let jsonMock: ReturnType<typeof vi.fn>;
|
|
35
|
+
let statusMock: ReturnType<typeof vi.fn>;
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
jsonMock = vi.fn();
|
|
39
|
+
statusMock = vi.fn().mockReturnValue({ json: jsonMock });
|
|
40
|
+
|
|
41
|
+
mockReq = {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
path: '/tools/call/localSearchCode',
|
|
44
|
+
query: {},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
mockRes = {
|
|
48
|
+
status: statusMock as unknown as Response['status'],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
mockNext = vi.fn();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('status code handling', () => {
|
|
55
|
+
it('uses error statusCode if provided', () => {
|
|
56
|
+
const error: ApiError = new Error('Bad request');
|
|
57
|
+
error.statusCode = 400;
|
|
58
|
+
|
|
59
|
+
errorHandler(error, mockReq as Request, mockRes as Response, mockNext);
|
|
60
|
+
|
|
61
|
+
expect(statusMock).toHaveBeenCalledWith(400);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('defaults to 500 for server errors', () => {
|
|
65
|
+
const error: ApiError = new Error('Internal error');
|
|
66
|
+
|
|
67
|
+
errorHandler(error, mockReq as Request, mockRes as Response, mockNext);
|
|
68
|
+
|
|
69
|
+
expect(statusMock).toHaveBeenCalledWith(500);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('response format', () => {
|
|
74
|
+
it('returns success: false', () => {
|
|
75
|
+
const error: ApiError = new Error('Test error');
|
|
76
|
+
|
|
77
|
+
errorHandler(error, mockReq as Request, mockRes as Response, mockNext);
|
|
78
|
+
|
|
79
|
+
expect(jsonMock).toHaveBeenCalledWith(
|
|
80
|
+
expect.objectContaining({
|
|
81
|
+
success: false,
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('includes error message', () => {
|
|
87
|
+
const error: ApiError = new Error('Test error message');
|
|
88
|
+
|
|
89
|
+
errorHandler(error, mockReq as Request, mockRes as Response, mockNext);
|
|
90
|
+
|
|
91
|
+
expect(jsonMock).toHaveBeenCalledWith(
|
|
92
|
+
expect.objectContaining({
|
|
93
|
+
error: expect.objectContaining({
|
|
94
|
+
message: 'Test error message',
|
|
95
|
+
}),
|
|
96
|
+
})
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('uses error code if provided', () => {
|
|
101
|
+
const error: ApiError = new Error('Test error');
|
|
102
|
+
error.code = 'CUSTOM_ERROR';
|
|
103
|
+
|
|
104
|
+
errorHandler(error, mockReq as Request, mockRes as Response, mockNext);
|
|
105
|
+
|
|
106
|
+
expect(jsonMock).toHaveBeenCalledWith(
|
|
107
|
+
expect.objectContaining({
|
|
108
|
+
error: expect.objectContaining({
|
|
109
|
+
code: 'CUSTOM_ERROR',
|
|
110
|
+
}),
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('defaults to INTERNAL_ERROR code', () => {
|
|
116
|
+
const error: ApiError = new Error('Test error');
|
|
117
|
+
|
|
118
|
+
errorHandler(error, mockReq as Request, mockRes as Response, mockNext);
|
|
119
|
+
|
|
120
|
+
expect(jsonMock).toHaveBeenCalledWith(
|
|
121
|
+
expect.objectContaining({
|
|
122
|
+
error: expect.objectContaining({
|
|
123
|
+
code: 'INTERNAL_ERROR',
|
|
124
|
+
}),
|
|
125
|
+
})
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('validation error details', () => {
|
|
131
|
+
it('includes details for 400 errors', () => {
|
|
132
|
+
const error: ApiError = new Error('Validation failed');
|
|
133
|
+
error.statusCode = 400;
|
|
134
|
+
error.details = [
|
|
135
|
+
{ path: ['queries'], message: 'Required', code: 'invalid_type', expected: 'array' } as z.core.$ZodIssue,
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
errorHandler(error, mockReq as Request, mockRes as Response, mockNext);
|
|
139
|
+
|
|
140
|
+
expect(jsonMock).toHaveBeenCalledWith(
|
|
141
|
+
expect.objectContaining({
|
|
142
|
+
error: expect.objectContaining({
|
|
143
|
+
details: expect.arrayContaining([
|
|
144
|
+
expect.objectContaining({ path: ['queries'] }),
|
|
145
|
+
]),
|
|
146
|
+
}),
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('excludes details for 500 errors', () => {
|
|
152
|
+
const error: ApiError = new Error('Server error');
|
|
153
|
+
error.details = [{ path: ['internal'], message: 'Debug info', code: 'custom' } as z.core.$ZodIssue];
|
|
154
|
+
|
|
155
|
+
errorHandler(error, mockReq as Request, mockRes as Response, mockNext);
|
|
156
|
+
|
|
157
|
+
const response = jsonMock.mock.calls[0][0];
|
|
158
|
+
expect(response.error.details).toBeUndefined();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('tool name extraction', () => {
|
|
163
|
+
it('extracts tool name from /tools/call/:toolName path', () => {
|
|
164
|
+
mockReq.path = '/tools/call/localSearchCode';
|
|
165
|
+
const error: ApiError = new Error('Test');
|
|
166
|
+
|
|
167
|
+
errorHandler(error, mockReq as Request, mockRes as Response, mockNext);
|
|
168
|
+
|
|
169
|
+
// Verify fireAndForgetWithTimeout was called (tool name is passed to logSessionError)
|
|
170
|
+
expect(fireAndForgetWithTimeout).toHaveBeenCalled();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('uses unknown for non-tool paths', () => {
|
|
174
|
+
mockReq.path = '/prompts/list';
|
|
175
|
+
const error: ApiError = new Error('Test');
|
|
176
|
+
|
|
177
|
+
errorHandler(error, mockReq as Request, mockRes as Response, mockNext);
|
|
178
|
+
|
|
179
|
+
// Tool name should be 'unknown' for non-tool paths
|
|
180
|
+
expect(fireAndForgetWithTimeout).toHaveBeenCalled();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for HTTP preprocessing utilities.
|
|
3
|
+
*
|
|
4
|
+
* Tests type coercion, path safety, and array handling for HTTP query strings.
|
|
5
|
+
*
|
|
6
|
+
* @module tests/unit/httpPreprocess
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect } from 'vitest';
|
|
10
|
+
import { toNumber, toBoolean, toArray, safePath, numericString, booleanString, stringArray } from '../../validation/httpPreprocess.js';
|
|
11
|
+
|
|
12
|
+
describe('toNumber', () => {
|
|
13
|
+
it('passes through numbers', () => {
|
|
14
|
+
expect(toNumber(42)).toBe(42);
|
|
15
|
+
expect(toNumber(0)).toBe(0);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('converts numeric strings to integers', () => {
|
|
19
|
+
expect(toNumber('42')).toBe(42);
|
|
20
|
+
expect(toNumber('0')).toBe(0);
|
|
21
|
+
expect(toNumber('100')).toBe(100);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('returns non-numeric strings as-is', () => {
|
|
25
|
+
expect(toNumber('abc')).toBe('abc');
|
|
26
|
+
expect(toNumber('12.5')).toBe('12.5'); // floats not supported
|
|
27
|
+
expect(toNumber('12abc')).toBe('12abc');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('returns non-string/non-number values as-is', () => {
|
|
31
|
+
expect(toNumber(null)).toBeNull();
|
|
32
|
+
expect(toNumber(undefined)).toBeUndefined();
|
|
33
|
+
expect(toNumber(true)).toBe(true);
|
|
34
|
+
expect(toNumber([])).toEqual([]);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('toBoolean', () => {
|
|
39
|
+
it('passes through booleans', () => {
|
|
40
|
+
expect(toBoolean(true)).toBe(true);
|
|
41
|
+
expect(toBoolean(false)).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('converts string "true" and "false"', () => {
|
|
45
|
+
expect(toBoolean('true')).toBe(true);
|
|
46
|
+
expect(toBoolean('false')).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('returns other values as-is', () => {
|
|
50
|
+
expect(toBoolean('yes')).toBe('yes');
|
|
51
|
+
expect(toBoolean('1')).toBe('1');
|
|
52
|
+
expect(toBoolean(null)).toBeNull();
|
|
53
|
+
expect(toBoolean(undefined)).toBeUndefined();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('toArray', () => {
|
|
58
|
+
it('passes through arrays', () => {
|
|
59
|
+
expect(toArray(['a', 'b'])).toEqual(['a', 'b']);
|
|
60
|
+
expect(toArray([])).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('splits comma-separated strings', () => {
|
|
64
|
+
expect(toArray('a,b,c')).toEqual(['a', 'b', 'c']);
|
|
65
|
+
expect(toArray('ts,js')).toEqual(['ts', 'js']);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('trims whitespace around items', () => {
|
|
69
|
+
expect(toArray('a , b , c')).toEqual(['a', 'b', 'c']);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('returns empty array for empty string', () => {
|
|
73
|
+
expect(toArray('')).toEqual([]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('returns empty array for whitespace-only string', () => {
|
|
77
|
+
expect(toArray(' ')).toEqual([]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('filters out empty items from trailing commas', () => {
|
|
81
|
+
expect(toArray('a,b,')).toEqual(['a', 'b']);
|
|
82
|
+
expect(toArray(',a')).toEqual(['a']);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('handles single value strings', () => {
|
|
86
|
+
expect(toArray('typescript')).toEqual(['typescript']);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('returns non-string/non-array values as-is', () => {
|
|
90
|
+
expect(toArray(null)).toBeNull();
|
|
91
|
+
expect(toArray(undefined)).toBeUndefined();
|
|
92
|
+
expect(toArray(42)).toBe(42);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('safePath', () => {
|
|
97
|
+
it('accepts valid paths', () => {
|
|
98
|
+
expect(safePath.safeParse('/Users/dev/project').success).toBe(true);
|
|
99
|
+
expect(safePath.safeParse('/tmp/test').success).toBe(true);
|
|
100
|
+
expect(safePath.safeParse('src/utils.ts').success).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('rejects null bytes', () => {
|
|
104
|
+
expect(safePath.safeParse('/path/\0/exploit').success).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('rejects directory traversal', () => {
|
|
108
|
+
expect(safePath.safeParse('../../../etc/passwd').success).toBe(false);
|
|
109
|
+
expect(safePath.safeParse('src/../../etc/passwd').success).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('rejects URL-encoded traversal', () => {
|
|
113
|
+
expect(safePath.safeParse('%2e%2e%2fetc/passwd').success).toBe(false);
|
|
114
|
+
expect(safePath.safeParse('/path/%2e%2e/exploit').success).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('rejects backslashes on non-Windows', () => {
|
|
118
|
+
if (process.platform !== 'win32') {
|
|
119
|
+
expect(safePath.safeParse('path\\to\\file').success).toBe(false);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('numericString (Zod schema)', () => {
|
|
125
|
+
it('parses string numbers', () => {
|
|
126
|
+
expect(numericString.parse('10')).toBe(10);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('handles undefined', () => {
|
|
130
|
+
expect(numericString.parse(undefined)).toBeUndefined();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('booleanString (Zod schema)', () => {
|
|
135
|
+
it('parses string booleans', () => {
|
|
136
|
+
expect(booleanString.parse('true')).toBe(true);
|
|
137
|
+
expect(booleanString.parse('false')).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('handles undefined', () => {
|
|
141
|
+
expect(booleanString.parse(undefined)).toBeUndefined();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('stringArray (Zod schema)', () => {
|
|
146
|
+
it('parses comma-separated strings', () => {
|
|
147
|
+
expect(stringArray.parse('a,b,c')).toEqual(['a', 'b', 'c']);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('passes through arrays', () => {
|
|
151
|
+
expect(stringArray.parse(['x', 'y'])).toEqual(['x', 'y']);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('handles empty string', () => {
|
|
155
|
+
expect(stringArray.parse('')).toEqual([]);
|
|
156
|
+
});
|
|
157
|
+
});
|