diff --git a/eng/common-tests/Verify-Links-ProcessNpmLink.Tests.ps1 b/eng/common-tests/Verify-Links-ProcessNpmLink.Tests.ps1 new file mode 100644 index 00000000000..1ea0589d1b2 --- /dev/null +++ b/eng/common-tests/Verify-Links-ProcessNpmLink.Tests.ps1 @@ -0,0 +1,97 @@ +Import-Module Pester + +BeforeAll { + . $PSScriptRoot/../common/scripts/Verify-Links.ps1 + + # Helper function to test URL transformation logic directly without web requests + # This mirrors the transformation logic in ProcessNpmLink to enable unit testing + function Get-TransformedNpmUrl([System.Uri]$linkUri) { + $urlString = $linkUri.ToString() + if ($urlString -match '^https?://(?:www\.)?npmjs\.com/package/([^?#]+)/v/([^?#]+)') { + # Versioned URL: remove the /v/ segment but keep the version + return "https://registry.npmjs.org/$($matches[1])/$($matches[2])" + } + elseif ($urlString -match '^https?://(?:www\.)?npmjs\.com/package/([^?#]+)') { + # Non-versioned URL: just replace the domain + return "https://registry.npmjs.org/$($matches[1])" + } + else { + # Fallback: use the original URL if it doesn't match expected patterns + return $urlString + } + } +} + +Describe "ProcessNpmLink" { + It "Should handle versioned scoped package URL" { + $inputUrl = [System.Uri]"https://www.npmjs.com/package/@azure/ai-agents/v/1.1.0" + $apiUrl = Get-TransformedNpmUrl $inputUrl + $apiUrl | Should -Be "https://registry.npmjs.org/@azure/ai-agents/1.1.0" + } + + It "Should handle versioned unscoped package URL" { + $inputUrl = [System.Uri]"https://www.npmjs.com/package/express/v/4.18.2" + $apiUrl = Get-TransformedNpmUrl $inputUrl + $apiUrl | Should -Be "https://registry.npmjs.org/express/4.18.2" + } + + It "Should handle non-versioned scoped package URL" { + $inputUrl = [System.Uri]"https://www.npmjs.com/package/@azure/ai-agents" + $apiUrl = Get-TransformedNpmUrl $inputUrl + $apiUrl | Should -Be "https://registry.npmjs.org/@azure/ai-agents" + } + + It "Should handle non-versioned unscoped package URL" { + $inputUrl = [System.Uri]"https://www.npmjs.com/package/express" + $apiUrl = Get-TransformedNpmUrl $inputUrl + $apiUrl | Should -Be "https://registry.npmjs.org/express" + } + + It "Should handle URL without www prefix - versioned" { + $inputUrl = [System.Uri]"https://npmjs.com/package/@azure/ai-agents/v/1.1.0" + $apiUrl = Get-TransformedNpmUrl $inputUrl + $apiUrl | Should -Be "https://registry.npmjs.org/@azure/ai-agents/1.1.0" + } + + It "Should handle URL without www prefix - non-versioned" { + $inputUrl = [System.Uri]"https://npmjs.com/package/@azure/identity" + $apiUrl = Get-TransformedNpmUrl $inputUrl + $apiUrl | Should -Be "https://registry.npmjs.org/@azure/identity" + } + + It "Should handle package name with hyphens and numbers" { + $inputUrl = [System.Uri]"https://www.npmjs.com/package/lodash-es" + $apiUrl = Get-TransformedNpmUrl $inputUrl + $apiUrl | Should -Be "https://registry.npmjs.org/lodash-es" + } + + It "Should handle package with multiple version segments" { + $inputUrl = [System.Uri]"https://www.npmjs.com/package/@azure/ai-agents/v/1.1.0-beta.1" + $apiUrl = Get-TransformedNpmUrl $inputUrl + $apiUrl | Should -Be "https://registry.npmjs.org/@azure/ai-agents/1.1.0-beta.1" + } + + It "Should handle URL with query parameters - non-versioned" { + $inputUrl = [System.Uri]"https://www.npmjs.com/package/@azure/ai-agents?activeTab=readme" + $apiUrl = Get-TransformedNpmUrl $inputUrl + $apiUrl | Should -Be "https://registry.npmjs.org/@azure/ai-agents" + } + + It "Should handle URL with fragments - non-versioned" { + $inputUrl = [System.Uri]"https://www.npmjs.com/package/@azure/ai-agents#installation" + $apiUrl = Get-TransformedNpmUrl $inputUrl + $apiUrl | Should -Be "https://registry.npmjs.org/@azure/ai-agents" + } + + It "Should handle URL with version and query parameters" { + $inputUrl = [System.Uri]"https://www.npmjs.com/package/@azure/ai-agents/v/1.1.0?activeTab=versions" + $apiUrl = Get-TransformedNpmUrl $inputUrl + $apiUrl | Should -Be "https://registry.npmjs.org/@azure/ai-agents/1.1.0" + } + + It "Should handle URL with trailing slash - non-versioned" { + $inputUrl = [System.Uri]"https://www.npmjs.com/package/@azure/ai-agents/" + $apiUrl = Get-TransformedNpmUrl $inputUrl + $apiUrl | Should -Be "https://registry.npmjs.org/@azure/ai-agents/" + } +} diff --git a/eng/common/scripts/Verify-Links.ps1 b/eng/common/scripts/Verify-Links.ps1 index d0ac61d7954..0eb1798da6c 100644 --- a/eng/common/scripts/Verify-Links.ps1 +++ b/eng/common/scripts/Verify-Links.ps1 @@ -163,7 +163,24 @@ function ProcessCratesIoLink([System.Uri]$linkUri, $path) { function ProcessNpmLink([System.Uri]$linkUri) { # npmjs.com started using Cloudflare which returns 403 and we need to instead check the registry api for existence checks # https://github.com/orgs/community/discussions/174098#discussioncomment-14461226 - $apiUrl = $linkUri.ToString() -replace '^https?://(?:www\.)?npmjs\.com/package/(.*)/v', 'https://registry.npmjs.org/$1' + + # Handle versioned URLs: https://www.npmjs.com/package/@azure/ai-agents/v/1.1.0 -> https://registry.npmjs.org/@azure/ai-agents/1.1.0 + # Handle non-versioned URLs: https://www.npmjs.com/package/@azure/ai-agents -> https://registry.npmjs.org/@azure/ai-agents + # The regex captures the package name (which may contain a slash for scoped packages) and optionally the version. + # Query parameters and URL fragments are excluded from the transformation. + $urlString = $linkUri.ToString() + if ($urlString -match '^https?://(?:www\.)?npmjs\.com/package/([^?#]+)/v/([^?#]+)') { + # Versioned URL: remove the /v/ segment but keep the version + $apiUrl = "https://registry.npmjs.org/$($matches[1])/$($matches[2])" + } + elseif ($urlString -match '^https?://(?:www\.)?npmjs\.com/package/([^?#]+)') { + # Non-versioned URL: just replace the domain + $apiUrl = "https://registry.npmjs.org/$($matches[1])" + } + else { + # Fallback: use the original URL if it doesn't match expected patterns + $apiUrl = $urlString + } return ProcessStandardLink ([System.Uri]$apiUrl) }