diff --git a/.eslintignore b/.eslintignore index 2d57eb5d..32e61d16 100644 --- a/.eslintignore +++ b/.eslintignore @@ -13,4 +13,5 @@ distribution.js pipBuild.js tests.js testUtils.js -docker.js \ No newline at end of file +docker.js +utilsTests.js \ No newline at end of file diff --git a/jfrog-tasks-utils/utils.d.ts b/jfrog-tasks-utils/utils.d.ts index 6fa0318c..e1c4892f 100644 --- a/jfrog-tasks-utils/utils.d.ts +++ b/jfrog-tasks-utils/utils.d.ts @@ -52,5 +52,6 @@ declare module '@jfrog/tasks-utils' { cliPath: string, buildDir: string, ): string; + export function parsePlatformUrlFromServiceUrl(serviceUrl: string): string; export { taskSelectedCliVersionEnv }; } diff --git a/jfrog-tasks-utils/utils.js b/jfrog-tasks-utils/utils.js index ac314498..acc2cfb8 100644 --- a/jfrog-tasks-utils/utils.js +++ b/jfrog-tasks-utils/utils.js @@ -32,7 +32,7 @@ const minCustomCliVersion = '2.10.0'; const minSupportedStdinSecretCliVersion = '2.36.0'; const minSupportedServerIdEnvCliVersion = '2.37.0'; const minSupportedOidcCliVersion = '2.75.0'; -const pluginVersion = '2.13.1'; +const pluginVersion = '2.12.5'; const buildAgent = 'jfrog-azure-devops-extension'; /** @@ -118,6 +118,7 @@ module.exports = { jfrogCliToolName: jfrogCliToolName, isServerIdEnvSupported: isServerIdEnvSupported, setJdkHomeForJavaTasks: setJdkHomeForJavaTasks, + parsePlatformUrlFromServiceUrl: parsePlatformUrlFromServiceUrl, }; /** @@ -437,7 +438,15 @@ function configureSpecificCliServer(service, urlFlag, serverId, cliPath, buildDi // username and access token params for further use by the users. if (oidcProviderName) { // we need platform url for oidc token exchange - let platformUrl = tl.getEndpointAuthorizationParameter(service, 'jfrogPlatformUrl', true); + let platformUrl = ""; + try { + platformUrl = tl.getEndpointAuthorizationParameter(service, 'jfrogPlatformUrl', true); + } catch (error) { + console.warn('Failed to get platform url from field: ' + error+"\nparsing from url instead"); + } + if (!platformUrl || !platformUrl.trim()) { + platformUrl = parsePlatformUrlFromServiceUrl(serviceUrl); + } serviceAccessToken = exchangeOidcTokenAndSetStepVariables(service, platformUrl, oidcProviderName, cliPath, buildDir); } @@ -953,6 +962,26 @@ function stripTrailingSlash(str) { return str.endsWith('/') ? str.slice(0, -1) : str; } +/** + * Parse platform URL from a service URL by stripping service-specific suffixes. + * Handles URLs ending with /xray, /artifactory, or /distribution (case-insensitive). + * + * @param serviceUrl - The service URL to parse (e.g., 'https://example.jfrog.io/artifactory') + * @returns The platform URL with the service suffix removed + */ +function parsePlatformUrlFromServiceUrl(serviceUrl) { + const suffixes = ['/xray', '/artifactory', '/distribution']; + const url = new URL(serviceUrl); + for (const suffix of suffixes) { + if (url.pathname.endsWith(suffix)) { + url.pathname = url.pathname.replace(suffix, ''); + break; + } + } + // Remove trailing slash + return url.toString().replace(/\/$/, ''); +} + /** * Determines the required working directory for running the cli. * Decision is based on the default path to run, and the provided path by the user. diff --git a/tests/.gitignore b/tests/.gitignore index 1ab5b7ea..de999644 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -2,6 +2,11 @@ tests.js.map tests.d.ts tests.js + testUtils.js.map testUtils.d.ts -testUtils.js \ No newline at end of file +testUtils.js + +utilsTests.js.map +utilsTests.d.ts +utilsTests.js \ No newline at end of file diff --git a/tests/utilsTests.ts b/tests/utilsTests.ts new file mode 100644 index 00000000..44b5e5c4 --- /dev/null +++ b/tests/utilsTests.ts @@ -0,0 +1,162 @@ +/// +import * as assert from 'assert'; + +// Use require to get the actual module with latest exports +import * as jfrogUtils from '@jfrog/tasks-utils'; + +/** + * Simulates the platformUrl resolution logic from utils.js lines 441-449: + * + * let platformUrl = ""; + * try { + * platformUrl = tl.getEndpointAuthorizationParameter(service, 'jfrogPlatformUrl', true); + * } catch (error) { + * console.warn('Failed to get platform url from field: ' + error + "\nparsing from url instead"); + * } + * if (!platformUrl || !platformUrl.trim()) { + * platformUrl = parsePlatformUrlFromServiceUrl(serviceUrl); + * } + * + * @param getEndpointResult - Simulated result from tl.getEndpointAuthorizationParameter + * @param shouldThrow - Whether the call should throw an error + * @param serviceUrl - The service URL to parse from if platformUrl is not available + * @returns The resolved platform URL + */ +function simulatePlatformUrlResolution( + getEndpointResult: string | undefined | null, + shouldThrow: boolean, + serviceUrl: string, +): string { + let platformUrl: string | undefined | null = ''; + try { + if (shouldThrow) { + throw new Error('Simulated error'); + } + platformUrl = getEndpointResult; + } catch { + // Simulates: console.warn('Failed to get platform url from field...') + } + if (!platformUrl || !platformUrl.trim()) { + platformUrl = jfrogUtils.parsePlatformUrlFromServiceUrl(serviceUrl); + } + return platformUrl; +} + +describe('Utils Unit Tests', (): void => { + describe('platformUrl resolution (simulating tl.getEndpointAuthorizationParameter)', (): void => { + const serviceUrl: string = 'https://example.jfrog.io/artifactory'; + + it('should use platformUrl directly when getEndpointAuthorizationParameter returns valid URL', (): void => { + const result: string = simulatePlatformUrlResolution('https://my-platform.jfrog.io', false, serviceUrl); + assert.strictEqual(result, 'https://my-platform.jfrog.io'); + }); + + it('should parse from serviceUrl when getEndpointAuthorizationParameter returns empty string', (): void => { + const result: string = simulatePlatformUrlResolution('', false, serviceUrl); + assert.strictEqual(result, 'https://example.jfrog.io'); + }); + + it('should parse from serviceUrl when getEndpointAuthorizationParameter returns undefined', (): void => { + const result: string = simulatePlatformUrlResolution(undefined, false, serviceUrl); + assert.strictEqual(result, 'https://example.jfrog.io'); + }); + + it('should parse from serviceUrl when getEndpointAuthorizationParameter returns null', (): void => { + const result: string = simulatePlatformUrlResolution(null, false, serviceUrl); + assert.strictEqual(result, 'https://example.jfrog.io'); + }); + + it('should parse from serviceUrl when getEndpointAuthorizationParameter returns whitespace only', (): void => { + const result: string = simulatePlatformUrlResolution(' ', false, serviceUrl); + assert.strictEqual(result, 'https://example.jfrog.io'); + }); + + it('should parse from serviceUrl when getEndpointAuthorizationParameter throws error', (): void => { + const result: string = simulatePlatformUrlResolution('', true, serviceUrl); + assert.strictEqual(result, 'https://example.jfrog.io'); + }); + + it('should parse from xray serviceUrl when platformUrl not available', (): void => { + const result: string = simulatePlatformUrlResolution('', false, 'https://example.jfrog.io/xray'); + assert.strictEqual(result, 'https://example.jfrog.io'); + }); + + it('should parse from distribution serviceUrl when platformUrl not available', (): void => { + const result: string = simulatePlatformUrlResolution('', false, 'https://example.jfrog.io/distribution'); + assert.strictEqual(result, 'https://example.jfrog.io'); + }); + }); + + describe('parsePlatformUrlFromServiceUrl', (): void => { + it('should strip /artifactory suffix', (): void => { + assert.strictEqual( + jfrogUtils.parsePlatformUrlFromServiceUrl('https://example.jfrog.io/artifactory'), + 'https://example.jfrog.io', + ); + }); + + it('should strip /xray suffix', (): void => { + assert.strictEqual( + jfrogUtils.parsePlatformUrlFromServiceUrl('https://example.jfrog.io/xray'), + 'https://example.jfrog.io', + ); + }); + + it('should strip /distribution suffix', (): void => { + assert.strictEqual( + jfrogUtils.parsePlatformUrlFromServiceUrl('https://example.jfrog.io/distribution'), + 'https://example.jfrog.io', + ); + }); + + it('should handle case-insensitive matching for /Artifactory', (): void => { + assert.strictEqual( + jfrogUtils.parsePlatformUrlFromServiceUrl('https://example.jfrog.io/Artifactory'), + 'https://example.jfrog.io', + ); + }); + + it('should handle case-insensitive matching for /XRAY', (): void => { + assert.strictEqual( + jfrogUtils.parsePlatformUrlFromServiceUrl('https://example.jfrog.io/XRAY'), + 'https://example.jfrog.io', + ); + }); + + it('should return URL as-is when no known suffix', (): void => { + assert.strictEqual( + jfrogUtils.parsePlatformUrlFromServiceUrl('https://example.jfrog.io/other'), + 'https://example.jfrog.io/other', + ); + }); + + it('should handle URL with port', (): void => { + assert.strictEqual( + jfrogUtils.parsePlatformUrlFromServiceUrl('https://example.jfrog.io:8080/artifactory'), + 'https://example.jfrog.io:8080', + ); + }); + + it('should handle URL with trailing slash on suffix', (): void => { + assert.strictEqual( + jfrogUtils.parsePlatformUrlFromServiceUrl('https://example.jfrog.io/artifactory/'), + 'https://example.jfrog.io', + ); + }); + + it('should handle URL with path prefix', (): void => { + assert.strictEqual( + jfrogUtils.parsePlatformUrlFromServiceUrl('https://example.com/jfrog/artifactory'), + 'https://example.com/jfrog', + ); + }); + + it('should handle URL without any prefix', (): void => { + assert.strictEqual(jfrogUtils.parsePlatformUrlFromServiceUrl('https://example.com/jfrog'), 'https://example.com/jfrog'); + }); + + it('should handle hostnames as well', (): void => { + assert.strictEqual(jfrogUtils.parsePlatformUrlFromServiceUrl('https://artifactory.com/jfrog/artifactory'), 'https://artifactory.com/jfrog'); + }); + }); +});