From 5c866dcb1860a51d77a23f6909ed4a485c0dc0fe Mon Sep 17 00:00:00 2001 From: Cptn Fizzbin Date: Wed, 23 Apr 2025 11:18:02 -0400 Subject: [PATCH] add tests for passing a user token Signed-off-by: Cptn Fizzbin --- .../urlReader/lib/GitlabUrlReader.test.ts | 287 ++++++++++++------ 1 file changed, 200 insertions(+), 87 deletions(-) diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts index 6274ba8dc3..b7ccf165ad 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts @@ -26,7 +26,7 @@ import { setupServer } from 'msw/node'; import path from 'path'; import { GitlabUrlReader } from './GitlabUrlReader'; import { DefaultReadTreeResponseFactory } from './tree'; -import { NotModifiedError, NotFoundError } from '@backstage/errors'; +import { NotFoundError, NotModifiedError } from '@backstage/errors'; import { GitLabIntegration, readGitLabIntegrationConfig, @@ -248,6 +248,29 @@ describe('GitlabUrlReader', () => { const content = await result.buffer(); expect(content.toString()).toBe('foo'); }); + + it('should return the file when using a user token', async () => { + worker.use( + rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => { + if (req.headers.get('private-token') !== 'gl-user-token') { + return res( + ctx.status(403), + ctx.json({ message: 'Not Authorized' }), + ); + } + return res(ctx.status(200), ctx.json({ id: 12345 })); + }), + rest.get('*', (_req, res, ctx) => { + return res(ctx.status(200), ctx.body('foo')); + }), + ); + const result = await reader.readUrl( + 'https://gitlab.com/user/project/-/blob/branch/my/path/to/file.yaml', + { token: 'gl-user-token' }, + ); + const content = await result.buffer(); + expect(content.toString()).toBe('foo'); + }); }); describe('readTree', () => { @@ -276,102 +299,103 @@ describe('GitlabUrlReader', () => { id: 'sha456def', }, ]; - }); - beforeEach(() => { - worker.use( - rest.get( - 'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/archive', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/zip'), - ctx.set( - 'content-disposition', - 'attachment; filename="mock-main-sha123abc.zip"', + const projectNames = ['backstage%2Fmock', 'user%2Fproject']; + projectNames.forEach(projectName => { + worker.use( + rest.get( + `https://gitlab.com/api/v4/projects/${projectName}/repository/archive`, + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/zip'), + ctx.set( + 'content-disposition', + 'attachment; filename="mock-main-sha123abc.zip"', + ), + ctx.body(archiveBuffer), ), - ctx.body(archiveBuffer), - ), - ), - rest.get( - 'https://gitlab.com/api/v4/projects/backstage%2Fmock', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json(projectGitlabApiResponse), - ), - ), - rest.get( - 'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/commits', - (req, res, ctx) => { - const refName = req.url.searchParams.get('ref_name'); - if (refName === 'main') { - const filepath = req.url.searchParams.get('path'); - if (filepath === 'testFilepath') { + ), + rest.get( + `https://gitlab.com/api/v4/projects/${projectName}`, + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(projectGitlabApiResponse), + ), + ), + rest.get( + `https://gitlab.com/api/v4/projects/${projectName}/repository/commits`, + (req, res, ctx) => { + const refName = req.url.searchParams.get('ref_name'); + if (refName === 'main') { + const filepath = req.url.searchParams.get('path'); + if (filepath === 'testFilepath') { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(specificPathCommitsGitlabApiResponse), + ); + } return res( ctx.status(200), ctx.set('Content-Type', 'application/json'), - ctx.json(specificPathCommitsGitlabApiResponse), + ctx.json(commitsGitlabApiResponse), ); } - return res( + if (refName === 'branchDoesNotExist') { + return res(ctx.status(404)); + } + return res(); + }, + ), + rest.get( + `https://gitlab.mycompany.com/api/v4/projects/${projectName}`, + (_, res, ctx) => + res( ctx.status(200), ctx.set('Content-Type', 'application/json'), - ctx.json(commitsGitlabApiResponse), - ); - } - if (refName === 'branchDoesNotExist') { - return res(ctx.status(404)); - } - return res(); - }, - ), - rest.get( - 'https://gitlab.mycompany.com/api/v4/projects/backstage%2Fmock', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json(projectGitlabApiResponse), - ), - ), - rest.get( - 'https://gitlab.mycompany.com/api/v4/projects/backstage%2Fmock/repository/commits', - (req, res, ctx) => { - const refName = req.url.searchParams.get('ref_name'); - if (refName === 'main') { - const filepath = req.url.searchParams.get('path'); - if (filepath === 'testFilepath') { + ctx.json(projectGitlabApiResponse), + ), + ), + rest.get( + `https://gitlab.mycompany.com/api/v4/projects/${projectName}/repository/commits`, + (req, res, ctx) => { + const refName = req.url.searchParams.get('ref_name'); + if (refName === 'main') { + const filepath = req.url.searchParams.get('path'); + if (filepath === 'testFilepath') { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(specificPathCommitsGitlabApiResponse), + ); + } return res( ctx.status(200), ctx.set('Content-Type', 'application/json'), - ctx.json(specificPathCommitsGitlabApiResponse), + ctx.json(commitsGitlabApiResponse), ); } - return res( + return res(); + }, + ), + rest.get( + `https://gitlab.mycompany.com/api/v4/projects/${projectName}/repository/archive`, + (_, res, ctx) => + res( ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json(commitsGitlabApiResponse), - ); - } - return res(); - }, - ), - rest.get( - 'https://gitlab.mycompany.com/api/v4/projects/backstage%2Fmock/repository/archive', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/zip'), - ctx.set( - 'content-disposition', - 'attachment; filename="mock-main-sha123abc.zip"', + ctx.set('Content-Type', 'application/zip'), + ctx.set( + 'content-disposition', + 'attachment; filename="mock-main-sha123abc.zip"', + ), + ctx.body(archiveBuffer), ), - ctx.body(archiveBuffer), - ), - ), - ); + ), + ); + }); }); it('returns the wanted files from an archive', async () => { @@ -543,6 +567,28 @@ describe('GitlabUrlReader', () => { expect(mkDocsFile.toString()).toBe('site_name: Test\n'); expect(indexMarkdownFile.toString()).toBe('# Test\n'); }); + + it('should return the file when using a user token', async () => { + worker.use( + rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => { + if (req.headers.get('private-token') !== 'gl-user-token') { + return res( + ctx.status(403), + ctx.json({ message: 'Not Authorized' }), + ); + } + return res(ctx.status(200), ctx.json({ id: 12345 })); + }), + ); + + const response = await gitlabProcessor.readTree( + 'https://gitlab.com/user/project/tree/main', + { token: 'gl-user-token' }, + ); + + const files = await response.files(); + expect(files.length).toBe(2); + }); }); describe('search', () => { @@ -690,6 +736,15 @@ describe('GitlabUrlReader', () => { '*/api/v4/projects/group%2Fsubgroup%2Fproject', (_, res, ctx) => res(ctx.status(200), ctx.json({ id: 12345 })), ), + rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => { + if (req.headers.get('private-token') !== 'gl-user-token') { + return res( + ctx.status(403), + ctx.json({ message: 'Not Authorized' }), + ); + } + return res(ctx.status(200), ctx.json({ id: 12345 })); + }), ); }); it('should fall back to getGitLabFileFetchUrl for blob urls', async () => { @@ -719,6 +774,16 @@ describe('GitlabUrlReader', () => { 'Failed converting /some/random/endpoint to a project id. Url path must include /blob/.', ); }); + it('should resolve the project path using a user token', async () => { + await expect( + (gitlabProcessor as any).getGitlabFetchUrl( + 'https://gitlab.com/user/project/-/blob/branch/my/path/to/file.yaml', + 'gl-user-token', + ), + ).resolves.toEqual( + 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch', + ); + }); }); describe('getGitlabArtifactFetchUrl', () => { @@ -728,12 +793,19 @@ describe('GitlabUrlReader', () => { '*/api/v4/projects/group%2Fsubgroup%2Fproject', (_, res, ctx) => res(ctx.status(200), ctx.json({ id: 12345 })), ), - ); - worker.use( rest.get( '*/api/v4/projects/groupA%2Fsubgroup%2Fproject', (_, res, ctx) => res(ctx.status(404)), ), + rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => { + if (req.headers.get('private-token') !== 'gl-user-token') { + return res( + ctx.status(403), + ctx.json({ message: 'Not Authorized' }), + ); + } + return res(ctx.status(200), ctx.json({ id: 12345 })); + }), ); }); it('should reject urls that are not for the job artifacts API', async () => { @@ -765,20 +837,61 @@ describe('GitlabUrlReader', () => { ), ).rejects.toThrow(/^Unable to translate GitLab artifact URL:/); }); + it('should resolve the project path using a user token', async () => { + await expect( + (gitlabProcessor as any).getGitlabArtifactFetchUrl( + new URL( + 'https://gitlab.com/user/project/-/jobs/artifacts/branch/raw/my/path/to/file.yaml?job=myJob', + ), + 'gl-user-token', + ), + ).resolves.toEqual( + new URL( + 'https://gitlab.com/api/v4/projects/12345/jobs/artifacts/branch/raw/my/path/to/file.yaml?job=myJob', + ), + ); + }); }); describe('resolveProjectToId', () => { - it('should resolve the project path to a valid project id', async () => { + beforeEach(() => { worker.use( - rest.get('*/api/v4/projects/some%2Fproject', (req, res, ctx) => { + rest.get('*/api/v4/projects/group%2Fproject', (req, res, ctx) => { // the private-token header must be included on API calls - expect(req.headers.get('private-token')).toBe('gl-dummy-token'); + if (req.headers.get('private-token') !== 'gl-dummy-token') { + return res( + ctx.status(403), + ctx.json({ message: 'Not Authorized' }), + ); + } + return res(ctx.status(200), ctx.json({ id: 12345 })); + }), + rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => { + // the private-token header must be included on API calls + if (req.headers.get('private-token') !== 'gl-user-token') { + return res( + ctx.status(403), + ctx.json({ message: 'Not Authorized' }), + ); + } return res(ctx.status(200), ctx.json({ id: 12345 })); }), ); + }); + + it('should resolve the project path to a valid project id', async () => { await expect( (gitlabProcessor as any).resolveProjectToId( - new URL('https://gitlab.com/some/project'), + new URL('https://gitlab.com/group/project'), + ), + ).resolves.toEqual(12345); + }); + + it('should resolve the project path to a valid project id using a user token', async () => { + await expect( + (gitlabProcessor as any).resolveProjectToId( + new URL('https://gitlab.com/user/project'), + 'gl-user-token', ), ).resolves.toEqual(12345); });