fix: enable schema-based resourceLocator mode validation

Root cause analysis revealed validator was looking at wrong path for
modes data. n8n stores modes at top level of properties, not nested
in typeOptions.

Changes:
- config-validator.ts: Changed from prop.typeOptions?.resourceLocator?.modes
  to prop.modes (lines 273-310)
- property-extractor.ts: Added modes field to normalizeProperties to
  capture mode definitions from n8n nodes
- Updated all test cases to match real n8n schema structure with modes
  at property top level
- Rebuilt database with modes field

Results:
- 100% coverage: All 70 resourceLocator nodes now have modes defined
- Schema-based validation now ACTIVE (was being skipped before)
- False positive eliminated: Google Sheets "name" mode now validates
- Helpful error messages showing actual allowed modes from schema

Testing:
- All 33 unit tests pass
- Verified with n8n-mcp-tester: valid "name" mode passes, invalid modes
  fail with clear error listing allowed options [list, url, id, name]

Fixes #304 (Google Sheets false positive)
Related to #306 (validator improvements)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
czlonkowski
2025-10-11 19:29:21 +02:00
parent 4625ebf64d
commit fc8fb66900
4 changed files with 24 additions and 36 deletions

View File

@@ -270,12 +270,13 @@ export class ConfigValidator {
message: `resourceLocator '${key}.mode' must be a string, got ${typeof value.mode}`,
fix: `Set mode to a valid string value`
});
} else if (prop.typeOptions?.resourceLocator?.modes) {
} else if (prop.modes) {
// Schema-based validation: Check if mode exists in the modes definition
// In n8n, modes are defined at the top level of resourceLocator properties
// Modes can be defined in different ways:
// 1. Object with mode keys: { list: {...}, id: {...}, url: {...}, name: {...} }
// 2. Array of mode objects: [{name: 'list', ...}, {name: 'id', ...}]
const modes = prop.typeOptions.resourceLocator.modes;
// 1. Array of mode objects: [{name: 'list', ...}, {name: 'id', ...}, {name: 'name', ...}]
// 2. Object with mode keys: { list: {...}, id: {...}, url: {...}, name: {...} }
const modes = prop.modes;
// Validate modes structure before processing to prevent crashes
if (!modes || typeof modes !== 'object') {
@@ -286,7 +287,7 @@ export class ConfigValidator {
let allowedModes: string[] = [];
if (Array.isArray(modes)) {
// Array format: extract name property from each mode object
// Array format (most common in n8n): extract name property from each mode object
allowedModes = modes
.map(m => (typeof m === 'object' && m !== null) ? m.name : m)
.filter(m => typeof m === 'string' && m.length > 0);
@@ -305,7 +306,7 @@ export class ConfigValidator {
});
}
}
// If no typeOptions.resourceLocator.modes defined, skip mode validation
// If no modes defined at property level, skip mode validation
// This prevents false positives for nodes with dynamic/runtime-determined modes
if (value.value === undefined) {