@dizzlkheinz/ynab-mcpb 0.12.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/.chunkhound.json +11 -0
- package/.code/agents/0427d95e-edca-431f-a214-5e53264e29c4/error.txt +8 -0
- package/.code/agents/0d675174-d1e1-41c3-9975-4c2e275819a9/error.txt +3 -0
- package/.code/agents/0d8c5afd-4787-422b-abf8-2e5943fc7e67/error.txt +3 -0
- package/.code/agents/0ec34a70-ed5d-4b9e-bee4-bb0e4cccbc4b/error.txt +1 -0
- package/.code/agents/0ef51a21-1ab1-49d7-9561-0eaa43875ebc/error.txt +12 -0
- package/.code/agents/15db95d7-abad-4b4d-9c3b-8446089cb61d/error.txt +1 -0
- package/.code/agents/19ab9acb-f675-4ff0-902a-09a5476f8149/error.txt +1 -0
- package/.code/agents/1ef7e12d-f6ff-4897-8a9b-152d523d898e/error.txt +5 -0
- package/.code/agents/2465/exec-call_lroN9KKzJVWC7t5423DK1nT9.txt +1453 -0
- package/.code/agents/28edb6fe-95a9-41a0-ae69-aa0100d26c0c/error.txt +8 -0
- package/.code/agents/2ae40cf5-b4bf-42e2-92bf-7ea350a7755e/error.txt +9 -0
- package/.code/agents/2bfc4e1f-ac4b-45a5-b6df-bf89d4dbb54c/error.txt +1 -0
- package/.code/agents/2e2e1134-eff0-49be-ba25-8e2c3468a564/error.txt +5 -0
- package/.code/agents/3/exec-call_203OC4TNVkLxW7z2HCVEQ1cM.txt +81 -0
- package/.code/agents/3/exec-call_SS5T0XSiXB4LSNzUKTl75wkh.txt +610 -0
- package/.code/agents/3322c003-ce5e-48e3-a342-f5049c5bf9a2/error.txt +1 -0
- package/.code/agents/391e9b08-1ebc-468c-9bcd-6d0cc3193b37/error.txt +1 -0
- package/.code/agents/3ab0aa84-b7bb-4054-afa3-40b8fd7d3be0/error.txt +1 -0
- package/.code/agents/3bed368d-50fe-477e-aee3-a6707eaa1ab9/error.txt +3 -0
- package/.code/agents/3e40b925-db12-442f-8d7a-a25fc69a6672/error.txt +8 -0
- package/.code/agents/414d5776-cf58-41f3-9328-a6daed503a50/error.txt +5 -0
- package/.code/agents/42687751-4565-4610-b240-67835b17d861/error.txt +1 -0
- package/.code/agents/46b98876-1a39-43c9-9e2f-507ca6d47335/error.txt +9 -0
- package/.code/agents/4a7d9491-b26f-43dd-850d-2ecdc49b5d1b/error.txt +1 -0
- package/.code/agents/4e60f00a-1b3e-447f-87f3-7faf9deddec3/error.txt +13 -0
- package/.code/agents/5138fc1c-4d49-4b74-a7da-ccdb3a8e44e7/error.txt +14 -0
- package/.code/agents/521cff39-a7a3-42e5-a557-134f0f7daaa0/error.txt +5 -0
- package/.code/agents/53302dc5-3857-4413-9a47-9e0f64a51dc4/error.txt +5 -0
- package/.code/agents/567c7c2e-6a6f-4761-a08d-d36deeb2e0ac/error.txt +5 -0
- package/.code/agents/57b00845-80dc-47c9-953c-3028d16275d6/error.txt +3 -0
- package/.code/agents/593d9005-c2a5-48fd-8813-ece0d3f2de96/error.txt +1 -0
- package/.code/agents/5a112e66-0e1a-42f9-877c-53af56ea3551/error.txt +1 -0
- package/.code/agents/5b05e8ed-7788-4738-b7ee-9faa8180f992/error.txt +5 -0
- package/.code/agents/5f888d6f-d7ca-4ac8-be23-9ea1bf753951/error.txt +5 -0
- package/.code/agents/607db3ab-e4b0-435b-b497-93e9aa525549/error.txt +8 -0
- package/.code/agents/67dcb2a2-900f-4c78-b3fc-80b5213e0ddf/error.txt +8 -0
- package/.code/agents/69ad848c-4e98-49b3-b16c-0094ac2d1759/error.txt +5 -0
- package/.code/agents/6c9cfc5f-0d0b-445c-b121-9f60082c4f70/error.txt +1 -0
- package/.code/agents/6f6f8f77-4ab0-4f6e-9f30-40e8be0bd8f5/error.txt +1 -0
- package/.code/agents/72a7cde4-fa8a-4024-9038-27faa550539b/error.txt +1 -0
- package/.code/agents/7b48335c-8247-43aa-9949-5f820ba8e199/error.txt +1 -0
- package/.code/agents/80944249-bea9-4ac5-87de-a666c4df306e/error.txt +1 -0
- package/.code/agents/826099df-1b66-4186-a915-7eb59f9db19d/error.txt +5 -0
- package/.code/agents/8291d158-18a8-4a92-b799-4e9a4d9cce88/error.txt +1 -0
- package/.code/agents/82fb71a3-20fb-4341-804a-a2fc900f95bc/error.txt +1 -0
- package/.code/agents/855790ea-54ee-43e4-8209-a66994e37590/error.txt +1 -0
- package/.code/agents/88ce3a2e-04f2-42be-9062-bf97aa798da0/error.txt +3 -0
- package/.code/agents/9a17e398-b6ed-4218-bb55-bc64a8d38ce8/error.txt +8 -0
- package/.code/agents/9a4f4bfc-a2a6-4f40-a896-9335b41a7ed1/error.txt +1 -0
- package/.code/agents/9b633e55-ef84-47d6-94bb-fd3dd172ad97/error.txt +1 -0
- package/.code/agents/9b81f3ab-c72b-4a81-9a8f-28a49ddba84a/error.txt +8 -0
- package/.code/agents/a35daf29-b2d1-4aef-9b42-dad63a76bd47/error.txt +3 -0
- package/.code/agents/a81990cc-69ee-44d2-b907-17403c9bc5d7/error.txt +5 -0
- package/.code/agents/ab56260a-4a83-4ad4-9410-f88a23d6520a/error.txt +1 -0
- package/.code/agents/ad722c31-2d1d-45f7-bae2-3f02ca455b60/error.txt +1 -0
- package/.code/agents/b62e8690-3324-4b97-9309-731bee79416b/error.txt +5 -0
- package/.code/agents/baf60a3a-752b-4ad8-99d6-df32423ed2eb/error.txt +1 -0
- package/.code/agents/be049042-7dcb-4ac8-9beb-c8f1aea67742/error.txt +14 -0
- package/.code/agents/bed1dcb4-bfce-4a9f-8594-0f994962aafd/error.txt +1 -0
- package/.code/agents/c324a6cf-e935-4ede-9529-b3ebc18e8d6b/error.txt +5 -0
- package/.code/agents/c37c06ff-dfe3-43f2-9bbc-3ec73ec8f41d/error.txt +5 -0
- package/.code/agents/c8cd6671-433a-456b-9f88-e51cb2df6bfc/error.txt +11 -0
- package/.code/agents/ca2ccb67-2f24-428e-b27d-9365beadd140/error.txt +1 -0
- package/.code/agents/cf08c0c8-e7f0-423e-93ba-547e8e818340/error.txt +8 -0
- package/.code/agents/d579c74f-874b-40a4-9d56-ced1eb6a701d/error.txt +1 -0
- package/.code/agents/df412c98-7378-4deb-8e1e-76c416931181/error.txt +3 -0
- package/.code/agents/e5134eb3-2af4-45b0-8998-051cb4afdb45/error.txt +3 -0
- package/.code/agents/e6308471-aa45-4e9e-9496-2e9404164d97/error.txt +8 -0
- package/.code/agents/e7bd8bc7-23fb-4f46-98dc-b0dcf11b75a1/error.txt +1 -0
- package/.code/agents/e92bec35-378d-4fe1-8ac0-6e1bb3c86911/error.txt +5 -0
- package/.code/agents/ed918fbf-2dc4-4aa2-bfc5-04b65d9471ea/error.txt +1 -0
- package/.code/agents/ef1d756f-b272-48fc-8729-f05c494674f7/error.txt +1 -0
- package/.code/agents/ef359853-0249-4e41-a804-c0fc459fe456/error.txt +1 -0
- package/.code/agents/effc7b4a-4b90-40a0-8c86-a7a99d2d5fd2/error.txt +1 -0
- package/.code/agents/fa15f8d5-8359-4a8b-83a3-2f2056b3ff40/error.txt +3 -0
- package/.code/agents/fbef4193-eadf-4c8a-83ff-4878a6310f25/error.txt +8 -0
- package/.code/agents/fd0a4b4a-fda4-4964-a6d6-2b8a2da387c6/error.txt +1 -0
- package/.dxtignore +57 -0
- package/.env.example +44 -0
- package/.gemini/settings.json +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- package/.github/ISSUE_TEMPLATE/release_checklist.md +31 -0
- package/.github/pull_request_template.md +41 -0
- package/.github/workflows/ci-tests.yml +41 -0
- package/.github/workflows/claude-code-review.yml +57 -0
- package/.github/workflows/claude.yml +50 -0
- package/.github/workflows/full-integration.yml +22 -0
- package/.github/workflows/pr-description-check.yml +88 -0
- package/.github/workflows/publish.yml +33 -0
- package/.github/workflows/release.yml +89 -0
- package/.mcpbignore +58 -0
- package/.prettierignore +10 -0
- package/.prettierrc.json +10 -0
- package/ADOS-2-Module-1-Complete-Manual.md +757 -0
- package/AGENTS.md +36 -0
- package/CHANGELOG.md +187 -0
- package/CLAUDE.md +414 -0
- package/CODEREVIEW_RESPONSE.md +128 -0
- package/LICENSE +17 -0
- package/NUL +1 -0
- package/README.md +222 -0
- package/SCHEMA_IMPROVEMENT_SUMMARY.md +120 -0
- package/TESTING_NOTES.md +217 -0
- package/WARP.md +245 -0
- package/accountactivity-merged.csv +149 -0
- package/bin/ynab-mcp-server.cjs +4 -0
- package/bin/ynab-mcp-server.js +8 -0
- package/bundle-analysis.html +13110 -0
- package/dist/bundle/index.cjs +124 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +85 -0
- package/dist/server/YNABMCPServer.d.ts +264 -0
- package/dist/server/YNABMCPServer.js +845 -0
- package/dist/server/budgetResolver.d.ts +15 -0
- package/dist/server/budgetResolver.js +99 -0
- package/dist/server/cacheManager.d.ts +74 -0
- package/dist/server/cacheManager.js +306 -0
- package/dist/server/config.d.ts +3 -0
- package/dist/server/config.js +19 -0
- package/dist/server/deltaCache.d.ts +61 -0
- package/dist/server/deltaCache.js +206 -0
- package/dist/server/deltaCache.merge.d.ts +9 -0
- package/dist/server/deltaCache.merge.js +111 -0
- package/dist/server/diagnostics.d.ts +90 -0
- package/dist/server/diagnostics.js +163 -0
- package/dist/server/errorHandler.d.ts +69 -0
- package/dist/server/errorHandler.js +524 -0
- package/dist/server/prompts.d.ts +31 -0
- package/dist/server/prompts.js +205 -0
- package/dist/server/rateLimiter.d.ts +27 -0
- package/dist/server/rateLimiter.js +82 -0
- package/dist/server/requestLogger.d.ts +62 -0
- package/dist/server/requestLogger.js +190 -0
- package/dist/server/resources.d.ts +39 -0
- package/dist/server/resources.js +85 -0
- package/dist/server/responseFormatter.d.ts +14 -0
- package/dist/server/responseFormatter.js +42 -0
- package/dist/server/securityMiddleware.d.ts +87 -0
- package/dist/server/securityMiddleware.js +117 -0
- package/dist/server/serverKnowledgeStore.d.ts +11 -0
- package/dist/server/serverKnowledgeStore.js +42 -0
- package/dist/server/toolRegistry.d.ts +85 -0
- package/dist/server/toolRegistry.js +272 -0
- package/dist/tools/__tests__/deltaTestUtils.d.ts +18 -0
- package/dist/tools/__tests__/deltaTestUtils.js +26 -0
- package/dist/tools/accountTools.d.ts +37 -0
- package/dist/tools/accountTools.js +175 -0
- package/dist/tools/budgetTools.d.ts +10 -0
- package/dist/tools/budgetTools.js +68 -0
- package/dist/tools/categoryTools.d.ts +27 -0
- package/dist/tools/categoryTools.js +232 -0
- package/dist/tools/compareTransactions/formatter.d.ts +71 -0
- package/dist/tools/compareTransactions/formatter.js +97 -0
- package/dist/tools/compareTransactions/index.d.ts +30 -0
- package/dist/tools/compareTransactions/index.js +160 -0
- package/dist/tools/compareTransactions/matcher.d.ts +12 -0
- package/dist/tools/compareTransactions/matcher.js +140 -0
- package/dist/tools/compareTransactions/parser.d.ts +14 -0
- package/dist/tools/compareTransactions/parser.js +430 -0
- package/dist/tools/compareTransactions/types.d.ts +27 -0
- package/dist/tools/compareTransactions/types.js +1 -0
- package/dist/tools/compareTransactions.d.ts +1 -0
- package/dist/tools/compareTransactions.js +1 -0
- package/dist/tools/deltaFetcher.d.ts +22 -0
- package/dist/tools/deltaFetcher.js +137 -0
- package/dist/tools/deltaSupport.d.ts +20 -0
- package/dist/tools/deltaSupport.js +176 -0
- package/dist/tools/exportTransactions.d.ts +17 -0
- package/dist/tools/exportTransactions.js +191 -0
- package/dist/tools/monthTools.d.ts +16 -0
- package/dist/tools/monthTools.js +107 -0
- package/dist/tools/payeeTools.d.ts +17 -0
- package/dist/tools/payeeTools.js +82 -0
- package/dist/tools/reconcileAdapter.d.ts +25 -0
- package/dist/tools/reconcileAdapter.js +167 -0
- package/dist/tools/reconciliation/analyzer.d.ts +3 -0
- package/dist/tools/reconciliation/analyzer.js +567 -0
- package/dist/tools/reconciliation/executor.d.ts +94 -0
- package/dist/tools/reconciliation/executor.js +611 -0
- package/dist/tools/reconciliation/index.d.ts +54 -0
- package/dist/tools/reconciliation/index.js +249 -0
- package/dist/tools/reconciliation/matcher.d.ts +3 -0
- package/dist/tools/reconciliation/matcher.js +160 -0
- package/dist/tools/reconciliation/payeeNormalizer.d.ts +6 -0
- package/dist/tools/reconciliation/payeeNormalizer.js +77 -0
- package/dist/tools/reconciliation/recommendationEngine.d.ts +2 -0
- package/dist/tools/reconciliation/recommendationEngine.js +273 -0
- package/dist/tools/reconciliation/reportFormatter.d.ts +13 -0
- package/dist/tools/reconciliation/reportFormatter.js +214 -0
- package/dist/tools/reconciliation/types.d.ts +172 -0
- package/dist/tools/reconciliation/types.js +7 -0
- package/dist/tools/schemas/outputs/accountOutputs.d.ts +58 -0
- package/dist/tools/schemas/outputs/accountOutputs.js +24 -0
- package/dist/tools/schemas/outputs/budgetOutputs.d.ts +48 -0
- package/dist/tools/schemas/outputs/budgetOutputs.js +15 -0
- package/dist/tools/schemas/outputs/categoryOutputs.d.ts +93 -0
- package/dist/tools/schemas/outputs/categoryOutputs.js +37 -0
- package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +269 -0
- package/dist/tools/schemas/outputs/comparisonOutputs.js +181 -0
- package/dist/tools/schemas/outputs/index.d.ts +14 -0
- package/dist/tools/schemas/outputs/index.js +14 -0
- package/dist/tools/schemas/outputs/monthOutputs.d.ts +122 -0
- package/dist/tools/schemas/outputs/monthOutputs.js +51 -0
- package/dist/tools/schemas/outputs/payeeOutputs.d.ts +34 -0
- package/dist/tools/schemas/outputs/payeeOutputs.js +16 -0
- package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +1275 -0
- package/dist/tools/schemas/outputs/reconciliationOutputs.js +377 -0
- package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +717 -0
- package/dist/tools/schemas/outputs/transactionMutationOutputs.js +260 -0
- package/dist/tools/schemas/outputs/transactionOutputs.d.ts +98 -0
- package/dist/tools/schemas/outputs/transactionOutputs.js +49 -0
- package/dist/tools/schemas/outputs/utilityOutputs.d.ts +219 -0
- package/dist/tools/schemas/outputs/utilityOutputs.js +120 -0
- package/dist/tools/schemas/shared/commonOutputs.d.ts +24 -0
- package/dist/tools/schemas/shared/commonOutputs.js +27 -0
- package/dist/tools/toolCategories.d.ts +32 -0
- package/dist/tools/toolCategories.js +32 -0
- package/dist/tools/transactionTools.d.ts +315 -0
- package/dist/tools/transactionTools.js +1722 -0
- package/dist/tools/utilityTools.d.ts +10 -0
- package/dist/tools/utilityTools.js +56 -0
- package/dist/types/index.d.ts +20 -0
- package/dist/types/index.js +16 -0
- package/dist/types/toolAnnotations.d.ts +7 -0
- package/dist/types/toolAnnotations.js +1 -0
- package/dist/utils/amountUtils.d.ts +3 -0
- package/dist/utils/amountUtils.js +10 -0
- package/dist/utils/dateUtils.d.ts +9 -0
- package/dist/utils/dateUtils.js +43 -0
- package/dist/utils/money.d.ts +21 -0
- package/dist/utils/money.js +51 -0
- package/docs/README.md +72 -0
- package/docs/assets/examples/reconciliation-with-recommendations.json +68 -0
- package/docs/assets/schemas/reconciliation-v2.json +338 -0
- package/docs/getting-started/CONFIGURATION.md +175 -0
- package/docs/getting-started/INSTALLATION.md +333 -0
- package/docs/getting-started/QUICKSTART.md +282 -0
- package/docs/guides/ARCHITECTURE.md +650 -0
- package/docs/guides/DEPLOYMENT.md +189 -0
- package/docs/guides/INTEGRATION_TESTING.md +730 -0
- package/docs/guides/TESTING.md +591 -0
- package/docs/reconciliation-flow.md +83 -0
- package/docs/reference/API.md +1450 -0
- package/docs/reference/EXAMPLES.md +946 -0
- package/docs/reference/TOOLS.md +348 -0
- package/docs/reference/TROUBLESHOOTING.md +481 -0
- package/esbuild.config.mjs +68 -0
- package/eslint.config.js +49 -0
- package/fix-types.sh +17 -0
- package/meta.json +12550 -0
- package/package.json +105 -0
- package/package.json.tmp +105 -0
- package/scripts/analyze-bundle.mjs +41 -0
- package/scripts/create-pr-description.js +203 -0
- package/scripts/generate-mcpb.ps1 +96 -0
- package/scripts/run-domain-integration-tests.js +33 -0
- package/scripts/run-generate-mcpb.js +29 -0
- package/scripts/run-throttled-integration-tests.js +116 -0
- package/scripts/test-delta-params.mjs +140 -0
- package/scripts/test-recommendations.ts +53 -0
- package/scripts/tmpTransaction.ts +48 -0
- package/scripts/validate-env.js +122 -0
- package/scripts/verify-build.js +105 -0
- package/scripts/watch-and-restart.ps1 +50 -0
- package/src/__tests__/comprehensive.integration.test.ts +1196 -0
- package/src/__tests__/delta.performance.test.ts +80 -0
- package/src/__tests__/performance.test.ts +725 -0
- package/src/__tests__/setup.ts +449 -0
- package/src/__tests__/testRunner.ts +444 -0
- package/src/__tests__/testUtils.ts +563 -0
- package/src/__tests__/workflows.e2e.test.ts +1675 -0
- package/src/index.ts +124 -0
- package/src/server/.gitkeep +1 -0
- package/src/server/YNABMCPServer.ts +1188 -0
- package/src/server/__tests__/YNABMCPServer.integration.test.ts +903 -0
- package/src/server/__tests__/YNABMCPServer.test.ts +894 -0
- package/src/server/__tests__/budgetResolver.test.ts +425 -0
- package/src/server/__tests__/cacheManager.test.ts +880 -0
- package/src/server/__tests__/config.test.ts +166 -0
- package/src/server/__tests__/deltaCache.merge.test.ts +724 -0
- package/src/server/__tests__/deltaCache.swr.test.ts +168 -0
- package/src/server/__tests__/deltaCache.test.ts +774 -0
- package/src/server/__tests__/diagnostics.test.ts +823 -0
- package/src/server/__tests__/errorHandler.integration.test.ts +466 -0
- package/src/server/__tests__/errorHandler.test.ts +416 -0
- package/src/server/__tests__/prompts.test.ts +354 -0
- package/src/server/__tests__/rateLimiter.test.ts +314 -0
- package/src/server/__tests__/requestLogger.test.ts +408 -0
- package/src/server/__tests__/resources.test.ts +299 -0
- package/src/server/__tests__/security.integration.test.ts +426 -0
- package/src/server/__tests__/securityMiddleware.test.ts +449 -0
- package/src/server/__tests__/server-startup.integration.test.ts +477 -0
- package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -0
- package/src/server/__tests__/toolRegistry.test.ts +855 -0
- package/src/server/budgetResolver.ts +235 -0
- package/src/server/cacheManager.ts +503 -0
- package/src/server/config.ts +41 -0
- package/src/server/deltaCache.merge.ts +149 -0
- package/src/server/deltaCache.ts +341 -0
- package/src/server/diagnostics.ts +338 -0
- package/src/server/errorHandler.ts +756 -0
- package/src/server/prompts.ts +291 -0
- package/src/server/rateLimiter.ts +156 -0
- package/src/server/requestLogger.ts +344 -0
- package/src/server/resources.ts +168 -0
- package/src/server/responseFormatter.ts +51 -0
- package/src/server/securityMiddleware.ts +236 -0
- package/src/server/serverKnowledgeStore.ts +91 -0
- package/src/server/toolRegistry.ts +489 -0
- package/src/tools/.gitkeep +1 -0
- package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -0
- package/src/tools/__tests__/accountTools.integration.test.ts +117 -0
- package/src/tools/__tests__/accountTools.test.ts +653 -0
- package/src/tools/__tests__/budgetTools.delta.integration.test.ts +90 -0
- package/src/tools/__tests__/budgetTools.integration.test.ts +134 -0
- package/src/tools/__tests__/budgetTools.test.ts +423 -0
- package/src/tools/__tests__/categoryTools.delta.integration.test.ts +80 -0
- package/src/tools/__tests__/categoryTools.integration.test.ts +295 -0
- package/src/tools/__tests__/categoryTools.test.ts +622 -0
- package/src/tools/__tests__/compareTransactions/formatter.test.ts +486 -0
- package/src/tools/__tests__/compareTransactions/index.test.ts +383 -0
- package/src/tools/__tests__/compareTransactions/matcher.test.ts +410 -0
- package/src/tools/__tests__/compareTransactions/parser.test.ts +764 -0
- package/src/tools/__tests__/compareTransactions.test.ts +342 -0
- package/src/tools/__tests__/compareTransactions.window.test.ts +147 -0
- package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +76 -0
- package/src/tools/__tests__/deltaFetcher.test.ts +270 -0
- package/src/tools/__tests__/deltaSupport.test.ts +188 -0
- package/src/tools/__tests__/deltaTestUtils.ts +46 -0
- package/src/tools/__tests__/exportTransactions.test.ts +213 -0
- package/src/tools/__tests__/monthTools.delta.integration.test.ts +80 -0
- package/src/tools/__tests__/monthTools.integration.test.ts +174 -0
- package/src/tools/__tests__/monthTools.test.ts +523 -0
- package/src/tools/__tests__/payeeTools.delta.integration.test.ts +80 -0
- package/src/tools/__tests__/payeeTools.integration.test.ts +150 -0
- package/src/tools/__tests__/payeeTools.test.ts +445 -0
- package/src/tools/__tests__/transactionTools.integration.test.ts +762 -0
- package/src/tools/__tests__/transactionTools.test.ts +3521 -0
- package/src/tools/__tests__/utilityTools.integration.test.ts +128 -0
- package/src/tools/__tests__/utilityTools.test.ts +205 -0
- package/src/tools/accountTools.ts +283 -0
- package/src/tools/budgetTools.ts +112 -0
- package/src/tools/categoryTools.ts +366 -0
- package/src/tools/compareTransactions/formatter.ts +163 -0
- package/src/tools/compareTransactions/index.ts +228 -0
- package/src/tools/compareTransactions/matcher.ts +240 -0
- package/src/tools/compareTransactions/parser.ts +557 -0
- package/src/tools/compareTransactions/types.ts +60 -0
- package/src/tools/compareTransactions.ts +3 -0
- package/src/tools/deltaFetcher.ts +278 -0
- package/src/tools/deltaSupport.ts +293 -0
- package/src/tools/exportTransactions.ts +273 -0
- package/src/tools/monthTools.ts +164 -0
- package/src/tools/payeeTools.ts +140 -0
- package/src/tools/reconcileAdapter.ts +312 -0
- package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +122 -0
- package/src/tools/reconciliation/__tests__/adapter.test.ts +234 -0
- package/src/tools/reconciliation/__tests__/analyzer.test.ts +406 -0
- package/src/tools/reconciliation/__tests__/executor.integration.test.ts +366 -0
- package/src/tools/reconciliation/__tests__/executor.test.ts +779 -0
- package/src/tools/reconciliation/__tests__/matcher.test.ts +650 -0
- package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +278 -0
- package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +658 -0
- package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1000 -0
- package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +151 -0
- package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +573 -0
- package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +78 -0
- package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +47 -0
- package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +61 -0
- package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +49 -0
- package/src/tools/reconciliation/analyzer.ts +824 -0
- package/src/tools/reconciliation/executor.ts +880 -0
- package/src/tools/reconciliation/index.ts +400 -0
- package/src/tools/reconciliation/matcher.ts +269 -0
- package/src/tools/reconciliation/payeeNormalizer.ts +167 -0
- package/src/tools/reconciliation/recommendationEngine.ts +506 -0
- package/src/tools/reconciliation/reportFormatter.ts +363 -0
- package/src/tools/reconciliation/types.ts +314 -0
- package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +424 -0
- package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +310 -0
- package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +448 -0
- package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +519 -0
- package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +155 -0
- package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +288 -0
- package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +478 -0
- package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +370 -0
- package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +401 -0
- package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +213 -0
- package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +474 -0
- package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +333 -0
- package/src/tools/schemas/outputs/accountOutputs.ts +137 -0
- package/src/tools/schemas/outputs/budgetOutputs.ts +86 -0
- package/src/tools/schemas/outputs/categoryOutputs.ts +194 -0
- package/src/tools/schemas/outputs/comparisonOutputs.ts +600 -0
- package/src/tools/schemas/outputs/index.ts +270 -0
- package/src/tools/schemas/outputs/monthOutputs.ts +243 -0
- package/src/tools/schemas/outputs/payeeOutputs.ts +105 -0
- package/src/tools/schemas/outputs/reconciliationOutputs.ts +796 -0
- package/src/tools/schemas/outputs/transactionMutationOutputs.ts +758 -0
- package/src/tools/schemas/outputs/transactionOutputs.ts +243 -0
- package/src/tools/schemas/outputs/utilityOutputs.ts +411 -0
- package/src/tools/schemas/shared/commonOutputs.ts +140 -0
- package/src/tools/toolCategories.ts +140 -0
- package/src/tools/transactionTools.ts +2509 -0
- package/src/tools/utilityTools.ts +90 -0
- package/src/types/.gitkeep +1 -0
- package/src/types/__tests__/index.test.ts +52 -0
- package/src/types/index.ts +67 -0
- package/src/types/integration-tests.d.ts +35 -0
- package/src/types/toolAnnotations.ts +44 -0
- package/src/utils/__tests__/dateUtils.test.ts +170 -0
- package/src/utils/__tests__/money.test.ts +189 -0
- package/src/utils/amountUtils.ts +32 -0
- package/src/utils/dateUtils.ts +108 -0
- package/src/utils/money.ts +123 -0
- package/test-csv-sample.csv +28 -0
- package/test-exports/sample_bank_statement.csv +7 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_09-04-53.json +23 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_10-37-42.json +23 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_4items_2025-11-19_09-02-09.json +44 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_6items_2025-11-19_10-37-52.json +58 -0
- package/test-exports/ynab_since_2025-11-01_account_4c18e9f0_minimal_14items_2025-11-16_10-07-10.json +115 -0
- package/test-reconcile-autodetect.js +40 -0
- package/test-reconcile-tool.js +152 -0
- package/test-reconcile-with-csv.cjs +89 -0
- package/test-statement.csv +8 -0
- package/test_debug.js +47 -0
- package/test_simple.mjs +16 -0
- package/tsconfig.json +31 -0
- package/tsconfig.prod.json +18 -0
- package/vitest-reporters/split-json-reporter.ts +211 -0
- package/vitest.config.ts +96 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for prompts module
|
|
3
|
+
*
|
|
4
|
+
* Tests prompt management functionality.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
8
|
+
import { PromptManager } from '../prompts.js';
|
|
9
|
+
|
|
10
|
+
describe('prompts module', () => {
|
|
11
|
+
let promptManager: PromptManager;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
promptManager = new PromptManager();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('PromptManager', () => {
|
|
18
|
+
describe('constructor', () => {
|
|
19
|
+
it('should initialize without dependencies', () => {
|
|
20
|
+
expect(promptManager).toBeInstanceOf(PromptManager);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('listPrompts', () => {
|
|
25
|
+
it('should return list of available prompts', () => {
|
|
26
|
+
const result = promptManager.listPrompts();
|
|
27
|
+
|
|
28
|
+
expect(result).toEqual({
|
|
29
|
+
prompts: [
|
|
30
|
+
{
|
|
31
|
+
name: 'create-transaction',
|
|
32
|
+
description: 'Create a new transaction in YNAB',
|
|
33
|
+
arguments: [
|
|
34
|
+
{
|
|
35
|
+
name: 'budget_name',
|
|
36
|
+
description: 'Name of the budget (optional, uses first budget if not specified)',
|
|
37
|
+
required: false,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'account_name',
|
|
41
|
+
description: 'Name of the account',
|
|
42
|
+
required: true,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'amount',
|
|
46
|
+
description: 'Transaction amount (negative for expenses, positive for income)',
|
|
47
|
+
required: true,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'payee',
|
|
51
|
+
description: 'Who you paid or received money from',
|
|
52
|
+
required: true,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'category',
|
|
56
|
+
description: 'Budget category (optional)',
|
|
57
|
+
required: false,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'memo',
|
|
61
|
+
description: 'Additional notes (optional)',
|
|
62
|
+
required: false,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'budget-summary',
|
|
68
|
+
description: 'Get a summary of your budget status',
|
|
69
|
+
arguments: [
|
|
70
|
+
{
|
|
71
|
+
name: 'budget_name',
|
|
72
|
+
description: 'Name of the budget (optional, uses first budget if not specified)',
|
|
73
|
+
required: false,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'month',
|
|
77
|
+
description:
|
|
78
|
+
'Month to analyze (YYYY-MM format, optional, uses current month if not specified)',
|
|
79
|
+
required: false,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'account-balances',
|
|
85
|
+
description: 'Check balances across all accounts',
|
|
86
|
+
arguments: [
|
|
87
|
+
{
|
|
88
|
+
name: 'budget_name',
|
|
89
|
+
description: 'Name of the budget (optional, uses first budget if not specified)',
|
|
90
|
+
required: false,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'account_type',
|
|
94
|
+
description: 'Filter by account type (checking, savings, creditCard, etc.)',
|
|
95
|
+
required: false,
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should return consistent prompt list', () => {
|
|
104
|
+
const result1 = promptManager.listPrompts();
|
|
105
|
+
const result2 = promptManager.listPrompts();
|
|
106
|
+
|
|
107
|
+
expect(result1).toEqual(result2);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('getPrompt', () => {
|
|
112
|
+
describe('create-transaction', () => {
|
|
113
|
+
it('should generate transaction creation prompt with default values', async () => {
|
|
114
|
+
const result = await promptManager.getPrompt('create-transaction', {});
|
|
115
|
+
|
|
116
|
+
expect(result.description).toBe('Create a transaction for [PAYEE] in [ACCOUNT_NAME]');
|
|
117
|
+
expect(result.messages).toHaveLength(1);
|
|
118
|
+
expect(result.messages[0].role).toBe('user');
|
|
119
|
+
expect(result.messages[0].content.type).toBe('text');
|
|
120
|
+
expect(result.messages[0].content.text).toContain('first available budget');
|
|
121
|
+
expect(result.messages[0].content.text).toContain('[ACCOUNT_NAME]');
|
|
122
|
+
expect(result.messages[0].content.text).toContain('[AMOUNT]');
|
|
123
|
+
expect(result.messages[0].content.text).toContain('[PAYEE]');
|
|
124
|
+
expect(result.messages[0].content.text).toContain('[CATEGORY]');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should generate transaction creation prompt with provided arguments', async () => {
|
|
128
|
+
const args = {
|
|
129
|
+
budget_name: 'Personal Budget',
|
|
130
|
+
account_name: 'Checking Account',
|
|
131
|
+
amount: '25.50',
|
|
132
|
+
payee: 'Grocery Store',
|
|
133
|
+
category: 'Groceries',
|
|
134
|
+
memo: 'Weekly shopping',
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const result = await promptManager.getPrompt('create-transaction', args);
|
|
138
|
+
|
|
139
|
+
expect(result.description).toBe(
|
|
140
|
+
'Create a transaction for Grocery Store in Checking Account',
|
|
141
|
+
);
|
|
142
|
+
expect(result.messages[0].content.text).toContain('Personal Budget');
|
|
143
|
+
expect(result.messages[0].content.text).toContain('Checking Account');
|
|
144
|
+
expect(result.messages[0].content.text).toContain('$25.50');
|
|
145
|
+
expect(result.messages[0].content.text).toContain('Grocery Store');
|
|
146
|
+
expect(result.messages[0].content.text).toContain('Groceries');
|
|
147
|
+
expect(result.messages[0].content.text).toContain('Weekly shopping');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should handle partial arguments', async () => {
|
|
151
|
+
const args = {
|
|
152
|
+
account_name: 'Savings Account',
|
|
153
|
+
payee: 'Transfer',
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const result = await promptManager.getPrompt('create-transaction', args);
|
|
157
|
+
|
|
158
|
+
expect(result.description).toBe('Create a transaction for Transfer in Savings Account');
|
|
159
|
+
expect(result.messages[0].content.text).toContain('first available budget');
|
|
160
|
+
expect(result.messages[0].content.text).toContain('Savings Account');
|
|
161
|
+
expect(result.messages[0].content.text).toContain('[AMOUNT]');
|
|
162
|
+
expect(result.messages[0].content.text).toContain('Transfer');
|
|
163
|
+
expect(result.messages[0].content.text).toContain('[CATEGORY]');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should include proper workflow instructions', async () => {
|
|
167
|
+
const result = await promptManager.getPrompt('create-transaction', {});
|
|
168
|
+
|
|
169
|
+
const text = result.messages[0].content.text;
|
|
170
|
+
expect(text).toContain('list budgets to find the budget ID');
|
|
171
|
+
expect(text).toContain('List accounts for that budget to find the account ID');
|
|
172
|
+
expect(text).toContain('list categories to find the category ID');
|
|
173
|
+
expect(text).toContain('Create the transaction with the correct amount in milliunits');
|
|
174
|
+
expect(text).toContain('multiply by 1000');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('budget-summary', () => {
|
|
179
|
+
it('should generate budget summary prompt with default values', async () => {
|
|
180
|
+
const result = await promptManager.getPrompt('budget-summary', {});
|
|
181
|
+
|
|
182
|
+
expect(result.description).toBe('Get budget summary for first available budget');
|
|
183
|
+
expect(result.messages).toHaveLength(1);
|
|
184
|
+
expect(result.messages[0].role).toBe('user');
|
|
185
|
+
expect(result.messages[0].content.text).toContain('first available budget');
|
|
186
|
+
expect(result.messages[0].content.text).toContain('current month');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should generate budget summary prompt with provided arguments', async () => {
|
|
190
|
+
const args = {
|
|
191
|
+
budget_name: 'Family Budget',
|
|
192
|
+
month: '2024-03',
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const result = await promptManager.getPrompt('budget-summary', args);
|
|
196
|
+
|
|
197
|
+
expect(result.description).toBe('Get budget summary for Family Budget');
|
|
198
|
+
expect(result.messages[0].content.text).toContain('Family Budget (2024-03)');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should include YNAB-specific guidance', async () => {
|
|
202
|
+
const result = await promptManager.getPrompt('budget-summary', {});
|
|
203
|
+
|
|
204
|
+
const text = result.messages[0].content.text;
|
|
205
|
+
expect(text).toContain('budgeted: Amount assigned to the category');
|
|
206
|
+
expect(text).toContain('activity: Spending/income in the category');
|
|
207
|
+
expect(text).toContain('balance: Available amount in the category');
|
|
208
|
+
expect(text).toContain('OVERSPENDING occurs when balance < 0');
|
|
209
|
+
expect(text).toContain('SPENDING TRENDS');
|
|
210
|
+
expect(text).toContain('BUDGET OPTIMIZATION');
|
|
211
|
+
expect(text).toContain('reliability_score');
|
|
212
|
+
expect(text).toContain('Consistently Under-Spent Categories');
|
|
213
|
+
expect(text).toContain('Categories Over Monthly Assignment');
|
|
214
|
+
expect(text).toContain('Large Unused Category Balances');
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('account-balances', () => {
|
|
219
|
+
it('should generate account balances prompt with default values', async () => {
|
|
220
|
+
const result = await promptManager.getPrompt('account-balances', {});
|
|
221
|
+
|
|
222
|
+
expect(result.description).toBe('Check account balances for all accounts');
|
|
223
|
+
expect(result.messages).toHaveLength(1);
|
|
224
|
+
expect(result.messages[0].role).toBe('user');
|
|
225
|
+
expect(result.messages[0].content.text).toContain('first available budget');
|
|
226
|
+
expect(result.messages[0].content.text).toContain('all accounts');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should generate account balances prompt with provided arguments', async () => {
|
|
230
|
+
const args = {
|
|
231
|
+
budget_name: 'Business Budget',
|
|
232
|
+
account_type: 'checking',
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const result = await promptManager.getPrompt('account-balances', args);
|
|
236
|
+
|
|
237
|
+
expect(result.description).toBe('Check account balances for checking');
|
|
238
|
+
expect(result.messages[0].content.text).toContain('Business Budget');
|
|
239
|
+
expect(result.messages[0].content.text).toContain('checking');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should include proper balance display instructions', async () => {
|
|
243
|
+
const result = await promptManager.getPrompt('account-balances', {});
|
|
244
|
+
|
|
245
|
+
const text = result.messages[0].content.text;
|
|
246
|
+
expect(text).toContain('Account name and type');
|
|
247
|
+
expect(text).toContain('Current balance');
|
|
248
|
+
expect(text).toContain('Cleared vs uncleared amounts');
|
|
249
|
+
expect(text).toContain('Total by account type');
|
|
250
|
+
expect(text).toContain('Net worth summary');
|
|
251
|
+
expect(text).toContain('Convert milliunits to dollars');
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe('unknown prompts', () => {
|
|
256
|
+
it('should throw error for unknown prompt names', async () => {
|
|
257
|
+
await expect(promptManager.getPrompt('unknown-prompt', {})).rejects.toThrow(
|
|
258
|
+
'Unknown prompt: unknown-prompt',
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should throw error for empty prompt name', async () => {
|
|
263
|
+
await expect(promptManager.getPrompt('', {})).rejects.toThrow('Unknown prompt: ');
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('argument handling', () => {
|
|
268
|
+
it('should handle undefined arguments', async () => {
|
|
269
|
+
const result = await promptManager.getPrompt('create-transaction', undefined);
|
|
270
|
+
|
|
271
|
+
expect(result.description).toBe('Create a transaction for [PAYEE] in [ACCOUNT_NAME]');
|
|
272
|
+
expect(result.messages[0].content.text).toContain('first available budget');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should handle null arguments', async () => {
|
|
276
|
+
const result = await promptManager.getPrompt('budget-summary', null as any);
|
|
277
|
+
|
|
278
|
+
expect(result.description).toBe('Get budget summary for first available budget');
|
|
279
|
+
expect(result.messages[0].content.text).toContain('current month');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should handle empty arguments object', async () => {
|
|
283
|
+
const result = await promptManager.getPrompt('account-balances', {});
|
|
284
|
+
|
|
285
|
+
expect(result.description).toBe('Check account balances for all accounts');
|
|
286
|
+
expect(result.messages[0].content.text).toContain('all accounts');
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should handle arguments with null/undefined values', async () => {
|
|
290
|
+
const args = {
|
|
291
|
+
budget_name: null,
|
|
292
|
+
account_name: undefined,
|
|
293
|
+
amount: '',
|
|
294
|
+
payee: 'Test Payee',
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const result = await promptManager.getPrompt('create-transaction', args);
|
|
298
|
+
|
|
299
|
+
expect(result.messages[0].content.text).toContain('first available budget');
|
|
300
|
+
expect(result.messages[0].content.text).toContain('[ACCOUNT_NAME]');
|
|
301
|
+
expect(result.messages[0].content.text).toContain('[AMOUNT]');
|
|
302
|
+
expect(result.messages[0].content.text).toContain('Test Payee');
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
describe('edge cases', () => {
|
|
307
|
+
it('should handle very long argument values', async () => {
|
|
308
|
+
const longValue = 'a'.repeat(1000);
|
|
309
|
+
const args = {
|
|
310
|
+
budget_name: longValue,
|
|
311
|
+
memo: longValue,
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const result = await promptManager.getPrompt('create-transaction', args);
|
|
315
|
+
|
|
316
|
+
expect(result.messages[0].content.text).toContain(longValue);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('should handle special characters in arguments', async () => {
|
|
320
|
+
const args = {
|
|
321
|
+
payee: "McDonald's & Co. (special chars: !@#$%^&*)",
|
|
322
|
+
category: 'Dining/Restaurants',
|
|
323
|
+
memo: 'Quote: "Great food!"',
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const result = await promptManager.getPrompt('create-transaction', args);
|
|
327
|
+
|
|
328
|
+
expect(result.messages[0].content.text).toContain(args.payee);
|
|
329
|
+
expect(result.messages[0].content.text).toContain(args.category);
|
|
330
|
+
expect(result.messages[0].content.text).toContain(args.memo);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should maintain consistent message structure across all prompts', async () => {
|
|
334
|
+
const prompts = ['create-transaction', 'budget-summary', 'account-balances'];
|
|
335
|
+
|
|
336
|
+
for (const promptName of prompts) {
|
|
337
|
+
const result = await promptManager.getPrompt(promptName, {});
|
|
338
|
+
|
|
339
|
+
expect(result).toHaveProperty('description');
|
|
340
|
+
expect(result).toHaveProperty('messages');
|
|
341
|
+
expect(Array.isArray(result.messages)).toBe(true);
|
|
342
|
+
expect(result.messages).toHaveLength(1);
|
|
343
|
+
expect(result.messages[0]).toHaveProperty('role', 'user');
|
|
344
|
+
expect(result.messages[0]).toHaveProperty('content');
|
|
345
|
+
expect(result.messages[0].content).toHaveProperty('type', 'text');
|
|
346
|
+
expect(result.messages[0].content).toHaveProperty('text');
|
|
347
|
+
expect(typeof result.messages[0].content.text).toBe('string');
|
|
348
|
+
expect(result.messages[0].content.text.length).toBeGreaterThan(0);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
});
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for RateLimiter class
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
6
|
+
import { RateLimiter, RateLimitError } from '../rateLimiter.js';
|
|
7
|
+
|
|
8
|
+
describe('RateLimiter', () => {
|
|
9
|
+
let rateLimiter: RateLimiter;
|
|
10
|
+
const testIdentifier = 'test-token-123';
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
rateLimiter = new RateLimiter({
|
|
14
|
+
maxRequests: 5,
|
|
15
|
+
windowMs: 1000, // 1 second for testing
|
|
16
|
+
enableLogging: false,
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
vi.restoreAllMocks();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('isAllowed', () => {
|
|
25
|
+
it('should allow requests within the limit', () => {
|
|
26
|
+
const result = rateLimiter.isAllowed(testIdentifier);
|
|
27
|
+
|
|
28
|
+
expect(result.isLimited).toBe(false);
|
|
29
|
+
expect(result.remaining).toBe(5);
|
|
30
|
+
expect(result.resetTime).toBeInstanceOf(Date);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should track requests correctly', () => {
|
|
34
|
+
// Make 3 requests
|
|
35
|
+
rateLimiter.recordRequest(testIdentifier);
|
|
36
|
+
rateLimiter.recordRequest(testIdentifier);
|
|
37
|
+
rateLimiter.recordRequest(testIdentifier);
|
|
38
|
+
|
|
39
|
+
const result = rateLimiter.isAllowed(testIdentifier);
|
|
40
|
+
|
|
41
|
+
expect(result.isLimited).toBe(false);
|
|
42
|
+
expect(result.remaining).toBe(2);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should limit requests when max is reached', () => {
|
|
46
|
+
// Make 5 requests (the limit)
|
|
47
|
+
for (let i = 0; i < 5; i++) {
|
|
48
|
+
rateLimiter.recordRequest(testIdentifier);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const result = rateLimiter.isAllowed(testIdentifier);
|
|
52
|
+
|
|
53
|
+
expect(result.isLimited).toBe(true);
|
|
54
|
+
expect(result.remaining).toBe(0);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should handle multiple identifiers independently', () => {
|
|
58
|
+
const identifier1 = 'token-1';
|
|
59
|
+
const identifier2 = 'token-2';
|
|
60
|
+
|
|
61
|
+
// Max out identifier1
|
|
62
|
+
for (let i = 0; i < 5; i++) {
|
|
63
|
+
rateLimiter.recordRequest(identifier1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// identifier2 should still be allowed
|
|
67
|
+
const result1 = rateLimiter.isAllowed(identifier1);
|
|
68
|
+
const result2 = rateLimiter.isAllowed(identifier2);
|
|
69
|
+
|
|
70
|
+
expect(result1.isLimited).toBe(true);
|
|
71
|
+
expect(result2.isLimited).toBe(false);
|
|
72
|
+
expect(result2.remaining).toBe(5);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should reset after time window expires', async () => {
|
|
76
|
+
// Use a very short window for testing
|
|
77
|
+
const shortWindowLimiter = new RateLimiter({
|
|
78
|
+
maxRequests: 2,
|
|
79
|
+
windowMs: 50, // 50ms
|
|
80
|
+
enableLogging: false,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Max out the requests
|
|
84
|
+
shortWindowLimiter.recordRequest(testIdentifier);
|
|
85
|
+
shortWindowLimiter.recordRequest(testIdentifier);
|
|
86
|
+
|
|
87
|
+
expect(shortWindowLimiter.isAllowed(testIdentifier).isLimited).toBe(true);
|
|
88
|
+
|
|
89
|
+
// Wait for window to expire
|
|
90
|
+
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
91
|
+
|
|
92
|
+
// Should be allowed again
|
|
93
|
+
const result = shortWindowLimiter.isAllowed(testIdentifier);
|
|
94
|
+
expect(result.isLimited).toBe(false);
|
|
95
|
+
expect(result.remaining).toBe(2);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('recordRequest', () => {
|
|
100
|
+
it('should record requests correctly', () => {
|
|
101
|
+
rateLimiter.recordRequest(testIdentifier);
|
|
102
|
+
rateLimiter.recordRequest(testIdentifier);
|
|
103
|
+
|
|
104
|
+
const status = rateLimiter.getStatus(testIdentifier);
|
|
105
|
+
expect(status.remaining).toBe(3);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should handle rapid successive requests', () => {
|
|
109
|
+
// Record requests rapidly
|
|
110
|
+
for (let i = 0; i < 10; i++) {
|
|
111
|
+
rateLimiter.recordRequest(testIdentifier);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const status = rateLimiter.getStatus(testIdentifier);
|
|
115
|
+
expect(status.isLimited).toBe(true);
|
|
116
|
+
expect(status.remaining).toBe(0);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('getStatus', () => {
|
|
121
|
+
it('should return current status without modifying state', () => {
|
|
122
|
+
rateLimiter.recordRequest(testIdentifier);
|
|
123
|
+
|
|
124
|
+
const status1 = rateLimiter.getStatus(testIdentifier);
|
|
125
|
+
const status2 = rateLimiter.getStatus(testIdentifier);
|
|
126
|
+
|
|
127
|
+
expect(status1.remaining).toBe(status2.remaining);
|
|
128
|
+
expect(status1.isLimited).toBe(status2.isLimited);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('reset', () => {
|
|
133
|
+
it('should reset specific identifier', () => {
|
|
134
|
+
// Max out requests
|
|
135
|
+
for (let i = 0; i < 5; i++) {
|
|
136
|
+
rateLimiter.recordRequest(testIdentifier);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
expect(rateLimiter.isAllowed(testIdentifier).isLimited).toBe(true);
|
|
140
|
+
|
|
141
|
+
// Reset
|
|
142
|
+
rateLimiter.reset(testIdentifier);
|
|
143
|
+
|
|
144
|
+
// Should be allowed again
|
|
145
|
+
const result = rateLimiter.isAllowed(testIdentifier);
|
|
146
|
+
expect(result.isLimited).toBe(false);
|
|
147
|
+
expect(result.remaining).toBe(5);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should reset all identifiers when no specific identifier provided', () => {
|
|
151
|
+
const identifier1 = 'token-1';
|
|
152
|
+
const identifier2 = 'token-2';
|
|
153
|
+
|
|
154
|
+
// Max out both identifiers
|
|
155
|
+
for (let i = 0; i < 5; i++) {
|
|
156
|
+
rateLimiter.recordRequest(identifier1);
|
|
157
|
+
rateLimiter.recordRequest(identifier2);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
expect(rateLimiter.isAllowed(identifier1).isLimited).toBe(true);
|
|
161
|
+
expect(rateLimiter.isAllowed(identifier2).isLimited).toBe(true);
|
|
162
|
+
|
|
163
|
+
// Reset all
|
|
164
|
+
rateLimiter.reset();
|
|
165
|
+
|
|
166
|
+
// Both should be allowed again
|
|
167
|
+
expect(rateLimiter.isAllowed(identifier1).isLimited).toBe(false);
|
|
168
|
+
expect(rateLimiter.isAllowed(identifier2).isLimited).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('cleanup', () => {
|
|
173
|
+
it('should remove expired requests', async () => {
|
|
174
|
+
const shortWindowLimiter = new RateLimiter({
|
|
175
|
+
maxRequests: 5,
|
|
176
|
+
windowMs: 50, // 50ms
|
|
177
|
+
enableLogging: false,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Record some requests
|
|
181
|
+
shortWindowLimiter.recordRequest(testIdentifier);
|
|
182
|
+
shortWindowLimiter.recordRequest(testIdentifier);
|
|
183
|
+
|
|
184
|
+
expect(shortWindowLimiter.getStatus(testIdentifier).remaining).toBe(3);
|
|
185
|
+
|
|
186
|
+
// Wait for requests to expire
|
|
187
|
+
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
188
|
+
|
|
189
|
+
// Cleanup
|
|
190
|
+
shortWindowLimiter.cleanup();
|
|
191
|
+
|
|
192
|
+
// Should have full capacity again
|
|
193
|
+
const result = shortWindowLimiter.getStatus(testIdentifier);
|
|
194
|
+
expect(result.remaining).toBe(5);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('logging', () => {
|
|
199
|
+
it('should log when logging is enabled', () => {
|
|
200
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {
|
|
201
|
+
// Mock implementation for testing
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const loggingLimiter = new RateLimiter({
|
|
205
|
+
maxRequests: 2,
|
|
206
|
+
windowMs: 1000,
|
|
207
|
+
enableLogging: true,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
loggingLimiter.isAllowed(testIdentifier);
|
|
211
|
+
loggingLimiter.recordRequest(testIdentifier);
|
|
212
|
+
|
|
213
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Rate limit check'));
|
|
214
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Recorded request'));
|
|
215
|
+
|
|
216
|
+
consoleSpy.mockRestore();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should not log when logging is disabled', () => {
|
|
220
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {
|
|
221
|
+
// Mock implementation for testing
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
rateLimiter.isAllowed(testIdentifier);
|
|
225
|
+
rateLimiter.recordRequest(testIdentifier);
|
|
226
|
+
|
|
227
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
228
|
+
|
|
229
|
+
consoleSpy.mockRestore();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe('RateLimitError', () => {
|
|
234
|
+
it('should create error with correct properties', () => {
|
|
235
|
+
const resetTime = new Date();
|
|
236
|
+
const error = new RateLimitError('Rate limit exceeded', resetTime, 0);
|
|
237
|
+
|
|
238
|
+
expect(error.message).toBe('Rate limit exceeded');
|
|
239
|
+
expect(error.resetTime).toBe(resetTime);
|
|
240
|
+
expect(error.remaining).toBe(0);
|
|
241
|
+
expect(error.name).toBe('RateLimitError');
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('YNAB API compliance', () => {
|
|
246
|
+
it('should use YNAB API limits by default', () => {
|
|
247
|
+
const defaultLimiter = new RateLimiter();
|
|
248
|
+
|
|
249
|
+
// YNAB allows 200 requests per hour
|
|
250
|
+
const status = defaultLimiter.getStatus(testIdentifier);
|
|
251
|
+
expect(status.remaining).toBe(200);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should handle YNAB-scale request volumes', () => {
|
|
255
|
+
const ynabLimiter = new RateLimiter({
|
|
256
|
+
maxRequests: 200,
|
|
257
|
+
windowMs: 60 * 60 * 1000, // 1 hour
|
|
258
|
+
enableLogging: false,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Make 150 requests
|
|
262
|
+
for (let i = 0; i < 150; i++) {
|
|
263
|
+
ynabLimiter.recordRequest(testIdentifier);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const status = ynabLimiter.getStatus(testIdentifier);
|
|
267
|
+
expect(status.remaining).toBe(50);
|
|
268
|
+
expect(status.isLimited).toBe(false);
|
|
269
|
+
|
|
270
|
+
// Make 50 more requests to hit the limit
|
|
271
|
+
for (let i = 0; i < 50; i++) {
|
|
272
|
+
ynabLimiter.recordRequest(testIdentifier);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const limitedStatus = ynabLimiter.getStatus(testIdentifier);
|
|
276
|
+
expect(limitedStatus.remaining).toBe(0);
|
|
277
|
+
expect(limitedStatus.isLimited).toBe(true);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe('security considerations', () => {
|
|
282
|
+
it('should hash identifiers in logs to avoid token exposure', () => {
|
|
283
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {
|
|
284
|
+
// Mock implementation for testing
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const loggingLimiter = new RateLimiter({
|
|
288
|
+
maxRequests: 2,
|
|
289
|
+
windowMs: 1000,
|
|
290
|
+
enableLogging: true,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const sensitiveToken = 'very-secret-token-12345';
|
|
294
|
+
loggingLimiter.isAllowed(sensitiveToken);
|
|
295
|
+
|
|
296
|
+
// Check that the actual token is not in the log
|
|
297
|
+
const logCalls = consoleSpy.mock.calls.flat();
|
|
298
|
+
const hasActualToken = logCalls.some(
|
|
299
|
+
(call) => typeof call === 'string' && call.includes(sensitiveToken),
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
expect(hasActualToken).toBe(false);
|
|
303
|
+
|
|
304
|
+
// Check that a hashed version is used
|
|
305
|
+
const hasHashedToken = logCalls.some(
|
|
306
|
+
(call) => typeof call === 'string' && call.includes('token_'),
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
expect(hasHashedToken).toBe(true);
|
|
310
|
+
|
|
311
|
+
consoleSpy.mockRestore();
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
});
|