From e7bfb46191540abf1599b223355c250e6842fc9e Mon Sep 17 00:00:00 2001 From: Tankatronic Date: Wed, 15 Apr 2026 12:14:33 -0700 Subject: [PATCH] Add Azure DevOps URL parsing tests This test file verifies the functionality of the CustomModuleManager's parseSource method for Azure DevOps Git URLs, including both dev.azure.com and legacy visualstudio.com formats. It includes various assertions to check the validity and expected outputs for different URL formats. --- test/test-azure-devops-url-parsing.js | 159 ++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 test/test-azure-devops-url-parsing.js diff --git a/test/test-azure-devops-url-parsing.js b/test/test-azure-devops-url-parsing.js new file mode 100644 index 000000000..c7b3a6818 --- /dev/null +++ b/test/test-azure-devops-url-parsing.js @@ -0,0 +1,159 @@ +/** + * Azure DevOps URL Parsing Tests + * + * Verifies that CustomModuleManager.parseSource() correctly handles + * Azure DevOps Git URLs (dev.azure.com and legacy visualstudio.com). + * + * Fixes: https://github.com/bmad-code-org/BMAD-METHOD/issues/2268 + * Usage: node test/test-azure-devops-url-parsing.js + */ + +const { CustomModuleManager } = require('../tools/installer/modules/custom-module-manager'); + +// ANSI colors +const colors = { + reset: '\u001B[0m', + green: '\u001B[32m', + red: '\u001B[31m', + cyan: '\u001B[36m', + dim: '\u001B[2m', +}; + +let passed = 0; +let failed = 0; + +function assert(condition, testName, errorMessage = '') { + if (condition) { + console.log(`${colors.green}✓${colors.reset} ${testName}`); + passed++; + } else { + console.log(`${colors.red}✗${colors.reset} ${testName}`); + if (errorMessage) { + console.log(` ${colors.dim}${errorMessage}${colors.reset}`); + } + failed++; + } +} + +const manager = new CustomModuleManager(); + +// ─── Azure DevOps: dev.azure.com ──────────────────────────────────────────── + +console.log(`\n${colors.cyan}Azure DevOps URL parsing (dev.azure.com)${colors.reset}\n`); + +{ + const result = manager.parseSource('https://dev.azure.com/myorg/MyProject/_git/my-module'); + assert(result.isValid === true, 'dev.azure.com basic URL is valid'); + assert(result.type === 'url', 'dev.azure.com type is url'); + assert( + result.cloneUrl === 'https://dev.azure.com/myorg/MyProject/_git/my-module', + 'dev.azure.com cloneUrl preserves full _git path', + `Got: ${result.cloneUrl}`, + ); + assert(result.subdir === null, 'dev.azure.com basic URL has no subdir'); + assert( + result.cacheKey === 'dev.azure.com/myorg/MyProject/my-module', + 'dev.azure.com cacheKey includes org/project/repo', + `Got: ${result.cacheKey}`, + ); + assert( + result.displayName === 'MyProject/my-module', + 'dev.azure.com displayName is project/repo', + `Got: ${result.displayName}`, + ); +} + +{ + const result = manager.parseSource('https://dev.azure.com/myorg/MyProject/_git/my-module.git'); + assert(result.isValid === true, 'dev.azure.com URL with .git suffix is valid'); + assert( + result.cloneUrl === 'https://dev.azure.com/myorg/MyProject/_git/my-module', + 'dev.azure.com .git suffix stripped from cloneUrl', + `Got: ${result.cloneUrl}`, + ); +} + +{ + const result = manager.parseSource('https://dev.azure.com/myorg/MyProject/_git/my-module/path/to/subdir'); + assert(result.isValid === true, 'dev.azure.com URL with subdir path is valid'); + assert( + result.cloneUrl === 'https://dev.azure.com/myorg/MyProject/_git/my-module', + 'dev.azure.com subdir URL cloneUrl excludes subdir', + `Got: ${result.cloneUrl}`, + ); + assert( + result.subdir === 'path/to/subdir', + 'dev.azure.com subdir correctly extracted', + `Got: ${result.subdir}`, + ); +} + +// ─── Azure DevOps: legacy visualstudio.com ────────────────────────────────── + +console.log(`\n${colors.cyan}Azure DevOps URL parsing (visualstudio.com)${colors.reset}\n`); + +{ + const result = manager.parseSource('https://myorg.visualstudio.com/MyProject/_git/my-module'); + assert(result.isValid === true, 'visualstudio.com basic URL is valid'); + assert(result.type === 'url', 'visualstudio.com type is url'); + assert( + result.cloneUrl === 'https://myorg.visualstudio.com/MyProject/_git/my-module', + 'visualstudio.com cloneUrl preserves full _git path', + `Got: ${result.cloneUrl}`, + ); + assert(result.subdir === null, 'visualstudio.com basic URL has no subdir'); + assert( + result.cacheKey === 'myorg.visualstudio.com/MyProject/my-module', + 'visualstudio.com cacheKey is host/project/repo', + `Got: ${result.cacheKey}`, + ); +} + +// ─── Non-Azure URLs still work ────────────────────────────────────────────── + +console.log(`\n${colors.cyan}Non-Azure HTTPS URLs (regression check)${colors.reset}\n`); + +{ + const result = manager.parseSource('https://github.com/owner/repo'); + assert(result.isValid === true, 'GitHub basic URL still valid'); + assert( + result.cloneUrl === 'https://github.com/owner/repo', + 'GitHub cloneUrl unchanged', + `Got: ${result.cloneUrl}`, + ); + assert( + result.cacheKey === 'github.com/owner/repo', + 'GitHub cacheKey unchanged', + `Got: ${result.cacheKey}`, + ); +} + +{ + const result = manager.parseSource('https://github.com/owner/repo/tree/main/subdir'); + assert(result.isValid === true, 'GitHub URL with tree path still valid'); + assert( + result.cloneUrl === 'https://github.com/owner/repo', + 'GitHub tree URL cloneUrl correct', + `Got: ${result.cloneUrl}`, + ); + assert( + result.subdir === 'subdir', + 'GitHub tree subdir still extracted', + `Got: ${result.subdir}`, + ); +} + +{ + const result = manager.parseSource('git@github.com:owner/repo.git'); + assert(result.isValid === true, 'SSH URL still valid'); + assert( + result.cloneUrl === 'git@github.com:owner/repo.git', + 'SSH cloneUrl unchanged', + `Got: ${result.cloneUrl}`, + ); +} + +// ─── Summary ──────────────────────────────────────────────────────────────── + +console.log(`\n${colors.cyan}Results: ${passed} passed, ${failed} failed${colors.reset}\n`); +process.exit(failed > 0 ? 1 : 0);