@burgan-tech/vnext-cli
Version:
CLI for creating and managing vNext domain projects with modular component sharing
477 lines (425 loc) โข 16.6 kB
JavaScript
#!/usr/bin/env node
const RefResolver = require('./lib/ref-resolver');
const path = require('path');
const chalk = require('chalk');
const fs = require('fs-extra');
async function testRefResolver() {
console.log(chalk.blue('๐งช Testing Enhanced Reference Resolver...'));
try {
const resolver = new RefResolver({
cacheDir: path.join(__dirname, '.test-cache'),
validateSchemas: false, // Disable schema validation for basic tests
validateReferenceConsistency: true,
schemaPath: path.join(__dirname, 'template', '.vscode', 'schemas')
});
// Test 1: Parse local reference
console.log(chalk.yellow('\n๐ Test 1: Parse Local Reference'));
const localRef = resolver.parseRef('Tasks/task-invalidate-cache.1.0.0.json');
console.log(' โ
Parsed:', JSON.stringify(localRef, null, 2));
// Test 2: Parse external reference
console.log(chalk.yellow('\n๐ Test 2: Parse External Reference'));
const externalRef = resolver.parseRef('@vnext/domain-core/Tasks/task-audit-log.1.0.0.json');
console.log(' โ
Parsed:', JSON.stringify(externalRef, null, 2));
// Test 3: Extract version from filename
console.log(chalk.yellow('\n๐ Test 3: Extract Version'));
const version = resolver.extractVersionFromFilename('task-example.1.2.3.json');
console.log(' โ
Version extracted:', version);
// Test 4: Component type detection
console.log(chalk.yellow('\n๐ Test 4: Component Type Detection'));
const taskType = resolver.detectComponentType('Tasks/task-example.1.0.0.json');
const workflowType = resolver.detectComponentType('Workflows/workflow-example.1.0.0.json');
console.log(' โ
Task type:', taskType);
console.log(' โ
Workflow type:', workflowType);
// Test 5: Reference consistency validation (success case)
console.log(chalk.yellow('\n๐ Test 5: Reference Consistency Validation (Success)'));
const validContent = {
key: 'task-invalidate-cache',
version: '1.0.0',
domain: 'core',
flow: 'sys-tasks'
};
const validParsedRef = {
filePath: 'Tasks/task-invalidate-cache.1.0.0.json',
version: '1.0.0'
};
try {
await resolver.validateReferenceConsistency(
'Tasks/task-invalidate-cache.1.0.0.json',
validContent,
validParsedRef
);
console.log(' โ
Reference consistency validation passed');
} catch (error) {
console.log(' โ Unexpected error:', error.message);
}
// Test 6: Reference consistency validation (failure case)
console.log(chalk.yellow('\n๐ Test 6: Reference Consistency Validation (Failure)'));
const invalidContent = {
key: 'different-task-name',
version: '1.0.0',
domain: 'core',
flow: 'sys-tasks'
};
try {
await resolver.validateReferenceConsistency(
'Tasks/task-invalidate-cache.1.0.0.json',
invalidContent,
validParsedRef
);
console.log(' โ
Expected validation error:', error.message);
} catch (error) {
console.log(' โ
Expected validation error:', error.message);
}
// Test 7: Version format validation
console.log(chalk.yellow('\n๐ Test 7: Version Format Validation'));
const invalidVersionContent = {
key: 'task-invalidate-cache',
version: 'invalid-version',
domain: 'core',
flow: 'sys-tasks'
};
try {
await resolver.validateReferenceConsistency(
'Tasks/task-invalidate-cache.invalid-version.json',
invalidVersionContent,
{ filePath: 'Tasks/task-invalidate-cache.invalid-version.json', version: 'invalid-version' }
);
console.log(' โ Should have failed but passed');
} catch (error) {
console.log(' โ
Expected version format error:', error.message);
}
// Test 8: Mock validation with enhanced details
console.log(chalk.yellow('\n๐ Test 8: Enhanced Reference Validation Structure'));
const mockJson = {
"key": "test-component",
"version": "1.0.0",
"domain": "test",
"tasks": [
{
"ref": "Tasks/task-example.1.0.0.json"
},
{
"ref": "@vnext/domain-core/Tasks/task-audit.1.0.0.json"
}
],
"nested": {
"workflow": {
"ref": "Workflows/example-flow.1.0.0.json"
}
}
};
// This will fail because files don't exist, but shows the enhanced structure
try {
const validation = await resolver.validateAllReferences(mockJson, 'test-domain');
console.log(' โ
Validation result:', {
valid: validation.valid,
details: validation.validationDetails,
errors: validation.errors.length
});
} catch (error) {
console.log(' โ ๏ธ Expected error (files don\'t exist):', error.message);
}
// Test 9: Test with actual template file
console.log(chalk.yellow('\n๐ Test 9: Test with Actual Template File'));
const templateTaskPath = path.join(__dirname, 'template', '{domainName}', 'Tasks', 'task-invalidate-cache.1.0.0.json');
if (await fs.pathExists(templateTaskPath)) {
try {
const templateContent = await fs.readJSON(templateTaskPath);
console.log(' ๐ Template task content:', {
key: templateContent.key,
version: templateContent.version,
domain: templateContent.domain,
flow: templateContent.flow
});
// Validate consistency if it's a real template
if (templateContent.key && templateContent.version) {
await resolver.validateReferenceConsistency(
'Tasks/task-invalidate-cache.1.0.0.json',
templateContent,
{ filePath: 'Tasks/task-invalidate-cache.1.0.0.json', version: '1.0.0' }
);
console.log(' โ
Template file consistency validation passed');
}
} catch (error) {
console.log(' โ ๏ธ Template file validation error:', error.message);
}
} else {
console.log(' โน๏ธ Template file not found, skipping test');
}
// Test 10: Load validation config
console.log(chalk.yellow('\n๐ Test 10: Load Validation Config'));
const configPath = path.join(__dirname, 'template', 'vnext.config.json');
if (await fs.pathExists(configPath)) {
try {
const config = await resolver.loadValidationConfig(configPath);
console.log(' โ
Config loaded:', {
domain: config.domain,
hasReferenceResolution: !!config.referenceResolution,
validateReferenceConsistency: config.referenceResolution?.validateReferenceConsistency,
validateSchemas: config.referenceResolution?.validateSchemas
});
} catch (error) {
console.log(' โ ๏ธ Config loading error:', error.message);
}
} else {
console.log(' โน๏ธ Config file not found, skipping test');
}
// Test 11: Schema validation with invalid task type
console.log(chalk.yellow('\n๐ Test 11: Schema Validation (Invalid Task Type)'));
resolver.options.validateSchemas = true; // Enable schema validation
const invalidTaskComponent = {
"key": "test-invalid-task",
"version": "1.0.0",
"domain": "test-domain",
"flow": "sys-tasks",
"flowVersion": "1.0.0",
"tags": ["test"],
"attributes": {
"type": "invalid-type-value" // This should fail validation
}
};
try {
await resolver.validateComponentSchema(invalidTaskComponent, 'Tasks/test-invalid-task.1.0.0.json');
console.log(' โ Should have failed schema validation but passed');
} catch (error) {
console.log(' โ
Expected schema validation error:', error.message);
}
// Test 12: Schema validation with valid task type
console.log(chalk.yellow('\n๐ Test 12: Schema Validation (Valid Task Type)'));
const validTaskComponent = {
"key": "test-valid-task",
"version": "1.0.0",
"domain": "test-domain",
"flow": "sys-tasks",
"flowVersion": "1.0.0",
"tags": ["test"],
"attributes": {
"type": "3", // Valid type for Dapr Service
"config": {
"appId": "test-app",
"methodName": "/api/test"
}
}
};
try {
await resolver.validateComponentSchema(validTaskComponent, 'Tasks/test-valid-task.1.0.0.json');
console.log(' โ
Schema validation passed for valid task');
} catch (error) {
console.log(' โ ๏ธ Unexpected error:', error.message);
}
// Test 13: Business validation (filename inconsistency)
console.log(chalk.yellow('\n๐ Test 13: Business Validation (Filename Inconsistency)'));
const inconsistentNameComponent = {
"key": "different-name",
"version": "1.0.0",
"domain": "test-domain",
"flow": "sys-tasks",
"flowVersion": "1.0.0",
"tags": ["test"],
"attributes": {
"type": "3",
"config": {
"appId": "test-app",
"methodName": "/api/test"
}
}
};
try {
await resolver.validateComponentSchema(inconsistentNameComponent, 'Tasks/wrong-filename.1.0.0.json');
console.log(' โ
Schema validation passed (business warnings expected above)');
} catch (error) {
console.log(' โ ๏ธ Unexpected error:', error.message);
}
// Test 14: Metadata cleanup validation
console.log(chalk.yellow('\n๐ Test 14: Metadata Cleanup (Resolver Fields)'));
const componentWithMetadata = {
"key": "test-with-metadata",
"version": "1.0.0",
"domain": "test-domain",
"flow": "sys-tasks",
"flowVersion": "1.0.0",
"tags": ["test"],
"attributes": {
"type": "3",
"config": {
"appId": "test-app",
"methodName": "/api/test"
}
},
// These metadata fields should be cleaned before validation
"_resolvedFrom": "local:Tasks/test-with-metadata.1.0.0.json",
"_resolvedAt": "2025-06-29T22:49:30.665Z",
"_packageVersion": "1.0.0"
};
try {
await resolver.validateComponentSchema(componentWithMetadata, 'Tasks/test-with-metadata.1.0.0.json');
console.log(' โ
Schema validation passed despite metadata fields (cleaned successfully)');
} catch (error) {
console.log(' โ Unexpected error (metadata not cleaned):', error.message);
}
// Test 15: Test cleanMetadataFields method directly
console.log(chalk.yellow('\n๐ Test 15: Direct Metadata Cleanup Method'));
const testComponentWithMeta = {
"key": "test",
"version": "1.0.0",
"_resolvedFrom": "test",
"_resolvedAt": "test",
"_packageVersion": "test",
"normalField": "should-remain"
};
const cleaned = resolver.cleanMetadataFields(testComponentWithMeta);
const hasMetadataRemoved = !cleaned._resolvedFrom && !cleaned._resolvedAt && !cleaned._packageVersion;
const hasNormalField = cleaned.normalField === "should-remain";
if (hasMetadataRemoved && hasNormalField) {
console.log(' โ
Metadata cleanup method works correctly');
} else {
console.log(' โ Metadata cleanup method failed:', {
hasMetadataRemoved,
hasNormalField,
cleanedKeys: Object.keys(cleaned)
});
}
// Test 16: ref validation for extension (raw payload)
console.log(chalk.yellow('\n๐ Test 16: Extension Schema Validation (Raw Payload)'));
const extensionRawPayload = {
"key": "test-extension",
"version": "1.0.0",
"domain": "core",
"flow": "sys-extensions",
"flowVersion": "1.0.0",
"tags": ["test"],
"attributes": {
"type": 1,
"scope": 1,
"task": {
"order": 1,
"task": {
"key": "test-task",
"domain": "core",
"flow": "sys-tasks",
"version": "1.0.0"
},
"mapping": {
"location": "./src/TestMapping.csx",
"code": ""
}
}
}
};
try {
await resolver.validateComponentSchema(extensionRawPayload, 'Extensions/test-extension.1.0.0.json');
console.log(' โ
Extension raw payload validation passed');
} catch (error) {
console.log(' โ ๏ธ Extension raw payload validation error:', error.message);
}
// Test 17: ref validation for extension (ref payload)
console.log(chalk.yellow('\n๐ Test 17: Extension Schema Validation (ref Payload)'));
const extensionRefPayload = {
"key": "test-extension-ref",
"version": "1.0.0",
"domain": "core",
"flow": "sys-extensions",
"flowVersion": "1.0.0",
"tags": ["test"],
"attributes": {
"type": 1,
"scope": 1,
"task": {
"order": 1,
"task": {
"ref": "Tasks/test-task.1.0.0.json"
},
"mapping": {
"location": "./src/TestMapping.csx",
"code": ""
}
}
}
};
try {
await resolver.validateComponentSchema(extensionRefPayload, 'Extensions/test-extension-ref.1.0.0.json');
console.log(' โ
Extension ref payload validation passed');
} catch (error) {
console.log(' โ ๏ธ Extension ref payload validation error:', error.message);
}
// Test 18: Function ref validation (raw payload)
console.log(chalk.yellow('\n๐ Test 18: Function Schema Validation (Raw Payload)'));
const functionRawPayload = {
"key": "test-function",
"version": "1.0.0",
"domain": "core",
"flow": "sys-functions",
"flowVersion": "1.0.0",
"tags": ["test"],
"attributes": {
"scope": 1,
"task": {
"key": "test-task",
"domain": "core",
"flow": "sys-tasks",
"version": "1.0.0"
},
"mapping": {
"location": "./src/TestMapping.csx",
"code": ""
}
}
};
try {
await resolver.validateComponentSchema(functionRawPayload, 'Functions/test-function.1.0.0.json');
console.log(' โ
Function raw payload validation passed');
} catch (error) {
console.log(' โ ๏ธ Function raw payload validation error:', error.message);
}
// Test 19: Function ref validation (ref payload)
console.log(chalk.yellow('\n๐ Test 19: Function Schema Validation (ref Payload)'));
const functionRefPayload = {
"key": "test-function-ref",
"version": "1.0.0",
"domain": "core",
"flow": "sys-functions",
"flowVersion": "1.0.0",
"tags": ["test"],
"attributes": {
"scope": 1,
"task": {
"ref": "Tasks/test-task.1.0.0.json"
},
"mapping": {
"location": "./src/TestMapping.csx",
"code": ""
}
}
};
try {
await resolver.validateComponentSchema(functionRefPayload, 'Functions/test-function-ref.1.0.0.json');
console.log(' โ
Function ref payload validation passed');
} catch (error) {
console.log(' โ ๏ธ Function ref payload validation error:', error.message);
}
console.log(chalk.green('\n๐ Enhanced Reference Resolver tests completed!'));
console.log(chalk.blue('โจ New features tested:'));
console.log(' โข Reference consistency validation (filename vs content)');
console.log(' โข Version format validation');
console.log(' โข Key format validation');
console.log(' โข Enhanced validation reporting');
console.log(' โข Configuration loading');
console.log(' โข JSON Schema validation (like validate-component.js)');
console.log(' โข Domain mismatch validation');
console.log(' โข Business rule validations');
console.log(' โข Enhanced error reporting with detailed paths');
console.log(' โข Metadata cleanup (fixes additionalProperties: false schema errors)');
console.log(' โข Component type detection fix (paths with/without leading slash)');
console.log(' โข ref support validation for all reference types');
console.log(chalk.gray('โน๏ธ Note: Some tests failed because referenced files don\'t exist - this is expected.'));
} catch (error) {
console.error(chalk.red('โ Test failed:'), error.message);
console.error(error.stack);
process.exit(1);
}
}
// Run tests if this file is executed directly
if (require.main === module) {
testRefResolver();
}
module.exports = { testRefResolver };