#
tokens: 30571/50000 1/614 files (page 50/59)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 50 of 59. Use http://codebase.md/czlonkowski/n8n-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── _config.yml
├── .claude
│   └── agents
│       ├── code-reviewer.md
│       ├── context-manager.md
│       ├── debugger.md
│       ├── deployment-engineer.md
│       ├── mcp-backend-engineer.md
│       ├── n8n-mcp-tester.md
│       ├── technical-researcher.md
│       └── test-automator.md
├── .dockerignore
├── .env.docker
├── .env.example
├── .env.n8n.example
├── .env.test
├── .env.test.example
├── .github
│   ├── ABOUT.md
│   ├── BENCHMARK_THRESHOLDS.md
│   ├── FUNDING.yml
│   ├── gh-pages.yml
│   ├── secret_scanning.yml
│   └── workflows
│       ├── benchmark-pr.yml
│       ├── benchmark.yml
│       ├── docker-build-fast.yml
│       ├── docker-build-n8n.yml
│       ├── docker-build.yml
│       ├── release.yml
│       ├── test.yml
│       └── update-n8n-deps.yml
├── .gitignore
├── .npmignore
├── ATTRIBUTION.md
├── CHANGELOG.md
├── CLAUDE.md
├── codecov.yml
├── coverage.json
├── data
│   ├── .gitkeep
│   ├── nodes.db
│   ├── nodes.db-shm
│   ├── nodes.db-wal
│   └── templates.db
├── deploy
│   └── quick-deploy-n8n.sh
├── docker
│   ├── docker-entrypoint.sh
│   ├── n8n-mcp
│   ├── parse-config.js
│   └── README.md
├── docker-compose.buildkit.yml
├── docker-compose.extract.yml
├── docker-compose.n8n.yml
├── docker-compose.override.yml.example
├── docker-compose.test-n8n.yml
├── docker-compose.yml
├── Dockerfile
├── Dockerfile.railway
├── Dockerfile.test
├── docs
│   ├── AUTOMATED_RELEASES.md
│   ├── BENCHMARKS.md
│   ├── CHANGELOG.md
│   ├── CLAUDE_CODE_SETUP.md
│   ├── CLAUDE_INTERVIEW.md
│   ├── CODECOV_SETUP.md
│   ├── CODEX_SETUP.md
│   ├── CURSOR_SETUP.md
│   ├── DEPENDENCY_UPDATES.md
│   ├── DOCKER_README.md
│   ├── DOCKER_TROUBLESHOOTING.md
│   ├── FINAL_AI_VALIDATION_SPEC.md
│   ├── FLEXIBLE_INSTANCE_CONFIGURATION.md
│   ├── HTTP_DEPLOYMENT.md
│   ├── img
│   │   ├── cc_command.png
│   │   ├── cc_connected.png
│   │   ├── codex_connected.png
│   │   ├── cursor_tut.png
│   │   ├── Railway_api.png
│   │   ├── Railway_server_address.png
│   │   ├── vsc_ghcp_chat_agent_mode.png
│   │   ├── vsc_ghcp_chat_instruction_files.png
│   │   ├── vsc_ghcp_chat_thinking_tool.png
│   │   └── windsurf_tut.png
│   ├── INSTALLATION.md
│   ├── LIBRARY_USAGE.md
│   ├── local
│   │   ├── DEEP_DIVE_ANALYSIS_2025-10-02.md
│   │   ├── DEEP_DIVE_ANALYSIS_README.md
│   │   ├── Deep_dive_p1_p2.md
│   │   ├── integration-testing-plan.md
│   │   ├── integration-tests-phase1-summary.md
│   │   ├── N8N_AI_WORKFLOW_BUILDER_ANALYSIS.md
│   │   ├── P0_IMPLEMENTATION_PLAN.md
│   │   └── TEMPLATE_MINING_ANALYSIS.md
│   ├── MCP_ESSENTIALS_README.md
│   ├── MCP_QUICK_START_GUIDE.md
│   ├── N8N_DEPLOYMENT.md
│   ├── RAILWAY_DEPLOYMENT.md
│   ├── README_CLAUDE_SETUP.md
│   ├── README.md
│   ├── tools-documentation-usage.md
│   ├── VS_CODE_PROJECT_SETUP.md
│   ├── WINDSURF_SETUP.md
│   └── workflow-diff-examples.md
├── examples
│   └── enhanced-documentation-demo.js
├── fetch_log.txt
├── LICENSE
├── MEMORY_N8N_UPDATE.md
├── MEMORY_TEMPLATE_UPDATE.md
├── monitor_fetch.sh
├── N8N_HTTP_STREAMABLE_SETUP.md
├── n8n-nodes.db
├── P0-R3-TEST-PLAN.md
├── package-lock.json
├── package.json
├── package.runtime.json
├── PRIVACY.md
├── railway.json
├── README.md
├── renovate.json
├── scripts
│   ├── analyze-optimization.sh
│   ├── audit-schema-coverage.ts
│   ├── build-optimized.sh
│   ├── compare-benchmarks.js
│   ├── demo-optimization.sh
│   ├── deploy-http.sh
│   ├── deploy-to-vm.sh
│   ├── export-webhook-workflows.ts
│   ├── extract-changelog.js
│   ├── extract-from-docker.js
│   ├── extract-nodes-docker.sh
│   ├── extract-nodes-simple.sh
│   ├── format-benchmark-results.js
│   ├── generate-benchmark-stub.js
│   ├── generate-detailed-reports.js
│   ├── generate-test-summary.js
│   ├── http-bridge.js
│   ├── mcp-http-client.js
│   ├── migrate-nodes-fts.ts
│   ├── migrate-tool-docs.ts
│   ├── n8n-docs-mcp.service
│   ├── nginx-n8n-mcp.conf
│   ├── prebuild-fts5.ts
│   ├── prepare-release.js
│   ├── publish-npm-quick.sh
│   ├── publish-npm.sh
│   ├── quick-test.ts
│   ├── run-benchmarks-ci.js
│   ├── sync-runtime-version.js
│   ├── test-ai-validation-debug.ts
│   ├── test-code-node-enhancements.ts
│   ├── test-code-node-fixes.ts
│   ├── test-docker-config.sh
│   ├── test-docker-fingerprint.ts
│   ├── test-docker-optimization.sh
│   ├── test-docker.sh
│   ├── test-empty-connection-validation.ts
│   ├── test-error-message-tracking.ts
│   ├── test-error-output-validation.ts
│   ├── test-error-validation.js
│   ├── test-essentials.ts
│   ├── test-expression-code-validation.ts
│   ├── test-expression-format-validation.js
│   ├── test-fts5-search.ts
│   ├── test-fuzzy-fix.ts
│   ├── test-fuzzy-simple.ts
│   ├── test-helpers-validation.ts
│   ├── test-http-search.ts
│   ├── test-http.sh
│   ├── test-jmespath-validation.ts
│   ├── test-multi-tenant-simple.ts
│   ├── test-multi-tenant.ts
│   ├── test-n8n-integration.sh
│   ├── test-node-info.js
│   ├── test-node-type-validation.ts
│   ├── test-nodes-base-prefix.ts
│   ├── test-operation-validation.ts
│   ├── test-optimized-docker.sh
│   ├── test-release-automation.js
│   ├── test-search-improvements.ts
│   ├── test-security.ts
│   ├── test-single-session.sh
│   ├── test-sqljs-triggers.ts
│   ├── test-telemetry-debug.ts
│   ├── test-telemetry-direct.ts
│   ├── test-telemetry-env.ts
│   ├── test-telemetry-integration.ts
│   ├── test-telemetry-no-select.ts
│   ├── test-telemetry-security.ts
│   ├── test-telemetry-simple.ts
│   ├── test-typeversion-validation.ts
│   ├── test-url-configuration.ts
│   ├── test-user-id-persistence.ts
│   ├── test-webhook-validation.ts
│   ├── test-workflow-insert.ts
│   ├── test-workflow-sanitizer.ts
│   ├── test-workflow-tracking-debug.ts
│   ├── update-and-publish-prep.sh
│   ├── update-n8n-deps.js
│   ├── update-readme-version.js
│   ├── vitest-benchmark-json-reporter.js
│   └── vitest-benchmark-reporter.ts
├── SECURITY.md
├── src
│   ├── config
│   │   └── n8n-api.ts
│   ├── data
│   │   └── canonical-ai-tool-examples.json
│   ├── database
│   │   ├── database-adapter.ts
│   │   ├── migrations
│   │   │   └── add-template-node-configs.sql
│   │   ├── node-repository.ts
│   │   ├── nodes.db
│   │   ├── schema-optimized.sql
│   │   └── schema.sql
│   ├── errors
│   │   └── validation-service-error.ts
│   ├── http-server-single-session.ts
│   ├── http-server.ts
│   ├── index.ts
│   ├── loaders
│   │   └── node-loader.ts
│   ├── mappers
│   │   └── docs-mapper.ts
│   ├── mcp
│   │   ├── handlers-n8n-manager.ts
│   │   ├── handlers-workflow-diff.ts
│   │   ├── index.ts
│   │   ├── server.ts
│   │   ├── stdio-wrapper.ts
│   │   ├── tool-docs
│   │   │   ├── configuration
│   │   │   │   ├── get-node-as-tool-info.ts
│   │   │   │   ├── get-node-documentation.ts
│   │   │   │   ├── get-node-essentials.ts
│   │   │   │   ├── get-node-info.ts
│   │   │   │   ├── get-property-dependencies.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── search-node-properties.ts
│   │   │   ├── discovery
│   │   │   │   ├── get-database-statistics.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── list-ai-tools.ts
│   │   │   │   ├── list-nodes.ts
│   │   │   │   └── search-nodes.ts
│   │   │   ├── guides
│   │   │   │   ├── ai-agents-guide.ts
│   │   │   │   └── index.ts
│   │   │   ├── index.ts
│   │   │   ├── system
│   │   │   │   ├── index.ts
│   │   │   │   ├── n8n-diagnostic.ts
│   │   │   │   ├── n8n-health-check.ts
│   │   │   │   ├── n8n-list-available-tools.ts
│   │   │   │   └── tools-documentation.ts
│   │   │   ├── templates
│   │   │   │   ├── get-template.ts
│   │   │   │   ├── get-templates-for-task.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── list-node-templates.ts
│   │   │   │   ├── list-tasks.ts
│   │   │   │   ├── search-templates-by-metadata.ts
│   │   │   │   └── search-templates.ts
│   │   │   ├── types.ts
│   │   │   ├── validation
│   │   │   │   ├── index.ts
│   │   │   │   ├── validate-node-minimal.ts
│   │   │   │   ├── validate-node-operation.ts
│   │   │   │   ├── validate-workflow-connections.ts
│   │   │   │   ├── validate-workflow-expressions.ts
│   │   │   │   └── validate-workflow.ts
│   │   │   └── workflow_management
│   │   │       ├── index.ts
│   │   │       ├── n8n-autofix-workflow.ts
│   │   │       ├── n8n-create-workflow.ts
│   │   │       ├── n8n-delete-execution.ts
│   │   │       ├── n8n-delete-workflow.ts
│   │   │       ├── n8n-get-execution.ts
│   │   │       ├── n8n-get-workflow-details.ts
│   │   │       ├── n8n-get-workflow-minimal.ts
│   │   │       ├── n8n-get-workflow-structure.ts
│   │   │       ├── n8n-get-workflow.ts
│   │   │       ├── n8n-list-executions.ts
│   │   │       ├── n8n-list-workflows.ts
│   │   │       ├── n8n-trigger-webhook-workflow.ts
│   │   │       ├── n8n-update-full-workflow.ts
│   │   │       ├── n8n-update-partial-workflow.ts
│   │   │       └── n8n-validate-workflow.ts
│   │   ├── tools-documentation.ts
│   │   ├── tools-n8n-friendly.ts
│   │   ├── tools-n8n-manager.ts
│   │   ├── tools.ts
│   │   └── workflow-examples.ts
│   ├── mcp-engine.ts
│   ├── mcp-tools-engine.ts
│   ├── n8n
│   │   ├── MCPApi.credentials.ts
│   │   └── MCPNode.node.ts
│   ├── parsers
│   │   ├── node-parser.ts
│   │   ├── property-extractor.ts
│   │   └── simple-parser.ts
│   ├── scripts
│   │   ├── debug-http-search.ts
│   │   ├── extract-from-docker.ts
│   │   ├── fetch-templates-robust.ts
│   │   ├── fetch-templates.ts
│   │   ├── rebuild-database.ts
│   │   ├── rebuild-optimized.ts
│   │   ├── rebuild.ts
│   │   ├── sanitize-templates.ts
│   │   ├── seed-canonical-ai-examples.ts
│   │   ├── test-autofix-documentation.ts
│   │   ├── test-autofix-workflow.ts
│   │   ├── test-execution-filtering.ts
│   │   ├── test-node-suggestions.ts
│   │   ├── test-protocol-negotiation.ts
│   │   ├── test-summary.ts
│   │   ├── test-webhook-autofix.ts
│   │   ├── validate.ts
│   │   └── validation-summary.ts
│   ├── services
│   │   ├── ai-node-validator.ts
│   │   ├── ai-tool-validators.ts
│   │   ├── confidence-scorer.ts
│   │   ├── config-validator.ts
│   │   ├── enhanced-config-validator.ts
│   │   ├── example-generator.ts
│   │   ├── execution-processor.ts
│   │   ├── expression-format-validator.ts
│   │   ├── expression-validator.ts
│   │   ├── n8n-api-client.ts
│   │   ├── n8n-validation.ts
│   │   ├── node-documentation-service.ts
│   │   ├── node-similarity-service.ts
│   │   ├── node-specific-validators.ts
│   │   ├── operation-similarity-service.ts
│   │   ├── property-dependencies.ts
│   │   ├── property-filter.ts
│   │   ├── resource-similarity-service.ts
│   │   ├── sqlite-storage-service.ts
│   │   ├── task-templates.ts
│   │   ├── universal-expression-validator.ts
│   │   ├── workflow-auto-fixer.ts
│   │   ├── workflow-diff-engine.ts
│   │   └── workflow-validator.ts
│   ├── telemetry
│   │   ├── batch-processor.ts
│   │   ├── config-manager.ts
│   │   ├── early-error-logger.ts
│   │   ├── error-sanitization-utils.ts
│   │   ├── error-sanitizer.ts
│   │   ├── event-tracker.ts
│   │   ├── event-validator.ts
│   │   ├── index.ts
│   │   ├── performance-monitor.ts
│   │   ├── rate-limiter.ts
│   │   ├── startup-checkpoints.ts
│   │   ├── telemetry-error.ts
│   │   ├── telemetry-manager.ts
│   │   ├── telemetry-types.ts
│   │   └── workflow-sanitizer.ts
│   ├── templates
│   │   ├── batch-processor.ts
│   │   ├── metadata-generator.ts
│   │   ├── README.md
│   │   ├── template-fetcher.ts
│   │   ├── template-repository.ts
│   │   └── template-service.ts
│   ├── types
│   │   ├── index.ts
│   │   ├── instance-context.ts
│   │   ├── n8n-api.ts
│   │   ├── node-types.ts
│   │   └── workflow-diff.ts
│   └── utils
│       ├── auth.ts
│       ├── bridge.ts
│       ├── cache-utils.ts
│       ├── console-manager.ts
│       ├── documentation-fetcher.ts
│       ├── enhanced-documentation-fetcher.ts
│       ├── error-handler.ts
│       ├── example-generator.ts
│       ├── fixed-collection-validator.ts
│       ├── logger.ts
│       ├── mcp-client.ts
│       ├── n8n-errors.ts
│       ├── node-source-extractor.ts
│       ├── node-type-normalizer.ts
│       ├── node-type-utils.ts
│       ├── node-utils.ts
│       ├── npm-version-checker.ts
│       ├── protocol-version.ts
│       ├── simple-cache.ts
│       ├── ssrf-protection.ts
│       ├── template-node-resolver.ts
│       ├── template-sanitizer.ts
│       ├── url-detector.ts
│       ├── validation-schemas.ts
│       └── version.ts
├── test-output.txt
├── test-reinit-fix.sh
├── tests
│   ├── __snapshots__
│   │   └── .gitkeep
│   ├── auth.test.ts
│   ├── benchmarks
│   │   ├── database-queries.bench.ts
│   │   ├── index.ts
│   │   ├── mcp-tools.bench.ts
│   │   ├── mcp-tools.bench.ts.disabled
│   │   ├── mcp-tools.bench.ts.skip
│   │   ├── node-loading.bench.ts.disabled
│   │   ├── README.md
│   │   ├── search-operations.bench.ts.disabled
│   │   └── validation-performance.bench.ts.disabled
│   ├── bridge.test.ts
│   ├── comprehensive-extraction-test.js
│   ├── data
│   │   └── .gitkeep
│   ├── debug-slack-doc.js
│   ├── demo-enhanced-documentation.js
│   ├── docker-tests-README.md
│   ├── error-handler.test.ts
│   ├── examples
│   │   └── using-database-utils.test.ts
│   ├── extracted-nodes-db
│   │   ├── database-import.json
│   │   ├── extraction-report.json
│   │   ├── insert-nodes.sql
│   │   ├── n8n-nodes-base__Airtable.json
│   │   ├── n8n-nodes-base__Discord.json
│   │   ├── n8n-nodes-base__Function.json
│   │   ├── n8n-nodes-base__HttpRequest.json
│   │   ├── n8n-nodes-base__If.json
│   │   ├── n8n-nodes-base__Slack.json
│   │   ├── n8n-nodes-base__SplitInBatches.json
│   │   └── n8n-nodes-base__Webhook.json
│   ├── factories
│   │   ├── node-factory.ts
│   │   └── property-definition-factory.ts
│   ├── fixtures
│   │   ├── .gitkeep
│   │   ├── database
│   │   │   └── test-nodes.json
│   │   ├── factories
│   │   │   ├── node.factory.ts
│   │   │   └── parser-node.factory.ts
│   │   └── template-configs.ts
│   ├── helpers
│   │   └── env-helpers.ts
│   ├── http-server-auth.test.ts
│   ├── integration
│   │   ├── ai-validation
│   │   │   ├── ai-agent-validation.test.ts
│   │   │   ├── ai-tool-validation.test.ts
│   │   │   ├── chat-trigger-validation.test.ts
│   │   │   ├── e2e-validation.test.ts
│   │   │   ├── helpers.ts
│   │   │   ├── llm-chain-validation.test.ts
│   │   │   ├── README.md
│   │   │   └── TEST_REPORT.md
│   │   ├── ci
│   │   │   └── database-population.test.ts
│   │   ├── database
│   │   │   ├── connection-management.test.ts
│   │   │   ├── empty-database.test.ts
│   │   │   ├── fts5-search.test.ts
│   │   │   ├── node-fts5-search.test.ts
│   │   │   ├── node-repository.test.ts
│   │   │   ├── performance.test.ts
│   │   │   ├── template-node-configs.test.ts
│   │   │   ├── template-repository.test.ts
│   │   │   ├── test-utils.ts
│   │   │   └── transactions.test.ts
│   │   ├── database-integration.test.ts
│   │   ├── docker
│   │   │   ├── docker-config.test.ts
│   │   │   ├── docker-entrypoint.test.ts
│   │   │   └── test-helpers.ts
│   │   ├── flexible-instance-config.test.ts
│   │   ├── mcp
│   │   │   └── template-examples-e2e.test.ts
│   │   ├── mcp-protocol
│   │   │   ├── basic-connection.test.ts
│   │   │   ├── error-handling.test.ts
│   │   │   ├── performance.test.ts
│   │   │   ├── protocol-compliance.test.ts
│   │   │   ├── README.md
│   │   │   ├── session-management.test.ts
│   │   │   ├── test-helpers.ts
│   │   │   ├── tool-invocation.test.ts
│   │   │   └── workflow-error-validation.test.ts
│   │   ├── msw-setup.test.ts
│   │   ├── n8n-api
│   │   │   ├── executions
│   │   │   │   ├── delete-execution.test.ts
│   │   │   │   ├── get-execution.test.ts
│   │   │   │   ├── list-executions.test.ts
│   │   │   │   └── trigger-webhook.test.ts
│   │   │   ├── scripts
│   │   │   │   └── cleanup-orphans.ts
│   │   │   ├── system
│   │   │   │   ├── diagnostic.test.ts
│   │   │   │   ├── health-check.test.ts
│   │   │   │   └── list-tools.test.ts
│   │   │   ├── test-connection.ts
│   │   │   ├── types
│   │   │   │   └── mcp-responses.ts
│   │   │   ├── utils
│   │   │   │   ├── cleanup-helpers.ts
│   │   │   │   ├── credentials.ts
│   │   │   │   ├── factories.ts
│   │   │   │   ├── fixtures.ts
│   │   │   │   ├── mcp-context.ts
│   │   │   │   ├── n8n-client.ts
│   │   │   │   ├── node-repository.ts
│   │   │   │   ├── response-types.ts
│   │   │   │   ├── test-context.ts
│   │   │   │   └── webhook-workflows.ts
│   │   │   └── workflows
│   │   │       ├── autofix-workflow.test.ts
│   │   │       ├── create-workflow.test.ts
│   │   │       ├── delete-workflow.test.ts
│   │   │       ├── get-workflow-details.test.ts
│   │   │       ├── get-workflow-minimal.test.ts
│   │   │       ├── get-workflow-structure.test.ts
│   │   │       ├── get-workflow.test.ts
│   │   │       ├── list-workflows.test.ts
│   │   │       ├── smart-parameters.test.ts
│   │   │       ├── update-partial-workflow.test.ts
│   │   │       ├── update-workflow.test.ts
│   │   │       └── validate-workflow.test.ts
│   │   ├── security
│   │   │   ├── command-injection-prevention.test.ts
│   │   │   └── rate-limiting.test.ts
│   │   ├── setup
│   │   │   ├── integration-setup.ts
│   │   │   └── msw-test-server.ts
│   │   ├── telemetry
│   │   │   ├── docker-user-id-stability.test.ts
│   │   │   └── mcp-telemetry.test.ts
│   │   ├── templates
│   │   │   └── metadata-operations.test.ts
│   │   └── workflow-creation-node-type-format.test.ts
│   ├── logger.test.ts
│   ├── MOCKING_STRATEGY.md
│   ├── mocks
│   │   ├── n8n-api
│   │   │   ├── data
│   │   │   │   ├── credentials.ts
│   │   │   │   ├── executions.ts
│   │   │   │   └── workflows.ts
│   │   │   ├── handlers.ts
│   │   │   └── index.ts
│   │   └── README.md
│   ├── node-storage-export.json
│   ├── setup
│   │   ├── global-setup.ts
│   │   ├── msw-setup.ts
│   │   ├── TEST_ENV_DOCUMENTATION.md
│   │   └── test-env.ts
│   ├── test-database-extraction.js
│   ├── test-direct-extraction.js
│   ├── test-enhanced-documentation.js
│   ├── test-enhanced-integration.js
│   ├── test-mcp-extraction.js
│   ├── test-mcp-server-extraction.js
│   ├── test-mcp-tools-integration.js
│   ├── test-node-documentation-service.js
│   ├── test-node-list.js
│   ├── test-package-info.js
│   ├── test-parsing-operations.js
│   ├── test-slack-node-complete.js
│   ├── test-small-rebuild.js
│   ├── test-sqlite-search.js
│   ├── test-storage-system.js
│   ├── unit
│   │   ├── __mocks__
│   │   │   ├── n8n-nodes-base.test.ts
│   │   │   ├── n8n-nodes-base.ts
│   │   │   └── README.md
│   │   ├── database
│   │   │   ├── __mocks__
│   │   │   │   └── better-sqlite3.ts
│   │   │   ├── database-adapter-unit.test.ts
│   │   │   ├── node-repository-core.test.ts
│   │   │   ├── node-repository-operations.test.ts
│   │   │   ├── node-repository-outputs.test.ts
│   │   │   ├── README.md
│   │   │   └── template-repository-core.test.ts
│   │   ├── docker
│   │   │   ├── config-security.test.ts
│   │   │   ├── edge-cases.test.ts
│   │   │   ├── parse-config.test.ts
│   │   │   └── serve-command.test.ts
│   │   ├── errors
│   │   │   └── validation-service-error.test.ts
│   │   ├── examples
│   │   │   └── using-n8n-nodes-base-mock.test.ts
│   │   ├── flexible-instance-security-advanced.test.ts
│   │   ├── flexible-instance-security.test.ts
│   │   ├── http-server
│   │   │   └── multi-tenant-support.test.ts
│   │   ├── http-server-n8n-mode.test.ts
│   │   ├── http-server-n8n-reinit.test.ts
│   │   ├── http-server-session-management.test.ts
│   │   ├── loaders
│   │   │   └── node-loader.test.ts
│   │   ├── mappers
│   │   │   └── docs-mapper.test.ts
│   │   ├── mcp
│   │   │   ├── get-node-essentials-examples.test.ts
│   │   │   ├── handlers-n8n-manager-simple.test.ts
│   │   │   ├── handlers-n8n-manager.test.ts
│   │   │   ├── handlers-workflow-diff.test.ts
│   │   │   ├── lru-cache-behavior.test.ts
│   │   │   ├── multi-tenant-tool-listing.test.ts.disabled
│   │   │   ├── parameter-validation.test.ts
│   │   │   ├── search-nodes-examples.test.ts
│   │   │   ├── tools-documentation.test.ts
│   │   │   └── tools.test.ts
│   │   ├── monitoring
│   │   │   └── cache-metrics.test.ts
│   │   ├── MULTI_TENANT_TEST_COVERAGE.md
│   │   ├── multi-tenant-integration.test.ts
│   │   ├── parsers
│   │   │   ├── node-parser-outputs.test.ts
│   │   │   ├── node-parser.test.ts
│   │   │   ├── property-extractor.test.ts
│   │   │   └── simple-parser.test.ts
│   │   ├── scripts
│   │   │   └── fetch-templates-extraction.test.ts
│   │   ├── services
│   │   │   ├── ai-node-validator.test.ts
│   │   │   ├── ai-tool-validators.test.ts
│   │   │   ├── confidence-scorer.test.ts
│   │   │   ├── config-validator-basic.test.ts
│   │   │   ├── config-validator-edge-cases.test.ts
│   │   │   ├── config-validator-node-specific.test.ts
│   │   │   ├── config-validator-security.test.ts
│   │   │   ├── debug-validator.test.ts
│   │   │   ├── enhanced-config-validator-integration.test.ts
│   │   │   ├── enhanced-config-validator-operations.test.ts
│   │   │   ├── enhanced-config-validator.test.ts
│   │   │   ├── example-generator.test.ts
│   │   │   ├── execution-processor.test.ts
│   │   │   ├── expression-format-validator.test.ts
│   │   │   ├── expression-validator-edge-cases.test.ts
│   │   │   ├── expression-validator.test.ts
│   │   │   ├── fixed-collection-validation.test.ts
│   │   │   ├── loop-output-edge-cases.test.ts
│   │   │   ├── n8n-api-client.test.ts
│   │   │   ├── n8n-validation.test.ts
│   │   │   ├── node-similarity-service.test.ts
│   │   │   ├── node-specific-validators.test.ts
│   │   │   ├── operation-similarity-service-comprehensive.test.ts
│   │   │   ├── operation-similarity-service.test.ts
│   │   │   ├── property-dependencies.test.ts
│   │   │   ├── property-filter-edge-cases.test.ts
│   │   │   ├── property-filter.test.ts
│   │   │   ├── resource-similarity-service-comprehensive.test.ts
│   │   │   ├── resource-similarity-service.test.ts
│   │   │   ├── task-templates.test.ts
│   │   │   ├── template-service.test.ts
│   │   │   ├── universal-expression-validator.test.ts
│   │   │   ├── validation-fixes.test.ts
│   │   │   ├── workflow-auto-fixer.test.ts
│   │   │   ├── workflow-diff-engine.test.ts
│   │   │   ├── workflow-fixed-collection-validation.test.ts
│   │   │   ├── workflow-validator-comprehensive.test.ts
│   │   │   ├── workflow-validator-edge-cases.test.ts
│   │   │   ├── workflow-validator-error-outputs.test.ts
│   │   │   ├── workflow-validator-expression-format.test.ts
│   │   │   ├── workflow-validator-loops-simple.test.ts
│   │   │   ├── workflow-validator-loops.test.ts
│   │   │   ├── workflow-validator-mocks.test.ts
│   │   │   ├── workflow-validator-performance.test.ts
│   │   │   ├── workflow-validator-with-mocks.test.ts
│   │   │   └── workflow-validator.test.ts
│   │   ├── telemetry
│   │   │   ├── batch-processor.test.ts
│   │   │   ├── config-manager.test.ts
│   │   │   ├── event-tracker.test.ts
│   │   │   ├── event-validator.test.ts
│   │   │   ├── rate-limiter.test.ts
│   │   │   ├── telemetry-error.test.ts
│   │   │   ├── telemetry-manager.test.ts
│   │   │   ├── v2.18.3-fixes-verification.test.ts
│   │   │   └── workflow-sanitizer.test.ts
│   │   ├── templates
│   │   │   ├── batch-processor.test.ts
│   │   │   ├── metadata-generator.test.ts
│   │   │   ├── template-repository-metadata.test.ts
│   │   │   └── template-repository-security.test.ts
│   │   ├── test-env-example.test.ts
│   │   ├── test-infrastructure.test.ts
│   │   ├── types
│   │   │   ├── instance-context-coverage.test.ts
│   │   │   └── instance-context-multi-tenant.test.ts
│   │   ├── utils
│   │   │   ├── auth-timing-safe.test.ts
│   │   │   ├── cache-utils.test.ts
│   │   │   ├── console-manager.test.ts
│   │   │   ├── database-utils.test.ts
│   │   │   ├── fixed-collection-validator.test.ts
│   │   │   ├── n8n-errors.test.ts
│   │   │   ├── node-type-normalizer.test.ts
│   │   │   ├── node-type-utils.test.ts
│   │   │   ├── node-utils.test.ts
│   │   │   ├── simple-cache-memory-leak-fix.test.ts
│   │   │   ├── ssrf-protection.test.ts
│   │   │   └── template-node-resolver.test.ts
│   │   └── validation-fixes.test.ts
│   └── utils
│       ├── assertions.ts
│       ├── builders
│       │   └── workflow.builder.ts
│       ├── data-generators.ts
│       ├── database-utils.ts
│       ├── README.md
│       └── test-helpers.ts
├── thumbnail.png
├── tsconfig.build.json
├── tsconfig.json
├── types
│   ├── mcp.d.ts
│   └── test-env.d.ts
├── verify-telemetry-fix.js
├── versioned-nodes.md
├── vitest.config.benchmark.ts
├── vitest.config.integration.ts
└── vitest.config.ts
```

# Files

--------------------------------------------------------------------------------
/tests/unit/services/node-specific-validators.test.ts:
--------------------------------------------------------------------------------

```typescript
   1 | import { describe, it, expect, beforeEach } from 'vitest';
   2 | import { NodeSpecificValidators, NodeValidationContext } from '@/services/node-specific-validators';
   3 | import { ValidationError, ValidationWarning } from '@/services/config-validator';
   4 | 
   5 | describe('NodeSpecificValidators', () => {
   6 |   let context: NodeValidationContext;
   7 | 
   8 |   beforeEach(() => {
   9 |     context = {
  10 |       config: {},
  11 |       errors: [],
  12 |       warnings: [],
  13 |       suggestions: [],
  14 |       autofix: {}
  15 |     };
  16 |   });
  17 | 
  18 |   describe('validateSlack', () => {
  19 |     describe('message send operation', () => {
  20 |       beforeEach(() => {
  21 |         context.config = {
  22 |           resource: 'message',
  23 |           operation: 'send'
  24 |         };
  25 |       });
  26 | 
  27 |       it('should require channel for sending messages', () => {
  28 |         NodeSpecificValidators.validateSlack(context);
  29 |         
  30 |         expect(context.errors).toHaveLength(2); // channel and text errors
  31 |         expect(context.errors[0]).toMatchObject({
  32 |           type: 'missing_required',
  33 |           property: 'channel',
  34 |           message: 'Channel is required to send a message'
  35 |         });
  36 |       });
  37 | 
  38 |       it('should accept channelId as alternative to channel', () => {
  39 |         context.config.channelId = 'C1234567890';
  40 |         context.config.text = 'Hello';
  41 |         
  42 |         NodeSpecificValidators.validateSlack(context);
  43 |         
  44 |         const channelErrors = context.errors.filter(e => e.property === 'channel');
  45 |         expect(channelErrors).toHaveLength(0);
  46 |       });
  47 | 
  48 |       it('should require message content', () => {
  49 |         context.config.channel = '#general';
  50 |         
  51 |         NodeSpecificValidators.validateSlack(context);
  52 |         
  53 |         expect(context.errors).toContainEqual({
  54 |           type: 'missing_required',
  55 |           property: 'text',
  56 |           message: 'Message content is required - provide text, blocks, or attachments',
  57 |           fix: 'Add text field with your message content'
  58 |         });
  59 |       });
  60 | 
  61 |       it('should accept blocks as alternative to text', () => {
  62 |         context.config.channel = '#general';
  63 |         context.config.blocks = [{ type: 'section', text: { type: 'mrkdwn', text: 'Hello' } }];
  64 |         
  65 |         NodeSpecificValidators.validateSlack(context);
  66 |         
  67 |         const textErrors = context.errors.filter(e => e.property === 'text');
  68 |         expect(textErrors).toHaveLength(0);
  69 |       });
  70 | 
  71 |       it('should accept attachments as alternative to text', () => {
  72 |         context.config.channel = '#general';
  73 |         context.config.attachments = [{ text: 'Attachment text' }];
  74 |         
  75 |         NodeSpecificValidators.validateSlack(context);
  76 |         
  77 |         const textErrors = context.errors.filter(e => e.property === 'text');
  78 |         expect(textErrors).toHaveLength(0);
  79 |       });
  80 | 
  81 |       it('should warn about text exceeding character limit', () => {
  82 |         context.config.channel = '#general';
  83 |         context.config.text = 'a'.repeat(40001);
  84 |         
  85 |         NodeSpecificValidators.validateSlack(context);
  86 |         
  87 |         expect(context.warnings).toContainEqual({
  88 |           type: 'inefficient',
  89 |           property: 'text',
  90 |           message: 'Message text exceeds Slack\'s 40,000 character limit',
  91 |           suggestion: 'Split into multiple messages or use a file upload'
  92 |         });
  93 |       });
  94 | 
  95 |       it('should warn about missing threadTs when replying to thread', () => {
  96 |         context.config.channel = '#general';
  97 |         context.config.text = 'Reply';
  98 |         context.config.replyToThread = true;
  99 |         
 100 |         NodeSpecificValidators.validateSlack(context);
 101 |         
 102 |         expect(context.warnings).toContainEqual({
 103 |           type: 'missing_common',
 104 |           property: 'threadTs',
 105 |           message: 'Thread timestamp required when replying to thread',
 106 |           suggestion: 'Set threadTs to the timestamp of the thread parent message'
 107 |         });
 108 |       });
 109 | 
 110 |       it('should suggest linkNames for mentions', () => {
 111 |         context.config.channel = '#general';
 112 |         context.config.text = 'Hello @user';
 113 |         
 114 |         NodeSpecificValidators.validateSlack(context);
 115 |         
 116 |         expect(context.suggestions).toContain('Set linkNames=true to convert @mentions to user links');
 117 |         expect(context.autofix.linkNames).toBe(true);
 118 |       });
 119 |     });
 120 | 
 121 |     describe('message update operation', () => {
 122 |       beforeEach(() => {
 123 |         context.config = {
 124 |           resource: 'message',
 125 |           operation: 'update'
 126 |         };
 127 |       });
 128 | 
 129 |       it('should require timestamp for updating messages', () => {
 130 |         NodeSpecificValidators.validateSlack(context);
 131 |         
 132 |         expect(context.errors).toContainEqual({
 133 |           type: 'missing_required',
 134 |           property: 'ts',
 135 |           message: 'Message timestamp (ts) is required to update a message',
 136 |           fix: 'Provide the timestamp of the message to update'
 137 |         });
 138 |       });
 139 | 
 140 |       it('should require channel for updating messages', () => {
 141 |         context.config.ts = '1234567890.123456';
 142 |         
 143 |         NodeSpecificValidators.validateSlack(context);
 144 |         
 145 |         expect(context.errors).toContainEqual({
 146 |           type: 'missing_required',
 147 |           property: 'channel',
 148 |           message: 'Channel is required to update a message',
 149 |           fix: 'Provide the channel where the message exists'
 150 |         });
 151 |       });
 152 |     });
 153 | 
 154 |     describe('message delete operation', () => {
 155 |       beforeEach(() => {
 156 |         context.config = {
 157 |           resource: 'message',
 158 |           operation: 'delete'
 159 |         };
 160 |       });
 161 | 
 162 |       it('should require timestamp for deleting messages', () => {
 163 |         NodeSpecificValidators.validateSlack(context);
 164 |         
 165 |         expect(context.errors).toContainEqual({
 166 |           type: 'missing_required',
 167 |           property: 'ts',
 168 |           message: 'Message timestamp (ts) is required to delete a message',
 169 |           fix: 'Provide the timestamp of the message to delete'
 170 |         });
 171 |       });
 172 | 
 173 |       it('should warn about permanent deletion', () => {
 174 |         context.config.ts = '1234567890.123456';
 175 |         context.config.channel = '#general';
 176 |         
 177 |         NodeSpecificValidators.validateSlack(context);
 178 |         
 179 |         expect(context.warnings).toContainEqual({
 180 |           type: 'security',
 181 |           message: 'Message deletion is permanent and cannot be undone',
 182 |           suggestion: 'Consider archiving or updating the message instead if you need to preserve history'
 183 |         });
 184 |       });
 185 |     });
 186 | 
 187 |     describe('channel create operation', () => {
 188 |       beforeEach(() => {
 189 |         context.config = {
 190 |           resource: 'channel',
 191 |           operation: 'create'
 192 |         };
 193 |       });
 194 | 
 195 |       it('should require channel name', () => {
 196 |         NodeSpecificValidators.validateSlack(context);
 197 |         
 198 |         expect(context.errors).toContainEqual({
 199 |           type: 'missing_required',
 200 |           property: 'name',
 201 |           message: 'Channel name is required',
 202 |           fix: 'Provide a channel name (lowercase, no spaces, 1-80 characters)'
 203 |         });
 204 |       });
 205 | 
 206 |       it('should validate channel name format', () => {
 207 |         context.config.name = 'Test Channel';
 208 |         
 209 |         NodeSpecificValidators.validateSlack(context);
 210 |         
 211 |         expect(context.errors).toContainEqual({
 212 |           type: 'invalid_value',
 213 |           property: 'name',
 214 |           message: 'Channel names cannot contain spaces',
 215 |           fix: 'Use hyphens or underscores instead of spaces'
 216 |         });
 217 |       });
 218 | 
 219 |       it('should require lowercase channel names', () => {
 220 |         context.config.name = 'TestChannel';
 221 |         
 222 |         NodeSpecificValidators.validateSlack(context);
 223 |         
 224 |         expect(context.errors).toContainEqual({
 225 |           type: 'invalid_value',
 226 |           property: 'name',
 227 |           message: 'Channel names must be lowercase',
 228 |           fix: 'Convert the channel name to lowercase'
 229 |         });
 230 |       });
 231 | 
 232 |       it('should validate channel name length', () => {
 233 |         context.config.name = 'a'.repeat(81);
 234 |         
 235 |         NodeSpecificValidators.validateSlack(context);
 236 |         
 237 |         expect(context.errors).toContainEqual({
 238 |           type: 'invalid_value',
 239 |           property: 'name',
 240 |           message: 'Channel name exceeds 80 character limit',
 241 |           fix: 'Shorten the channel name'
 242 |         });
 243 |       });
 244 |     });
 245 | 
 246 |     describe('user operations', () => {
 247 |       it('should require user identifier for get operation', () => {
 248 |         context.config = {
 249 |           resource: 'user',
 250 |           operation: 'get'
 251 |         };
 252 |         
 253 |         NodeSpecificValidators.validateSlack(context);
 254 |         
 255 |         expect(context.errors).toContainEqual({
 256 |           type: 'missing_required',
 257 |           property: 'user',
 258 |           message: 'User identifier required - use email, user ID, or username',
 259 |           fix: 'Set user to an email like "[email protected]" or user ID like "U1234567890"'
 260 |         });
 261 |       });
 262 |     });
 263 | 
 264 |     describe('error handling', () => {
 265 |       it('should suggest error handling for Slack operations', () => {
 266 |         context.config = {
 267 |           resource: 'message',
 268 |           operation: 'send',
 269 |           channel: '#general',
 270 |           text: 'Hello'
 271 |         };
 272 |         
 273 |         NodeSpecificValidators.validateSlack(context);
 274 |         
 275 |         expect(context.warnings).toContainEqual({
 276 |           type: 'best_practice',
 277 |           property: 'errorHandling',
 278 |           message: 'Slack API can have rate limits and transient failures',
 279 |           suggestion: 'Add onError: "continueRegularOutput" with retryOnFail for resilience'
 280 |         });
 281 |         
 282 |         expect(context.autofix).toMatchObject({
 283 |           onError: 'continueRegularOutput',
 284 |           retryOnFail: true,
 285 |           maxTries: 2,
 286 |           waitBetweenTries: 3000
 287 |         });
 288 |       });
 289 | 
 290 |       it('should warn about deprecated continueOnFail', () => {
 291 |         context.config = {
 292 |           resource: 'message',
 293 |           operation: 'send',
 294 |           channel: '#general',
 295 |           text: 'Hello',
 296 |           continueOnFail: true
 297 |         };
 298 |         
 299 |         NodeSpecificValidators.validateSlack(context);
 300 |         
 301 |         expect(context.warnings).toContainEqual({
 302 |           type: 'deprecated',
 303 |           property: 'continueOnFail',
 304 |           message: 'continueOnFail is deprecated. Use onError instead',
 305 |           suggestion: 'Replace with onError: "continueRegularOutput"'
 306 |         });
 307 |       });
 308 |     });
 309 |   });
 310 | 
 311 |   describe('validateGoogleSheets', () => {
 312 |     describe('common validations', () => {
 313 |       it('should require spreadsheet ID', () => {
 314 |         context.config = {
 315 |           operation: 'read'
 316 |         };
 317 |         
 318 |         NodeSpecificValidators.validateGoogleSheets(context);
 319 |         
 320 |         expect(context.errors).toContainEqual({
 321 |           type: 'missing_required',
 322 |           property: 'sheetId',
 323 |           message: 'Spreadsheet ID is required',
 324 |           fix: 'Provide the Google Sheets document ID from the URL'
 325 |         });
 326 |       });
 327 | 
 328 |       it('should accept documentId as alternative to sheetId', () => {
 329 |         context.config = {
 330 |           operation: 'read',
 331 |           documentId: '1234567890',
 332 |           range: 'Sheet1!A:B'
 333 |         };
 334 |         
 335 |         NodeSpecificValidators.validateGoogleSheets(context);
 336 |         
 337 |         const sheetIdErrors = context.errors.filter(e => e.property === 'sheetId');
 338 |         expect(sheetIdErrors).toHaveLength(0);
 339 |       });
 340 |     });
 341 | 
 342 |     describe('append operation', () => {
 343 |       beforeEach(() => {
 344 |         context.config = {
 345 |           operation: 'append',
 346 |           sheetId: '1234567890'
 347 |         };
 348 |       });
 349 | 
 350 |       it('should require range or columns for append', () => {
 351 |         NodeSpecificValidators.validateGoogleSheets(context);
 352 | 
 353 |         expect(context.errors).toContainEqual({
 354 |           type: 'missing_required',
 355 |           property: 'range',
 356 |           message: 'Range or columns mapping is required for append operation',
 357 |           fix: 'Specify range like "Sheet1!A:B" OR use columns with mappingMode'
 358 |         });
 359 |       });
 360 | 
 361 |       it('should suggest valueInputMode', () => {
 362 |         context.config.range = 'Sheet1!A:B';
 363 |         
 364 |         NodeSpecificValidators.validateGoogleSheets(context);
 365 |         
 366 |         expect(context.warnings).toContainEqual({
 367 |           type: 'missing_common',
 368 |           property: 'options.valueInputMode',
 369 |           message: 'Consider setting valueInputMode for proper data formatting',
 370 |           suggestion: 'Use "USER_ENTERED" to parse formulas and dates, or "RAW" for literal values'
 371 |         });
 372 |         
 373 |         expect(context.autofix.options).toMatchObject({
 374 |           valueInputMode: 'USER_ENTERED'
 375 |         });
 376 |       });
 377 |     });
 378 | 
 379 |     describe('read operation', () => {
 380 |       beforeEach(() => {
 381 |         context.config = {
 382 |           operation: 'read',
 383 |           sheetId: '1234567890'
 384 |         };
 385 |       });
 386 | 
 387 |       it('should require range for read', () => {
 388 |         NodeSpecificValidators.validateGoogleSheets(context);
 389 |         
 390 |         expect(context.errors).toContainEqual({
 391 |           type: 'missing_required',
 392 |           property: 'range',
 393 |           message: 'Range is required for read operation',
 394 |           fix: 'Specify range like "Sheet1!A:B" or "Sheet1!A1:B10"'
 395 |         });
 396 |       });
 397 | 
 398 |       it('should suggest data structure option', () => {
 399 |         context.config.range = 'Sheet1!A:B';
 400 |         
 401 |         NodeSpecificValidators.validateGoogleSheets(context);
 402 |         
 403 |         expect(context.suggestions).toContain('Consider setting options.dataStructure to "object" for easier data manipulation');
 404 |       });
 405 |     });
 406 | 
 407 |     describe('update operation', () => {
 408 |       beforeEach(() => {
 409 |         context.config = {
 410 |           operation: 'update',
 411 |           sheetId: '1234567890'
 412 |         };
 413 |       });
 414 | 
 415 |       it('should require range for update', () => {
 416 |         NodeSpecificValidators.validateGoogleSheets(context);
 417 |         
 418 |         expect(context.errors).toContainEqual({
 419 |           type: 'missing_required',
 420 |           property: 'range',
 421 |           message: 'Range is required for update operation',
 422 |           fix: 'Specify the exact range to update like "Sheet1!A1:B10"'
 423 |         });
 424 |       });
 425 | 
 426 |       it('should require values for update', () => {
 427 |         context.config.range = 'Sheet1!A1:B10';
 428 |         
 429 |         NodeSpecificValidators.validateGoogleSheets(context);
 430 |         
 431 |         expect(context.errors).toContainEqual({
 432 |           type: 'missing_required',
 433 |           property: 'values',
 434 |           message: 'Values are required for update operation',
 435 |           fix: 'Provide the data to write to the spreadsheet'
 436 |         });
 437 |       });
 438 | 
 439 |       it('should accept rawData as alternative to values', () => {
 440 |         context.config.range = 'Sheet1!A1:B10';
 441 |         context.config.rawData = [[1, 2], [3, 4]];
 442 |         
 443 |         NodeSpecificValidators.validateGoogleSheets(context);
 444 |         
 445 |         const valuesErrors = context.errors.filter(e => e.property === 'values');
 446 |         expect(valuesErrors).toHaveLength(0);
 447 |       });
 448 |     });
 449 | 
 450 |     describe('delete operation', () => {
 451 |       beforeEach(() => {
 452 |         context.config = {
 453 |           operation: 'delete',
 454 |           sheetId: '1234567890'
 455 |         };
 456 |       });
 457 | 
 458 |       it('should require toDelete specification', () => {
 459 |         NodeSpecificValidators.validateGoogleSheets(context);
 460 |         
 461 |         expect(context.errors).toContainEqual({
 462 |           type: 'missing_required',
 463 |           property: 'toDelete',
 464 |           message: 'Specify what to delete (rows or columns)',
 465 |           fix: 'Set toDelete to "rows" or "columns"'
 466 |         });
 467 |       });
 468 | 
 469 |       it('should require startIndex for row deletion', () => {
 470 |         context.config.toDelete = 'rows';
 471 |         
 472 |         NodeSpecificValidators.validateGoogleSheets(context);
 473 |         
 474 |         expect(context.errors).toContainEqual({
 475 |           type: 'missing_required',
 476 |           property: 'startIndex',
 477 |           message: 'Start index is required when deleting rows',
 478 |           fix: 'Specify the starting row index (0-based)'
 479 |         });
 480 |       });
 481 | 
 482 |       it('should accept startIndex of 0', () => {
 483 |         context.config.toDelete = 'rows';
 484 |         context.config.startIndex = 0;
 485 |         
 486 |         NodeSpecificValidators.validateGoogleSheets(context);
 487 |         
 488 |         const startIndexErrors = context.errors.filter(e => e.property === 'startIndex');
 489 |         expect(startIndexErrors).toHaveLength(0);
 490 |       });
 491 | 
 492 |       it('should warn about permanent deletion', () => {
 493 |         context.config.toDelete = 'rows';
 494 |         context.config.startIndex = 0;
 495 |         
 496 |         NodeSpecificValidators.validateGoogleSheets(context);
 497 |         
 498 |         expect(context.warnings).toContainEqual({
 499 |           type: 'security',
 500 |           message: 'Deletion is permanent. Consider backing up data first',
 501 |           suggestion: 'Read the data before deletion to create a backup'
 502 |         });
 503 |       });
 504 |     });
 505 | 
 506 |     describe('range validation', () => {
 507 |       beforeEach(() => {
 508 |         context.config = {
 509 |           operation: 'read',
 510 |           sheetId: '1234567890'
 511 |         };
 512 |       });
 513 | 
 514 |       it('should suggest including sheet name in range', () => {
 515 |         context.config.range = 'A1:B10';
 516 |         
 517 |         NodeSpecificValidators.validateGoogleSheets(context);
 518 |         
 519 |         expect(context.warnings).toContainEqual({
 520 |           type: 'inefficient',
 521 |           property: 'range',
 522 |           message: 'Range should include sheet name for clarity',
 523 |           suggestion: 'Format: "SheetName!A1:B10" or "SheetName!A:B"'
 524 |         });
 525 |       });
 526 | 
 527 |       it('should validate sheet names with spaces', () => {
 528 |         context.config.range = 'Sheet Name!A1:B10';
 529 |         
 530 |         NodeSpecificValidators.validateGoogleSheets(context);
 531 |         
 532 |         expect(context.errors).toContainEqual({
 533 |           type: 'invalid_value',
 534 |           property: 'range',
 535 |           message: 'Sheet names with spaces must be quoted',
 536 |           fix: 'Use single quotes around sheet name: \'Sheet Name\'!A1:B10'
 537 |         });
 538 |       });
 539 | 
 540 |       it('should accept quoted sheet names with spaces', () => {
 541 |         context.config.range = "'Sheet Name'!A1:B10";
 542 |         
 543 |         NodeSpecificValidators.validateGoogleSheets(context);
 544 |         
 545 |         const rangeErrors = context.errors.filter(e => e.property === 'range' && e.message.includes('quoted'));
 546 |         expect(rangeErrors).toHaveLength(0);
 547 |       });
 548 | 
 549 |       it('should validate A1 notation format', () => {
 550 |         // Use an invalid range that doesn't match the A1 pattern
 551 |         context.config.range = 'Sheet1!123ABC';
 552 |         
 553 |         NodeSpecificValidators.validateGoogleSheets(context);
 554 |         
 555 |         expect(context.warnings).toContainEqual({
 556 |           type: 'inefficient',
 557 |           property: 'range',
 558 |           message: 'Range may not be in valid A1 notation',
 559 |           suggestion: 'Examples: "Sheet1!A1:B10", "Sheet1!A:B", "Sheet1!1:10"'
 560 |         });
 561 |       });
 562 |     });
 563 |   });
 564 | 
 565 |   describe('validateOpenAI', () => {
 566 |     describe('chat create operation', () => {
 567 |       beforeEach(() => {
 568 |         context.config = {
 569 |           resource: 'chat',
 570 |           operation: 'create'
 571 |         };
 572 |       });
 573 | 
 574 |       it('should require model selection', () => {
 575 |         NodeSpecificValidators.validateOpenAI(context);
 576 |         
 577 |         expect(context.errors).toContainEqual({
 578 |           type: 'missing_required',
 579 |           property: 'model',
 580 |           message: 'Model selection is required',
 581 |           fix: 'Choose a model like "gpt-4", "gpt-3.5-turbo", etc.'
 582 |         });
 583 |       });
 584 | 
 585 |       it('should warn about deprecated models', () => {
 586 |         context.config.model = 'text-davinci-003';
 587 |         context.config.messages = [{ role: 'user', content: 'Hello' }];
 588 |         
 589 |         NodeSpecificValidators.validateOpenAI(context);
 590 |         
 591 |         expect(context.warnings).toContainEqual({
 592 |           type: 'deprecated',
 593 |           property: 'model',
 594 |           message: 'Model text-davinci-003 is deprecated',
 595 |           suggestion: 'Use "gpt-3.5-turbo" or "gpt-4" instead'
 596 |         });
 597 |       });
 598 | 
 599 |       it('should require messages or prompt', () => {
 600 |         context.config.model = 'gpt-4';
 601 |         
 602 |         NodeSpecificValidators.validateOpenAI(context);
 603 |         
 604 |         expect(context.errors).toContainEqual({
 605 |           type: 'missing_required',
 606 |           property: 'messages',
 607 |           message: 'Messages or prompt required for chat completion',
 608 |           fix: 'Add messages array or use the prompt field'
 609 |         });
 610 |       });
 611 | 
 612 |       it('should accept prompt as alternative to messages', () => {
 613 |         context.config.model = 'gpt-4';
 614 |         context.config.prompt = 'Hello AI';
 615 |         
 616 |         NodeSpecificValidators.validateOpenAI(context);
 617 |         
 618 |         const messageErrors = context.errors.filter(e => e.property === 'messages');
 619 |         expect(messageErrors).toHaveLength(0);
 620 |       });
 621 | 
 622 |       it('should warn about high token limits', () => {
 623 |         context.config.model = 'gpt-4';
 624 |         context.config.messages = [{ role: 'user', content: 'Hello' }];
 625 |         context.config.maxTokens = 5000;
 626 |         
 627 |         NodeSpecificValidators.validateOpenAI(context);
 628 |         
 629 |         expect(context.warnings).toContainEqual({
 630 |           type: 'inefficient',
 631 |           property: 'maxTokens',
 632 |           message: 'High token limit may increase costs significantly',
 633 |           suggestion: 'Consider if you really need more than 4000 tokens'
 634 |         });
 635 |       });
 636 | 
 637 |       it('should validate temperature range', () => {
 638 |         context.config.model = 'gpt-4';
 639 |         context.config.messages = [{ role: 'user', content: 'Hello' }];
 640 |         context.config.temperature = 2.5;
 641 |         
 642 |         NodeSpecificValidators.validateOpenAI(context);
 643 |         
 644 |         expect(context.errors).toContainEqual({
 645 |           type: 'invalid_value',
 646 |           property: 'temperature',
 647 |           message: 'Temperature must be between 0 and 2',
 648 |           fix: 'Set temperature between 0 (deterministic) and 2 (creative)'
 649 |         });
 650 |       });
 651 |     });
 652 | 
 653 |     describe('error handling', () => {
 654 |       it('should suggest error handling for AI API calls', () => {
 655 |         context.config = {
 656 |           resource: 'chat',
 657 |           operation: 'create',
 658 |           model: 'gpt-4',
 659 |           messages: [{ role: 'user', content: 'Hello' }]
 660 |         };
 661 |         
 662 |         NodeSpecificValidators.validateOpenAI(context);
 663 |         
 664 |         expect(context.warnings).toContainEqual({
 665 |           type: 'best_practice',
 666 |           property: 'errorHandling',
 667 |           message: 'AI APIs have rate limits and can return errors',
 668 |           suggestion: 'Add onError: "continueRegularOutput" with retryOnFail and longer wait times'
 669 |         });
 670 |         
 671 |         expect(context.autofix).toMatchObject({
 672 |           onError: 'continueRegularOutput',
 673 |           retryOnFail: true,
 674 |           maxTries: 3,
 675 |           waitBetweenTries: 5000,
 676 |           alwaysOutputData: true
 677 |         });
 678 |       });
 679 | 
 680 |       it('should warn about deprecated continueOnFail', () => {
 681 |         context.config = {
 682 |           resource: 'chat',
 683 |           operation: 'create',
 684 |           model: 'gpt-4',
 685 |           messages: [{ role: 'user', content: 'Hello' }],
 686 |           continueOnFail: true
 687 |         };
 688 |         
 689 |         NodeSpecificValidators.validateOpenAI(context);
 690 |         
 691 |         expect(context.warnings).toContainEqual({
 692 |           type: 'deprecated',
 693 |           property: 'continueOnFail',
 694 |           message: 'continueOnFail is deprecated. Use onError instead',
 695 |           suggestion: 'Replace with onError: "continueRegularOutput"'
 696 |         });
 697 |       });
 698 |     });
 699 |   });
 700 | 
 701 |   describe('validateMongoDB', () => {
 702 |     describe('common validations', () => {
 703 |       it('should require collection name', () => {
 704 |         context.config = {
 705 |           operation: 'find'
 706 |         };
 707 |         
 708 |         NodeSpecificValidators.validateMongoDB(context);
 709 |         
 710 |         expect(context.errors).toContainEqual({
 711 |           type: 'missing_required',
 712 |           property: 'collection',
 713 |           message: 'Collection name is required',
 714 |           fix: 'Specify the MongoDB collection to work with'
 715 |         });
 716 |       });
 717 |     });
 718 | 
 719 |     describe('find operation', () => {
 720 |       beforeEach(() => {
 721 |         context.config = {
 722 |           operation: 'find',
 723 |           collection: 'users'
 724 |         };
 725 |       });
 726 | 
 727 |       it('should validate query JSON', () => {
 728 |         context.config.query = '{ invalid json';
 729 |         
 730 |         NodeSpecificValidators.validateMongoDB(context);
 731 |         
 732 |         expect(context.errors).toContainEqual({
 733 |           type: 'invalid_value',
 734 |           property: 'query',
 735 |           message: 'Query must be valid JSON',
 736 |           fix: 'Ensure query is valid JSON like: {"name": "John"}'
 737 |         });
 738 |       });
 739 | 
 740 |       it('should accept valid JSON query', () => {
 741 |         context.config.query = '{"name": "John"}';
 742 |         
 743 |         NodeSpecificValidators.validateMongoDB(context);
 744 |         
 745 |         const queryErrors = context.errors.filter(e => e.property === 'query');
 746 |         expect(queryErrors).toHaveLength(0);
 747 |       });
 748 |     });
 749 | 
 750 |     describe('insert operation', () => {
 751 |       beforeEach(() => {
 752 |         context.config = {
 753 |           operation: 'insert',
 754 |           collection: 'users'
 755 |         };
 756 |       });
 757 | 
 758 |       it('should require document data', () => {
 759 |         NodeSpecificValidators.validateMongoDB(context);
 760 |         
 761 |         expect(context.errors).toContainEqual({
 762 |           type: 'missing_required',
 763 |           property: 'fields',
 764 |           message: 'Document data is required for insert',
 765 |           fix: 'Provide the data to insert'
 766 |         });
 767 |       });
 768 | 
 769 |       it('should accept documents as alternative to fields', () => {
 770 |         context.config.documents = [{ name: 'John' }];
 771 |         
 772 |         NodeSpecificValidators.validateMongoDB(context);
 773 |         
 774 |         const fieldsErrors = context.errors.filter(e => e.property === 'fields');
 775 |         expect(fieldsErrors).toHaveLength(0);
 776 |       });
 777 |     });
 778 | 
 779 |     describe('update operation', () => {
 780 |       beforeEach(() => {
 781 |         context.config = {
 782 |           operation: 'update',
 783 |           collection: 'users'
 784 |         };
 785 |       });
 786 | 
 787 |       it('should warn about update without query', () => {
 788 |         NodeSpecificValidators.validateMongoDB(context);
 789 |         
 790 |         expect(context.warnings).toContainEqual({
 791 |           type: 'security',
 792 |           message: 'Update without query will affect all documents',
 793 |           suggestion: 'Add a query to target specific documents'
 794 |         });
 795 |       });
 796 |     });
 797 | 
 798 |     describe('delete operation', () => {
 799 |       beforeEach(() => {
 800 |         context.config = {
 801 |           operation: 'delete',
 802 |           collection: 'users'
 803 |         };
 804 |       });
 805 | 
 806 |       it('should error on delete without query', () => {
 807 |         NodeSpecificValidators.validateMongoDB(context);
 808 |         
 809 |         expect(context.errors).toContainEqual({
 810 |           type: 'invalid_value',
 811 |           property: 'query',
 812 |           message: 'Delete without query would remove all documents - this is a critical security issue',
 813 |           fix: 'Add a query to specify which documents to delete'
 814 |         });
 815 |       });
 816 | 
 817 |       it('should error on delete with empty query', () => {
 818 |         context.config.query = '{}';
 819 |         
 820 |         NodeSpecificValidators.validateMongoDB(context);
 821 |         
 822 |         expect(context.errors).toContainEqual({
 823 |           type: 'invalid_value',
 824 |           property: 'query',
 825 |           message: 'Delete without query would remove all documents - this is a critical security issue',
 826 |           fix: 'Add a query to specify which documents to delete'
 827 |         });
 828 |       });
 829 |     });
 830 | 
 831 |     describe('error handling', () => {
 832 |       it('should suggest error handling for find operations', () => {
 833 |         context.config = {
 834 |           operation: 'find',
 835 |           collection: 'users'
 836 |         };
 837 |         
 838 |         NodeSpecificValidators.validateMongoDB(context);
 839 |         
 840 |         expect(context.warnings).toContainEqual({
 841 |           type: 'best_practice',
 842 |           property: 'errorHandling',
 843 |           message: 'MongoDB queries can fail due to connection issues',
 844 |           suggestion: 'Add onError: "continueRegularOutput" with retryOnFail'
 845 |         });
 846 |         
 847 |         expect(context.autofix).toMatchObject({
 848 |           onError: 'continueRegularOutput',
 849 |           retryOnFail: true,
 850 |           maxTries: 3
 851 |         });
 852 |       });
 853 | 
 854 |       it('should suggest different error handling for write operations', () => {
 855 |         context.config = {
 856 |           operation: 'insert',
 857 |           collection: 'users',
 858 |           fields: { name: 'John' }
 859 |         };
 860 |         
 861 |         NodeSpecificValidators.validateMongoDB(context);
 862 |         
 863 |         expect(context.warnings).toContainEqual({
 864 |           type: 'best_practice',
 865 |           property: 'errorHandling',
 866 |           message: 'MongoDB write operations should handle errors carefully',
 867 |           suggestion: 'Add onError: "continueErrorOutput" to handle write failures separately'
 868 |         });
 869 |         
 870 |         expect(context.autofix).toMatchObject({
 871 |           onError: 'continueErrorOutput',
 872 |           retryOnFail: true,
 873 |           maxTries: 2,
 874 |           waitBetweenTries: 1000
 875 |         });
 876 |       });
 877 | 
 878 |       it('should warn about deprecated continueOnFail', () => {
 879 |         context.config = {
 880 |           operation: 'find',
 881 |           collection: 'users',
 882 |           continueOnFail: true
 883 |         };
 884 |         
 885 |         NodeSpecificValidators.validateMongoDB(context);
 886 |         
 887 |         expect(context.warnings).toContainEqual({
 888 |           type: 'deprecated',
 889 |           property: 'continueOnFail',
 890 |           message: 'continueOnFail is deprecated. Use onError instead',
 891 |           suggestion: 'Replace with onError: "continueRegularOutput" or "continueErrorOutput"'
 892 |         });
 893 |       });
 894 |     });
 895 |   });
 896 | 
 897 |   describe('validatePostgres', () => {
 898 |     describe('insert operation', () => {
 899 |       beforeEach(() => {
 900 |         context.config = {
 901 |           operation: 'insert'
 902 |         };
 903 |       });
 904 | 
 905 |       it('should require table name', () => {
 906 |         NodeSpecificValidators.validatePostgres(context);
 907 |         
 908 |         expect(context.errors).toContainEqual({
 909 |           type: 'missing_required',
 910 |           property: 'table',
 911 |           message: 'Table name is required for insert operation',
 912 |           fix: 'Specify the table to insert data into'
 913 |         });
 914 |       });
 915 | 
 916 |       it('should warn about missing columns', () => {
 917 |         context.config.table = 'users';
 918 |         
 919 |         NodeSpecificValidators.validatePostgres(context);
 920 |         
 921 |         expect(context.warnings).toContainEqual({
 922 |           type: 'missing_common',
 923 |           property: 'columns',
 924 |           message: 'No columns specified for insert',
 925 |           suggestion: 'Define which columns to insert data into'
 926 |         });
 927 |       });
 928 | 
 929 |       it('should not warn if dataMode is set', () => {
 930 |         context.config.table = 'users';
 931 |         context.config.dataMode = 'autoMapInputData';
 932 |         
 933 |         NodeSpecificValidators.validatePostgres(context);
 934 |         
 935 |         const columnWarnings = context.warnings.filter(w => w.property === 'columns');
 936 |         expect(columnWarnings).toHaveLength(0);
 937 |       });
 938 |     });
 939 | 
 940 |     describe('update operation', () => {
 941 |       beforeEach(() => {
 942 |         context.config = {
 943 |           operation: 'update'
 944 |         };
 945 |       });
 946 | 
 947 |       it('should require table name', () => {
 948 |         NodeSpecificValidators.validatePostgres(context);
 949 |         
 950 |         expect(context.errors).toContainEqual({
 951 |           type: 'missing_required',
 952 |           property: 'table',
 953 |           message: 'Table name is required for update operation',
 954 |           fix: 'Specify the table to update'
 955 |         });
 956 |       });
 957 | 
 958 |       it('should warn about missing updateKey', () => {
 959 |         context.config.table = 'users';
 960 |         
 961 |         NodeSpecificValidators.validatePostgres(context);
 962 |         
 963 |         expect(context.warnings).toContainEqual({
 964 |           type: 'missing_common',
 965 |           property: 'updateKey',
 966 |           message: 'No update key specified',
 967 |           suggestion: 'Set updateKey to identify which rows to update (e.g., "id")'
 968 |         });
 969 |       });
 970 |     });
 971 | 
 972 |     describe('delete operation', () => {
 973 |       beforeEach(() => {
 974 |         context.config = {
 975 |           operation: 'delete'
 976 |         };
 977 |       });
 978 | 
 979 |       it('should require table name', () => {
 980 |         NodeSpecificValidators.validatePostgres(context);
 981 |         
 982 |         expect(context.errors).toContainEqual({
 983 |           type: 'missing_required',
 984 |           property: 'table',
 985 |           message: 'Table name is required for delete operation',
 986 |           fix: 'Specify the table to delete from'
 987 |         });
 988 |       });
 989 | 
 990 |       it('should require deleteKey', () => {
 991 |         context.config.table = 'users';
 992 |         
 993 |         NodeSpecificValidators.validatePostgres(context);
 994 |         
 995 |         expect(context.errors).toContainEqual({
 996 |           type: 'missing_required',
 997 |           property: 'deleteKey',
 998 |           message: 'Delete key is required to identify rows',
 999 |           fix: 'Set deleteKey (e.g., "id") to specify which rows to delete'
1000 |         });
1001 |       });
1002 |     });
1003 | 
1004 |     describe('execute operation', () => {
1005 |       beforeEach(() => {
1006 |         context.config = {
1007 |           operation: 'execute'
1008 |         };
1009 |       });
1010 | 
1011 |       it('should require SQL query', () => {
1012 |         NodeSpecificValidators.validatePostgres(context);
1013 |         
1014 |         expect(context.errors).toContainEqual({
1015 |           type: 'missing_required',
1016 |           property: 'query',
1017 |           message: 'SQL query is required',
1018 |           fix: 'Provide the SQL query to execute'
1019 |         });
1020 |       });
1021 |     });
1022 | 
1023 |     describe('SQL query validation', () => {
1024 |       beforeEach(() => {
1025 |         context.config = {
1026 |           operation: 'execute'
1027 |         };
1028 |       });
1029 | 
1030 |       it('should warn about SQL injection risks', () => {
1031 |         context.config.query = 'SELECT * FROM users WHERE id = ${userId}';
1032 |         
1033 |         NodeSpecificValidators.validatePostgres(context);
1034 |         
1035 |         expect(context.warnings).toContainEqual({
1036 |           type: 'security',
1037 |           message: 'Query contains template expressions that might be vulnerable to SQL injection',
1038 |           suggestion: 'Use parameterized queries with query parameters instead of string interpolation'
1039 |         });
1040 |       });
1041 | 
1042 |       it('should error on DELETE without WHERE', () => {
1043 |         context.config.query = 'DELETE FROM users';
1044 |         
1045 |         NodeSpecificValidators.validatePostgres(context);
1046 |         
1047 |         expect(context.errors).toContainEqual({
1048 |           type: 'invalid_value',
1049 |           property: 'query',
1050 |           message: 'DELETE query without WHERE clause will delete all records',
1051 |           fix: 'Add a WHERE clause to specify which records to delete'
1052 |         });
1053 |       });
1054 | 
1055 |       it('should warn on UPDATE without WHERE', () => {
1056 |         context.config.query = 'UPDATE users SET active = true';
1057 |         
1058 |         NodeSpecificValidators.validatePostgres(context);
1059 |         
1060 |         expect(context.warnings).toContainEqual({
1061 |           type: 'security',
1062 |           message: 'UPDATE query without WHERE clause will update all records',
1063 |           suggestion: 'Add a WHERE clause to specify which records to update'
1064 |         });
1065 |       });
1066 | 
1067 |       it('should warn about TRUNCATE', () => {
1068 |         context.config.query = 'TRUNCATE TABLE users';
1069 |         
1070 |         NodeSpecificValidators.validatePostgres(context);
1071 |         
1072 |         expect(context.warnings).toContainEqual({
1073 |           type: 'security',
1074 |           message: 'TRUNCATE will remove all data from the table',
1075 |           suggestion: 'Consider using DELETE with WHERE clause if you need to keep some data'
1076 |         });
1077 |       });
1078 | 
1079 |       it('should error on DROP operations', () => {
1080 |         context.config.query = 'DROP TABLE users';
1081 |         
1082 |         NodeSpecificValidators.validatePostgres(context);
1083 |         
1084 |         expect(context.errors).toContainEqual({
1085 |           type: 'invalid_value',
1086 |           property: 'query',
1087 |           message: 'DROP operations are extremely dangerous and will permanently delete database objects',
1088 |           fix: 'Use this only if you really intend to delete tables/databases permanently'
1089 |         });
1090 |       });
1091 | 
1092 |       it('should suggest specific columns instead of SELECT *', () => {
1093 |         context.config.query = 'SELECT * FROM users';
1094 |         
1095 |         NodeSpecificValidators.validatePostgres(context);
1096 |         
1097 |         expect(context.suggestions).toContain('Consider selecting specific columns instead of * for better performance');
1098 |       });
1099 | 
1100 |       it('should suggest PostgreSQL-specific dollar quotes', () => {
1101 |         context.config.query = 'CREATE FUNCTION test() RETURNS void AS $$ BEGIN END; $$ LANGUAGE plpgsql';
1102 |         
1103 |         NodeSpecificValidators.validatePostgres(context);
1104 |         
1105 |         expect(context.suggestions).toContain('Dollar-quoted strings detected - ensure they are properly closed');
1106 |       });
1107 |     });
1108 | 
1109 |     describe('connection and error handling', () => {
1110 |       it('should suggest connection timeout', () => {
1111 |         context.config = {
1112 |           operation: 'execute',
1113 |           query: 'SELECT * FROM users'
1114 |         };
1115 |         
1116 |         NodeSpecificValidators.validatePostgres(context);
1117 |         
1118 |         expect(context.suggestions).toContain('Consider setting connectionTimeout to handle slow connections');
1119 |       });
1120 | 
1121 |       it('should suggest error handling for read operations', () => {
1122 |         context.config = {
1123 |           operation: 'execute',
1124 |           query: 'SELECT * FROM users'
1125 |         };
1126 |         
1127 |         NodeSpecificValidators.validatePostgres(context);
1128 |         
1129 |         expect(context.warnings).toContainEqual({
1130 |           type: 'best_practice',
1131 |           property: 'errorHandling',
1132 |           message: 'Database reads can fail due to connection issues',
1133 |           suggestion: 'Add onError: "continueRegularOutput" and retryOnFail: true'
1134 |         });
1135 |         
1136 |         expect(context.autofix).toMatchObject({
1137 |           onError: 'continueRegularOutput',
1138 |           retryOnFail: true,
1139 |           maxTries: 3
1140 |         });
1141 |       });
1142 | 
1143 |       it('should suggest different error handling for write operations', () => {
1144 |         context.config = {
1145 |           operation: 'insert',
1146 |           table: 'users'
1147 |         };
1148 |         
1149 |         NodeSpecificValidators.validatePostgres(context);
1150 |         
1151 |         expect(context.warnings).toContainEqual({
1152 |           type: 'best_practice',
1153 |           property: 'errorHandling',
1154 |           message: 'Database writes should handle errors carefully',
1155 |           suggestion: 'Add onError: "stopWorkflow" with retryOnFail for transient failures'
1156 |         });
1157 |         
1158 |         expect(context.autofix).toMatchObject({
1159 |           onError: 'stopWorkflow',
1160 |           retryOnFail: true,
1161 |           maxTries: 2,
1162 |           waitBetweenTries: 2000
1163 |         });
1164 |       });
1165 | 
1166 |       it('should warn about deprecated continueOnFail', () => {
1167 |         context.config = {
1168 |           operation: 'execute',
1169 |           query: 'SELECT * FROM users',
1170 |           continueOnFail: true
1171 |         };
1172 |         
1173 |         NodeSpecificValidators.validatePostgres(context);
1174 |         
1175 |         expect(context.warnings).toContainEqual({
1176 |           type: 'deprecated',
1177 |           property: 'continueOnFail',
1178 |           message: 'continueOnFail is deprecated. Use onError instead',
1179 |           suggestion: 'Replace with onError: "continueRegularOutput" or "stopWorkflow"'
1180 |         });
1181 |       });
1182 |     });
1183 |   });
1184 | 
1185 |   describe('validateMySQL', () => {
1186 |     describe('operations', () => {
1187 |       it('should validate insert operation', () => {
1188 |         context.config = {
1189 |           operation: 'insert'
1190 |         };
1191 |         
1192 |         NodeSpecificValidators.validateMySQL(context);
1193 |         
1194 |         expect(context.errors).toContainEqual({
1195 |           type: 'missing_required',
1196 |           property: 'table',
1197 |           message: 'Table name is required for insert operation',
1198 |           fix: 'Specify the table to insert data into'
1199 |         });
1200 |       });
1201 | 
1202 |       it('should validate update operation', () => {
1203 |         context.config = {
1204 |           operation: 'update'
1205 |         };
1206 |         
1207 |         NodeSpecificValidators.validateMySQL(context);
1208 |         
1209 |         expect(context.errors).toContainEqual({
1210 |           type: 'missing_required',
1211 |           property: 'table',
1212 |           message: 'Table name is required for update operation',
1213 |           fix: 'Specify the table to update'
1214 |         });
1215 |       });
1216 | 
1217 |       it('should validate delete operation', () => {
1218 |         context.config = {
1219 |           operation: 'delete'
1220 |         };
1221 |         
1222 |         NodeSpecificValidators.validateMySQL(context);
1223 |         
1224 |         expect(context.errors).toContainEqual({
1225 |           type: 'missing_required',
1226 |           property: 'table',
1227 |           message: 'Table name is required for delete operation',
1228 |           fix: 'Specify the table to delete from'
1229 |         });
1230 |       });
1231 | 
1232 |       it('should validate execute operation', () => {
1233 |         context.config = {
1234 |           operation: 'execute'
1235 |         };
1236 |         
1237 |         NodeSpecificValidators.validateMySQL(context);
1238 |         
1239 |         expect(context.errors).toContainEqual({
1240 |           type: 'missing_required',
1241 |           property: 'query',
1242 |           message: 'SQL query is required',
1243 |           fix: 'Provide the SQL query to execute'
1244 |         });
1245 |       });
1246 |     });
1247 | 
1248 |     describe('MySQL-specific features', () => {
1249 |       it('should suggest timezone configuration', () => {
1250 |         context.config = {
1251 |           operation: 'execute',
1252 |           query: 'SELECT NOW()'
1253 |         };
1254 |         
1255 |         NodeSpecificValidators.validateMySQL(context);
1256 |         
1257 |         expect(context.suggestions).toContain('Consider setting timezone to ensure consistent date/time handling');
1258 |       });
1259 | 
1260 |       it('should check for MySQL backticks', () => {
1261 |         context.config = {
1262 |           operation: 'execute',
1263 |           query: 'SELECT `name` FROM `users`'
1264 |         };
1265 |         
1266 |         NodeSpecificValidators.validateMySQL(context);
1267 |         
1268 |         expect(context.suggestions).toContain('Using backticks for identifiers - ensure they are properly paired');
1269 |       });
1270 |     });
1271 | 
1272 |     describe('error handling', () => {
1273 |       it('should suggest error handling for queries', () => {
1274 |         context.config = {
1275 |           operation: 'execute',
1276 |           query: 'SELECT * FROM users'
1277 |         };
1278 |         
1279 |         NodeSpecificValidators.validateMySQL(context);
1280 |         
1281 |         expect(context.warnings).toContainEqual({
1282 |           type: 'best_practice',
1283 |           property: 'errorHandling',
1284 |           message: 'Database queries can fail due to connection issues',
1285 |           suggestion: 'Add onError: "continueRegularOutput" and retryOnFail: true'
1286 |         });
1287 |       });
1288 | 
1289 |       it('should suggest error handling for modifications', () => {
1290 |         context.config = {
1291 |           operation: 'update',
1292 |           table: 'users',
1293 |           updateKey: 'id'
1294 |         };
1295 |         
1296 |         NodeSpecificValidators.validateMySQL(context);
1297 |         
1298 |         expect(context.warnings).toContainEqual({
1299 |           type: 'best_practice',
1300 |           property: 'errorHandling',
1301 |           message: 'Database modifications should handle errors carefully',
1302 |           suggestion: 'Add onError: "stopWorkflow" with retryOnFail for transient failures'
1303 |         });
1304 |       });
1305 |     });
1306 |   });
1307 | 
1308 |   describe('validateHttpRequest', () => {
1309 |     describe('URL validation', () => {
1310 |       it('should require URL', () => {
1311 |         context.config = {
1312 |           method: 'GET'
1313 |         };
1314 |         
1315 |         NodeSpecificValidators.validateHttpRequest(context);
1316 |         
1317 |         expect(context.errors).toContainEqual({
1318 |           type: 'missing_required',
1319 |           property: 'url',
1320 |           message: 'URL is required for HTTP requests',
1321 |           fix: 'Provide the full URL including protocol (https://...)'
1322 |         });
1323 |       });
1324 | 
1325 |       it('should warn about missing protocol', () => {
1326 |         context.config = {
1327 |           method: 'GET',
1328 |           url: 'example.com/api'
1329 |         };
1330 |         
1331 |         NodeSpecificValidators.validateHttpRequest(context);
1332 |         
1333 |         expect(context.warnings).toContainEqual({
1334 |           type: 'invalid_value',
1335 |           property: 'url',
1336 |           message: 'URL should start with http:// or https://',
1337 |           suggestion: 'Use https:// for secure connections'
1338 |         });
1339 |       });
1340 | 
1341 |       it('should accept URLs with expressions', () => {
1342 |         context.config = {
1343 |           method: 'GET',
1344 |           url: '{{$node.Config.json.apiUrl}}/users'
1345 |         };
1346 |         
1347 |         NodeSpecificValidators.validateHttpRequest(context);
1348 |         
1349 |         const urlWarnings = context.warnings.filter(w => w.property === 'url');
1350 |         expect(urlWarnings).toHaveLength(0);
1351 |       });
1352 |     });
1353 | 
1354 |     describe('method-specific validation', () => {
1355 |       it('should suggest body for POST requests', () => {
1356 |         context.config = {
1357 |           method: 'POST',
1358 |           url: 'https://api.example.com/users'
1359 |         };
1360 |         
1361 |         NodeSpecificValidators.validateHttpRequest(context);
1362 |         
1363 |         expect(context.warnings).toContainEqual({
1364 |           type: 'missing_common',
1365 |           property: 'sendBody',
1366 |           message: 'POST requests typically include a body',
1367 |           suggestion: 'Set sendBody: true and configure the body content'
1368 |         });
1369 |       });
1370 | 
1371 |       it('should suggest body for PUT requests', () => {
1372 |         context.config = {
1373 |           method: 'PUT',
1374 |           url: 'https://api.example.com/users/1'
1375 |         };
1376 |         
1377 |         NodeSpecificValidators.validateHttpRequest(context);
1378 |         
1379 |         expect(context.warnings).toContainEqual({
1380 |           type: 'missing_common',
1381 |           property: 'sendBody',
1382 |           message: 'PUT requests typically include a body',
1383 |           suggestion: 'Set sendBody: true and configure the body content'
1384 |         });
1385 |       });
1386 | 
1387 |       it('should suggest body for PATCH requests', () => {
1388 |         context.config = {
1389 |           method: 'PATCH',
1390 |           url: 'https://api.example.com/users/1'
1391 |         };
1392 |         
1393 |         NodeSpecificValidators.validateHttpRequest(context);
1394 |         
1395 |         expect(context.warnings).toContainEqual({
1396 |           type: 'missing_common',
1397 |           property: 'sendBody',
1398 |           message: 'PATCH requests typically include a body',
1399 |           suggestion: 'Set sendBody: true and configure the body content'
1400 |         });
1401 |       });
1402 |     });
1403 | 
1404 |     describe('error handling', () => {
1405 |       it('should suggest error handling for HTTP requests', () => {
1406 |         context.config = {
1407 |           method: 'GET',
1408 |           url: 'https://api.example.com/data'
1409 |         };
1410 |         
1411 |         NodeSpecificValidators.validateHttpRequest(context);
1412 |         
1413 |         expect(context.warnings).toContainEqual({
1414 |           type: 'best_practice',
1415 |           property: 'errorHandling',
1416 |           message: 'HTTP requests can fail due to network issues or server errors',
1417 |           suggestion: 'Add onError: "continueRegularOutput" and retryOnFail: true for resilience'
1418 |         });
1419 |         
1420 |         expect(context.autofix).toMatchObject({
1421 |           onError: 'continueRegularOutput',
1422 |           retryOnFail: true,
1423 |           maxTries: 3,
1424 |           waitBetweenTries: 1000
1425 |         });
1426 |       });
1427 | 
1428 |       it('should handle deprecated continueOnFail', () => {
1429 |         context.config = {
1430 |           method: 'GET',
1431 |           url: 'https://api.example.com/data',
1432 |           continueOnFail: true
1433 |         };
1434 |         
1435 |         NodeSpecificValidators.validateHttpRequest(context);
1436 |         
1437 |         expect(context.warnings).toContainEqual({
1438 |           type: 'deprecated',
1439 |           property: 'continueOnFail',
1440 |           message: 'continueOnFail is deprecated. Use onError instead',
1441 |           suggestion: 'Replace with onError: "continueRegularOutput"'
1442 |         });
1443 |         
1444 |         expect(context.autofix.onError).toBe('continueRegularOutput');
1445 |         expect(context.autofix.continueOnFail).toBeUndefined();
1446 |       });
1447 | 
1448 |       it('should handle continueOnFail false', () => {
1449 |         context.config = {
1450 |           method: 'GET',
1451 |           url: 'https://api.example.com/data',
1452 |           continueOnFail: false
1453 |         };
1454 |         
1455 |         NodeSpecificValidators.validateHttpRequest(context);
1456 |         
1457 |         expect(context.autofix.onError).toBe('stopWorkflow');
1458 |       });
1459 |     });
1460 | 
1461 |     describe('retry configuration', () => {
1462 |       it('should warn about retrying non-idempotent operations', () => {
1463 |         context.config = {
1464 |           method: 'POST',
1465 |           url: 'https://api.example.com/orders',
1466 |           retryOnFail: true,
1467 |           maxTries: 5
1468 |         };
1469 |         
1470 |         NodeSpecificValidators.validateHttpRequest(context);
1471 |         
1472 |         expect(context.warnings).toContainEqual({
1473 |           type: 'best_practice',
1474 |           property: 'maxTries',
1475 |           message: 'POST requests might not be idempotent. Use fewer retries.',
1476 |           suggestion: 'Set maxTries: 2 for non-idempotent operations'
1477 |         });
1478 |       });
1479 | 
1480 |       it('should suggest alwaysOutputData for debugging', () => {
1481 |         context.config = {
1482 |           method: 'GET',
1483 |           url: 'https://api.example.com/data',
1484 |           retryOnFail: true
1485 |         };
1486 |         
1487 |         NodeSpecificValidators.validateHttpRequest(context);
1488 |         
1489 |         expect(context.suggestions).toContain('Enable alwaysOutputData to capture error responses for debugging');
1490 |         expect(context.autofix.alwaysOutputData).toBe(true);
1491 |       });
1492 |     });
1493 | 
1494 |     describe('authentication and security', () => {
1495 |       it('should warn about missing authentication for API endpoints', () => {
1496 |         context.config = {
1497 |           method: 'GET',
1498 |           url: 'https://api.example.com/users'
1499 |         };
1500 |         
1501 |         NodeSpecificValidators.validateHttpRequest(context);
1502 |         
1503 |         expect(context.warnings).toContainEqual({
1504 |           type: 'security',
1505 |           property: 'authentication',
1506 |           message: 'API endpoints typically require authentication',
1507 |           suggestion: 'Configure authentication method (Bearer token, API key, etc.)'
1508 |         });
1509 |       });
1510 | 
1511 |       it('should not warn about authentication for non-API URLs', () => {
1512 |         context.config = {
1513 |           method: 'GET',
1514 |           url: 'https://example.com/public-page'
1515 |         };
1516 |         
1517 |         NodeSpecificValidators.validateHttpRequest(context);
1518 |         
1519 |         const authWarnings = context.warnings.filter(w => w.property === 'authentication');
1520 |         expect(authWarnings).toHaveLength(0);
1521 |       });
1522 |     });
1523 | 
1524 |     describe('timeout', () => {
1525 |       it('should suggest timeout configuration', () => {
1526 |         context.config = {
1527 |           method: 'GET',
1528 |           url: 'https://api.example.com/data'
1529 |         };
1530 |         
1531 |         NodeSpecificValidators.validateHttpRequest(context);
1532 |         
1533 |         expect(context.suggestions).toContain('Consider setting a timeout to prevent hanging requests');
1534 |       });
1535 |     });
1536 |   });
1537 | 
1538 |   describe('validateWebhook', () => {
1539 |     describe('path validation', () => {
1540 |       it('should require webhook path', () => {
1541 |         context.config = {
1542 |           httpMethod: 'POST'
1543 |         };
1544 |         
1545 |         NodeSpecificValidators.validateWebhook(context);
1546 |         
1547 |         expect(context.errors).toContainEqual({
1548 |           type: 'missing_required',
1549 |           property: 'path',
1550 |           message: 'Webhook path is required',
1551 |           fix: 'Provide a unique path like "my-webhook" or "github-events"'
1552 |         });
1553 |       });
1554 | 
1555 |       it('should warn about leading slash in path', () => {
1556 |         context.config = {
1557 |           path: '/my-webhook',
1558 |           httpMethod: 'POST'
1559 |         };
1560 |         
1561 |         NodeSpecificValidators.validateWebhook(context);
1562 |         
1563 |         expect(context.warnings).toContainEqual({
1564 |           type: 'invalid_value',
1565 |           property: 'path',
1566 |           message: 'Webhook path should not start with /',
1567 |           suggestion: 'Use "webhook-name" instead of "/webhook-name"'
1568 |         });
1569 |       });
1570 |     });
1571 | 
1572 |     describe('error handling', () => {
1573 |       it('should suggest error handling for webhooks', () => {
1574 |         context.config = {
1575 |           path: 'my-webhook',
1576 |           httpMethod: 'POST'
1577 |         };
1578 |         
1579 |         NodeSpecificValidators.validateWebhook(context);
1580 |         
1581 |         expect(context.warnings).toContainEqual({
1582 |           type: 'best_practice',
1583 |           property: 'onError',
1584 |           message: 'Webhooks should always send a response, even on error',
1585 |           suggestion: 'Set onError: "continueRegularOutput" to ensure webhook responses'
1586 |         });
1587 |         
1588 |         expect(context.autofix.onError).toBe('continueRegularOutput');
1589 |       });
1590 | 
1591 |       it('should handle deprecated continueOnFail', () => {
1592 |         context.config = {
1593 |           path: 'my-webhook',
1594 |           httpMethod: 'POST',
1595 |           continueOnFail: true
1596 |         };
1597 |         
1598 |         NodeSpecificValidators.validateWebhook(context);
1599 |         
1600 |         expect(context.warnings).toContainEqual({
1601 |           type: 'deprecated',
1602 |           property: 'continueOnFail',
1603 |           message: 'continueOnFail is deprecated. Use onError instead',
1604 |           suggestion: 'Replace with onError: "continueRegularOutput"'
1605 |         });
1606 |         
1607 |         expect(context.autofix.onError).toBe('continueRegularOutput');
1608 |         expect(context.autofix.continueOnFail).toBeUndefined();
1609 |       });
1610 |     });
1611 | 
1612 |     describe('response mode validation', () => {
1613 |       it('should error on responseNode without error handling', () => {
1614 |         context.config = {
1615 |           path: 'my-webhook',
1616 |           httpMethod: 'POST',
1617 |           responseMode: 'responseNode'
1618 |         };
1619 |         
1620 |         NodeSpecificValidators.validateWebhook(context);
1621 |         
1622 |         expect(context.errors).toContainEqual({
1623 |           type: 'invalid_configuration',
1624 |           property: 'responseMode',
1625 |           message: 'responseNode mode requires onError: "continueRegularOutput"',
1626 |           fix: 'Set onError to ensure response is always sent'
1627 |         });
1628 |       });
1629 | 
1630 |       it('should not error on responseNode with proper error handling', () => {
1631 |         context.config = {
1632 |           path: 'my-webhook',
1633 |           httpMethod: 'POST',
1634 |           responseMode: 'responseNode',
1635 |           onError: 'continueRegularOutput'
1636 |         };
1637 |         
1638 |         NodeSpecificValidators.validateWebhook(context);
1639 |         
1640 |         const responseModeErrors = context.errors.filter(e => e.property === 'responseMode');
1641 |         expect(responseModeErrors).toHaveLength(0);
1642 |       });
1643 |     });
1644 | 
1645 |     describe('debugging and security', () => {
1646 |       it('should suggest alwaysOutputData for debugging', () => {
1647 |         context.config = {
1648 |           path: 'my-webhook',
1649 |           httpMethod: 'POST'
1650 |         };
1651 |         
1652 |         NodeSpecificValidators.validateWebhook(context);
1653 |         
1654 |         expect(context.suggestions).toContain('Enable alwaysOutputData to debug webhook payloads');
1655 |         expect(context.autofix.alwaysOutputData).toBe(true);
1656 |       });
1657 | 
1658 |       it('should suggest security measures', () => {
1659 |         context.config = {
1660 |           path: 'my-webhook',
1661 |           httpMethod: 'POST'
1662 |         };
1663 |         
1664 |         NodeSpecificValidators.validateWebhook(context);
1665 |         
1666 |         expect(context.suggestions).toContain('Consider adding webhook validation (HMAC signature verification)');
1667 |         expect(context.suggestions).toContain('Implement rate limiting for public webhooks');
1668 |       });
1669 |     });
1670 |   });
1671 | 
1672 |   describe('validateCode', () => {
1673 |     describe('empty code validation', () => {
1674 |       it('should error on empty JavaScript code', () => {
1675 |         context.config = {
1676 |           language: 'javaScript',
1677 |           jsCode: ''
1678 |         };
1679 |         
1680 |         NodeSpecificValidators.validateCode(context);
1681 |         
1682 |         expect(context.errors).toContainEqual({
1683 |           type: 'missing_required',
1684 |           property: 'jsCode',
1685 |           message: 'Code cannot be empty',
1686 |           fix: 'Add your code logic. Start with: return [{json: {result: "success"}}]'
1687 |         });
1688 |       });
1689 | 
1690 |       it('should error on whitespace-only code', () => {
1691 |         context.config = {
1692 |           language: 'javaScript',
1693 |           jsCode: '   \n\t  '
1694 |         };
1695 |         
1696 |         NodeSpecificValidators.validateCode(context);
1697 |         
1698 |         expect(context.errors).toContainEqual({
1699 |           type: 'missing_required',
1700 |           property: 'jsCode',
1701 |           message: 'Code cannot be empty',
1702 |           fix: 'Add your code logic. Start with: return [{json: {result: "success"}}]'
1703 |         });
1704 |       });
1705 | 
1706 |       it('should error on empty Python code', () => {
1707 |         context.config = {
1708 |           language: 'python',
1709 |           pythonCode: ''
1710 |         };
1711 |         
1712 |         NodeSpecificValidators.validateCode(context);
1713 |         
1714 |         expect(context.errors).toContainEqual({
1715 |           type: 'missing_required',
1716 |           property: 'pythonCode',
1717 |           message: 'Code cannot be empty',
1718 |           fix: 'Add your code logic. Start with: return [{json: {result: "success"}}]'
1719 |         });
1720 |       });
1721 |     });
1722 | 
1723 |     describe('JavaScript syntax validation', () => {
1724 |       it('should detect duplicate const declarations', () => {
1725 |         context.config = {
1726 |           language: 'javaScript',
1727 |           jsCode: 'const const x = 5; return [{json: {x}}];'
1728 |         };
1729 |         
1730 |         NodeSpecificValidators.validateCode(context);
1731 |         
1732 |         expect(context.errors).toContainEqual({
1733 |           type: 'invalid_value',
1734 |           property: 'jsCode',
1735 |           message: 'Syntax error: Duplicate const declaration',
1736 |           fix: 'Check your JavaScript syntax'
1737 |         });
1738 |       });
1739 | 
1740 |       it('should warn about await in non-async function', () => {
1741 |         context.config = {
1742 |           language: 'javaScript',
1743 |           jsCode: `
1744 |             function fetchData() {
1745 |               const result = await fetch('https://api.example.com');
1746 |               return [{json: result}];
1747 |             }
1748 |           `
1749 |         };
1750 |         
1751 |         NodeSpecificValidators.validateCode(context);
1752 |         
1753 |         expect(context.warnings).toContainEqual({
1754 |           type: 'best_practice',
1755 |           message: 'Using await inside a non-async function',
1756 |           suggestion: 'Add async keyword to the function, or use top-level await (Code nodes support it)'
1757 |         });
1758 |       });
1759 | 
1760 |       it('should suggest async usage for $helpers.httpRequest', () => {
1761 |         context.config = {
1762 |           language: 'javaScript',
1763 |           jsCode: 'const response = $helpers.httpRequest(...); return [{json: response}];'
1764 |         };
1765 |         
1766 |         NodeSpecificValidators.validateCode(context);
1767 |         
1768 |         expect(context.suggestions).toContain('$helpers.httpRequest is async - use: const response = await $helpers.httpRequest(...)');
1769 |       });
1770 | 
1771 |       it('should warn about DateTime usage', () => {
1772 |         context.config = {
1773 |           language: 'javaScript',
1774 |           jsCode: 'const now = DateTime(); return [{json: {now}}];'
1775 |         };
1776 |         
1777 |         NodeSpecificValidators.validateCode(context);
1778 |         
1779 |         expect(context.warnings).toContainEqual({
1780 |           type: 'best_practice',
1781 |           message: 'DateTime is from Luxon library',
1782 |           suggestion: 'Use DateTime.now() or DateTime.fromISO() for date operations'
1783 |         });
1784 |       });
1785 |     });
1786 | 
1787 |     describe('Python syntax validation', () => {
1788 |       it('should warn about unnecessary main check', () => {
1789 |         context.config = {
1790 |           language: 'python',
1791 |           pythonCode: `
1792 | if __name__ == "__main__":
1793 |     result = {"status": "ok"}
1794 |     return [{"json": result}]
1795 |           `
1796 |         };
1797 |         
1798 |         NodeSpecificValidators.validateCode(context);
1799 |         
1800 |         expect(context.warnings).toContainEqual({
1801 |           type: 'inefficient',
1802 |           message: 'if __name__ == "__main__" is not needed in Code nodes',
1803 |           suggestion: 'Code node Python runs directly - remove the main check'
1804 |         });
1805 |       });
1806 | 
1807 |       it('should not warn about __name__ without __main__', () => {
1808 |         context.config = {
1809 |           language: 'python',
1810 |           pythonCode: `
1811 | module_name = __name__
1812 | return [{"json": {"module": module_name}}]
1813 |           `
1814 |         };
1815 |         
1816 |         NodeSpecificValidators.validateCode(context);
1817 |         
1818 |         const mainWarnings = context.warnings.filter(w => w.message.includes('__main__'));
1819 |         expect(mainWarnings).toHaveLength(0);
1820 |       });
1821 | 
1822 |       it('should error on unavailable imports', () => {
1823 |         context.config = {
1824 |           language: 'python',
1825 |           pythonCode: 'import requests\nreturn [{"json": {"status": "ok"}}]'
1826 |         };
1827 |         
1828 |         NodeSpecificValidators.validateCode(context);
1829 |         
1830 |         expect(context.errors).toContainEqual({
1831 |           type: 'invalid_value',
1832 |           property: 'pythonCode',
1833 |           message: 'Module \'requests\' is not available in Code nodes',
1834 |           fix: 'Use JavaScript Code node with $helpers.httpRequest for HTTP requests'
1835 |         });
1836 |       });
1837 | 
1838 |       it('should check indentation after colons', () => {
1839 |         context.config = {
1840 |           language: 'python',
1841 |           pythonCode: `
1842 | def process():
1843 | result = "ok"
1844 | return [{"json": {"result": result}}]
1845 |           `
1846 |         };
1847 |         
1848 |         NodeSpecificValidators.validateCode(context);
1849 |         
1850 |         expect(context.errors).toContainEqual({
1851 |           type: 'invalid_value',
1852 |           property: 'pythonCode',
1853 |           message: 'Missing indentation after line 2',
1854 |           fix: 'Indent the line after the colon'
1855 |         });
1856 |       });
1857 |     });
1858 | 
1859 |     describe('return statement validation', () => {
1860 |       it('should error on missing return statement', () => {
1861 |         context.config = {
1862 |           language: 'javaScript',
1863 |           jsCode: 'const result = {status: "ok"}; // missing return'
1864 |         };
1865 |         
1866 |         NodeSpecificValidators.validateCode(context);
1867 |         
1868 |         expect(context.errors).toContainEqual({
1869 |           type: 'missing_required',
1870 |           property: 'jsCode',
1871 |           message: 'Code must return data for the next node',
1872 |           fix: 'Add: return [{json: {result: "success"}}]'
1873 |         });
1874 |       });
1875 | 
1876 |       it('should error on object return without array', () => {
1877 |         context.config = {
1878 |           language: 'javaScript',
1879 |           jsCode: 'return {status: "ok"};'
1880 |         };
1881 |         
1882 |         NodeSpecificValidators.validateCode(context);
1883 |         
1884 |         expect(context.errors).toContainEqual({
1885 |           type: 'invalid_value',
1886 |           property: 'jsCode',
1887 |           message: 'Return value must be an array of objects',
1888 |           fix: 'Wrap in array: return [{json: yourObject}]'
1889 |         });
1890 |       });
1891 | 
1892 |       it('should error on primitive return', () => {
1893 |         context.config = {
1894 |           language: 'javaScript',
1895 |           jsCode: 'return "success";'
1896 |         };
1897 |         
1898 |         NodeSpecificValidators.validateCode(context);
1899 |         
1900 |         expect(context.errors).toContainEqual({
1901 |           type: 'invalid_value',
1902 |           property: 'jsCode',
1903 |           message: 'Cannot return primitive values directly',
1904 |           fix: 'Return array of objects: return [{json: {value: yourData}}]'
1905 |         });
1906 |       });
1907 | 
1908 |       it('should error on Python primitive return', () => {
1909 |         context.config = {
1910 |           language: 'python',
1911 |           pythonCode: 'return "success"'
1912 |         };
1913 |         
1914 |         NodeSpecificValidators.validateCode(context);
1915 |         
1916 |         expect(context.errors).toContainEqual({
1917 |           type: 'invalid_value',
1918 |           property: 'pythonCode',
1919 |           message: 'Cannot return primitive values directly',
1920 |           fix: 'Return list of dicts: return [{"json": {"value": your_data}}]'
1921 |         });
1922 |       });
1923 | 
1924 |       it('should error on array of non-objects', () => {
1925 |         context.config = {
1926 |           language: 'javaScript',
1927 |           jsCode: 'return ["item1", "item2"];'
1928 |         };
1929 |         
1930 |         NodeSpecificValidators.validateCode(context);
1931 |         
1932 |         expect(context.errors).toContainEqual({
1933 |           type: 'invalid_value',
1934 |           property: 'jsCode',
1935 |           message: 'Array items must be objects with json property',
1936 |           fix: 'Use: return [{json: {value: "data"}}] not return ["data"]'
1937 |         });
1938 |       });
1939 | 
1940 |       it('should suggest proper items return format', () => {
1941 |         context.config = {
1942 |           language: 'javaScript',
1943 |           jsCode: 'return items;'
1944 |         };
1945 |         
1946 |         NodeSpecificValidators.validateCode(context);
1947 |         
1948 |         expect(context.suggestions).toContain(
1949 |           'Returning items directly is fine if they already have {json: ...} structure. ' +
1950 |           'To modify: return items.map(item => ({json: {...item.json, newField: "value"}}))'
1951 |         );
1952 |       });
1953 |     });
1954 | 
1955 |     describe('n8n variable usage', () => {
1956 |       it('should warn when code doesn\'t reference input data', () => {
1957 |         context.config = {
1958 |           language: 'javaScript',
1959 |           jsCode: 'const result = Math.random(); return [{json: {result}}];'
1960 |         };
1961 |         
1962 |         NodeSpecificValidators.validateCode(context);
1963 |         
1964 |         expect(context.warnings).toContainEqual({
1965 |           type: 'missing_common',
1966 |           message: 'Code doesn\'t reference input data',
1967 |           suggestion: 'Access input with: items, $input.all(), or $json (single-item mode)'
1968 |         });
1969 |       });
1970 | 
1971 |       it('should error on expression syntax in code', () => {
1972 |         context.config = {
1973 |           language: 'javaScript',
1974 |           jsCode: 'const name = {{$json.name}}; return [{json: {name}}];'
1975 |         };
1976 |         
1977 |         NodeSpecificValidators.validateCode(context);
1978 |         
1979 |         expect(context.errors).toContainEqual({
1980 |           type: 'invalid_value',
1981 |           property: 'jsCode',
1982 |           message: 'Expression syntax {{...}} is not valid in Code nodes',
1983 |           fix: 'Use regular JavaScript/Python syntax without double curly braces'
1984 |         });
1985 |       });
1986 | 
1987 |       it('should warn about wrong $node syntax', () => {
1988 |         context.config = {
1989 |           language: 'javaScript',
1990 |           jsCode: 'const data = $node[\'Previous Node\'].json; return [{json: data}];'
1991 |         };
1992 |         
1993 |         NodeSpecificValidators.validateCode(context);
1994 |         
1995 |         expect(context.warnings).toContainEqual({
1996 |           type: 'invalid_value',
1997 |           property: 'jsCode',
1998 |           message: 'Use $(\'Node Name\') instead of $node[\'Node Name\'] in Code nodes',
1999 |           suggestion: 'Replace $node[\'NodeName\'] with $(\'NodeName\')'
2000 |         });
2001 |       });
2002 | 
2003 |       it('should warn about expression-only functions', () => {
2004 |         context.config = {
2005 |           language: 'javaScript',
2006 |           jsCode: 'const now = $now(); return [{json: {now}}];'
2007 |         };
2008 |         
2009 |         NodeSpecificValidators.validateCode(context);
2010 |         
2011 |         expect(context.warnings).toContainEqual({
2012 |           type: 'invalid_value',
2013 |           property: 'jsCode',
2014 |           message: '$now() is an expression-only function not available in Code nodes',
2015 |           suggestion: 'See Code node documentation for alternatives'
2016 |         });
2017 |       });
2018 | 
2019 |       it('should warn about invalid $ usage', () => {
2020 |         context.config = {
2021 |           language: 'javaScript',
2022 |           jsCode: 'const value = $; return [{json: {value}}];'
2023 |         };
2024 |         
2025 |         NodeSpecificValidators.validateCode(context);
2026 |         
2027 |         expect(context.warnings).toContainEqual({
2028 |           type: 'best_practice',
2029 |           message: 'Invalid $ usage detected',
2030 |           suggestion: 'n8n variables start with $: $json, $input, $node, $workflow, $execution'
2031 |         });
2032 |       });
2033 | 
2034 |       it('should correct helpers usage', () => {
2035 |         context.config = {
2036 |           language: 'javaScript',
2037 |           jsCode: 'const result = helpers.httpRequest(); return [{json: {result}}];'
2038 |         };
2039 |         
2040 |         NodeSpecificValidators.validateCode(context);
2041 |         
2042 |         expect(context.warnings).toContainEqual({
2043 |           type: 'invalid_value',
2044 |           property: 'jsCode',
2045 |           message: 'Use $helpers not helpers',
2046 |           suggestion: 'Change helpers. to $helpers.'
2047 |         });
2048 |       });
2049 | 
2050 |       it('should warn about $helpers availability', () => {
2051 |         context.config = {
2052 |           language: 'javaScript',
2053 |           jsCode: 'const result = await $helpers.httpRequest(); return [{json: {result}}];'
2054 |         };
2055 |         
2056 |         NodeSpecificValidators.validateCode(context);
2057 |         
2058 |         expect(context.warnings).toContainEqual({
2059 |           type: 'best_practice',
2060 |           message: '$helpers availability varies by n8n version',
2061 |           suggestion: 'Check availability first: if (typeof $helpers !== "undefined" && $helpers.httpRequest) { ... }'
2062 |         });
2063 |       });
2064 | 
2065 |       it('should error on incorrect getWorkflowStaticData usage', () => {
2066 |         context.config = {
2067 |           language: 'javaScript',
2068 |           jsCode: 'const data = $helpers.getWorkflowStaticData(); return [{json: data}];'
2069 |         };
2070 |         
2071 |         NodeSpecificValidators.validateCode(context);
2072 |         
2073 |         expect(context.errors).toContainEqual({
2074 |           type: 'invalid_value',
2075 |           property: 'jsCode',
2076 |           message: '$helpers.getWorkflowStaticData() will cause "$helpers is not defined" error',
2077 |           fix: 'Use $getWorkflowStaticData("global") or $getWorkflowStaticData("node") directly'
2078 |         });
2079 |       });
2080 | 
2081 |       it('should warn about wrong JMESPath parameter order', () => {
2082 |         context.config = {
2083 |           language: 'javaScript',
2084 |           jsCode: 'const result = $jmespath("name", data); return [{json: {result}}];'
2085 |         };
2086 |         
2087 |         NodeSpecificValidators.validateCode(context);
2088 |         
2089 |         expect(context.warnings).toContainEqual({
2090 |           type: 'invalid_value',
2091 |           property: 'jsCode',
2092 |           message: 'Code node $jmespath has reversed parameter order: $jmespath(data, query)',
2093 |           suggestion: 'Use: $jmespath(dataObject, "query.path") not $jmespath("query.path", dataObject)'
2094 |         });
2095 |       });
2096 | 
2097 |       it('should warn about webhook data access', () => {
2098 |         context.config = {
2099 |           language: 'javaScript',
2100 |           jsCode: 'const payload = items[0].json.payload; return [{json: {payload}}];'
2101 |         };
2102 |         
2103 |         NodeSpecificValidators.validateCode(context);
2104 |         
2105 |         expect(context.warnings).toContainEqual({
2106 |           type: 'best_practice',
2107 |           message: 'If processing webhook data, remember it\'s nested under .body',
2108 |           suggestion: 'Webhook payloads are at items[0].json.body, not items[0].json'
2109 |         });
2110 |       });
2111 | 
2112 |       it('should warn about webhook data access when webhook node is referenced', () => {
2113 |         context.config = {
2114 |           language: 'javaScript',
2115 |           jsCode: 'const webhookData = $("Webhook"); const data = items[0].json.someField; return [{json: {data}}];'
2116 |         };
2117 |         
2118 |         NodeSpecificValidators.validateCode(context);
2119 |         
2120 |         expect(context.warnings).toContainEqual({
2121 |           type: 'invalid_value',
2122 |           property: 'jsCode',
2123 |           message: 'Webhook data is nested under .body property',
2124 |           suggestion: 'Use items[0].json.body.fieldName instead of items[0].json.fieldName for webhook data'
2125 |         });
2126 |       });
2127 | 
2128 |       it('should warn when code includes webhook string', () => {
2129 |         context.config = {
2130 |           language: 'javaScript',
2131 |           jsCode: '// Process webhook response\nconst data = items[0].json.data; return [{json: {data}}];'
2132 |         };
2133 |         
2134 |         NodeSpecificValidators.validateCode(context);
2135 |         
2136 |         expect(context.warnings).toContainEqual({
2137 |           type: 'invalid_value',
2138 |           property: 'jsCode',
2139 |           message: 'Webhook data is nested under .body property',
2140 |           suggestion: 'Use items[0].json.body.fieldName instead of items[0].json.fieldName for webhook data'
2141 |         });
2142 |       });
2143 | 
2144 |       it('should error on JMESPath numeric literals without backticks', () => {
2145 |         context.config = {
2146 |           language: 'javaScript',
2147 |           jsCode: 'const filtered = $jmespath(data, "[?age >= 18]"); return [{json: {filtered}}];'
2148 |         };
2149 |         
2150 |         NodeSpecificValidators.validateCode(context);
2151 |         
2152 |         expect(context.errors).toContainEqual({
2153 |           type: 'invalid_value',
2154 |           property: 'jsCode',
2155 |           message: 'JMESPath numeric literal 18 must be wrapped in backticks',
2156 |           fix: 'Change [?field >= 18] to [?field >= `18`]'
2157 |         });
2158 |       });
2159 |     });
2160 | 
2161 |     describe('code security', () => {
2162 |       it('should warn about eval usage', () => {
2163 |         context.config = {
2164 |           language: 'javaScript',
2165 |           jsCode: 'const result = eval("1 + 1"); return [{json: {result}}];'
2166 |         };
2167 |         
2168 |         NodeSpecificValidators.validateCode(context);
2169 |         
2170 |         expect(context.warnings).toContainEqual({
2171 |           type: 'security',
2172 |           message: 'Avoid eval() - it\'s a security risk',
2173 |           suggestion: 'Use safer alternatives or built-in functions'
2174 |         });
2175 |       });
2176 | 
2177 |       it('should warn about Function constructor', () => {
2178 |         context.config = {
2179 |           language: 'javaScript',
2180 |           jsCode: 'const fn = new Function("return 1"); return [{json: {result: fn()}}];'
2181 |         };
2182 |         
2183 |         NodeSpecificValidators.validateCode(context);
2184 |         
2185 |         expect(context.warnings).toContainEqual({
2186 |           type: 'security',
2187 |           message: 'Avoid Function constructor - use regular functions',
2188 |           suggestion: 'Use safer alternatives or built-in functions'
2189 |         });
2190 |       });
2191 | 
2192 |       it('should warn about unavailable modules', () => {
2193 |         context.config = {
2194 |           language: 'javaScript',
2195 |           jsCode: 'const axios = require("axios"); return [{json: {}}];'
2196 |         };
2197 |         
2198 |         NodeSpecificValidators.validateCode(context);
2199 |         
2200 |         expect(context.warnings).toContainEqual({
2201 |           type: 'security',
2202 |           message: 'Cannot require(\'axios\') - only built-in Node.js modules are available',
2203 |           suggestion: 'Available modules: crypto, util, querystring, url, buffer'
2204 |         });
2205 |       });
2206 | 
2207 |       it('should warn about dynamic require', () => {
2208 |         context.config = {
2209 |           language: 'javaScript',
2210 |           jsCode: 'const module = require(moduleName); return [{json: {}}];'
2211 |         };
2212 |         
2213 |         NodeSpecificValidators.validateCode(context);
2214 |         
2215 |         expect(context.warnings).toContainEqual({
2216 |           type: 'security',
2217 |           message: 'Dynamic require() not supported',
2218 |           suggestion: 'Use static require with string literals: require("crypto")'
2219 |         });
2220 |       });
2221 | 
2222 |       it('should warn about crypto usage without require', () => {
2223 |         context.config = {
2224 |           language: 'javaScript',
2225 |           jsCode: 'const hash = crypto.createHash("sha256"); return [{json: {hash}}];'
2226 |         };
2227 |         
2228 |         NodeSpecificValidators.validateCode(context);
2229 |         
2230 |         expect(context.warnings).toContainEqual({
2231 |           type: 'invalid_value',
2232 |           message: 'Using crypto without require statement',
2233 |           suggestion: 'Add: const crypto = require("crypto"); at the beginning (ignore editor warnings)'
2234 |         });
2235 |       });
2236 | 
2237 |       it('should warn about file system access', () => {
2238 |         context.config = {
2239 |           language: 'javaScript',
2240 |           jsCode: 'const fs = require("fs"); return [{json: {}}];'
2241 |         };
2242 |         
2243 |         NodeSpecificValidators.validateCode(context);
2244 |         
2245 |         expect(context.warnings).toContainEqual({
2246 |           type: 'security',
2247 |           message: 'File system and process access not available in Code nodes',
2248 |           suggestion: 'Use other n8n nodes for file operations (e.g., Read/Write Files node)'
2249 |         });
2250 |       });
2251 |     });
2252 | 
2253 |     describe('mode-specific validation', () => {
2254 |       it('should warn about items usage in single-item mode', () => {
2255 |         context.config = {
2256 |           mode: 'runOnceForEachItem',
2257 |           language: 'javaScript',
2258 |           jsCode: 'const allItems = items.length; return [{json: {count: allItems}}];'
2259 |         };
2260 |         
2261 |         NodeSpecificValidators.validateCode(context);
2262 |         
2263 |         expect(context.warnings).toContainEqual({
2264 |           type: 'best_practice',
2265 |           message: 'In "Run Once for Each Item" mode, use $json instead of items array',
2266 |           suggestion: 'Access current item data with $json.fieldName'
2267 |         });
2268 |       });
2269 | 
2270 |       it('should warn about $json usage without single-item mode', () => {
2271 |         context.config = {
2272 |           language: 'javaScript',
2273 |           jsCode: 'const name = $json.name; return [{json: {name}}];'
2274 |         };
2275 |         
2276 |         NodeSpecificValidators.validateCode(context);
2277 |         
2278 |         expect(context.warnings).toContainEqual({
2279 |           type: 'best_practice',
2280 |           message: '$json only works in "Run Once for Each Item" mode',
2281 |           suggestion: 'Either set mode: "runOnceForEachItem" or use items[0].json'
2282 |         });
2283 |       });
2284 |     });
2285 | 
2286 |     describe('error handling', () => {
2287 |       it('should suggest error handling for complex code', () => {
2288 |         context.config = {
2289 |           language: 'javaScript',
2290 |           jsCode: 'a'.repeat(101) + '\nreturn [{json: {}}];'
2291 |         };
2292 |         
2293 |         NodeSpecificValidators.validateCode(context);
2294 |         
2295 |         expect(context.warnings).toContainEqual({
2296 |           type: 'best_practice',
2297 |           property: 'errorHandling',
2298 |           message: 'Code nodes can throw errors - consider error handling',
2299 |           suggestion: 'Add onError: "continueRegularOutput" to handle errors gracefully'
2300 |         });
2301 |         
2302 |         expect(context.autofix.onError).toBe('continueRegularOutput');
2303 |       });
2304 |     });
2305 |   });
2306 | });
```
Page 50/59FirstPrevNextLast