From 58c58420d0c1516c1b3758361e8cceb15875b105 Mon Sep 17 00:00:00 2001 From: Thibault Friedrich Date: Fri, 15 Mar 2024 11:55:54 -0400 Subject: [PATCH 1/2] feat: add gitlab provider --- src/features/readme/generateReadme.ts | 2 + src/services/__mocks__/mockFetch.ts | 4 +- src/services/git/__tests__/gitlab.test.ts | 126 ++++++++++++++++++++++ src/services/git/gitlab.ts | 71 ++++++++++++ src/services/git/index.ts | 3 +- 5 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 src/services/git/__tests__/gitlab.test.ts create mode 100644 src/services/git/gitlab.ts diff --git a/src/features/readme/generateReadme.ts b/src/features/readme/generateReadme.ts index b7cb342..e7a1045 100644 --- a/src/features/readme/generateReadme.ts +++ b/src/features/readme/generateReadme.ts @@ -124,7 +124,9 @@ ${installCommand} # Install dependencies${ fs.writeFile(readmeFilename, readmeContent) + // eslint-disable-next-line no-console console.log() + // eslint-disable-next-line no-console console.log(readmeContent) if (created) { diff --git a/src/services/__mocks__/mockFetch.ts b/src/services/__mocks__/mockFetch.ts index 6357067..4c06713 100644 --- a/src/services/__mocks__/mockFetch.ts +++ b/src/services/__mocks__/mockFetch.ts @@ -1,10 +1,10 @@ -const mockFetch = (url: string, status: number, data: object = {}) => { +const mockFetch = (referenceUrl: string, status: number, data: object = {}) => { jest.spyOn(global, 'fetch').mockImplementationOnce( jest.fn((url: string) => Promise.resolve({ status, json: () => { - if (url === url) { + if (url === referenceUrl) { return Promise.resolve(data) } return Promise.resolve({ data: 100 }) diff --git a/src/services/git/__tests__/gitlab.test.ts b/src/services/git/__tests__/gitlab.test.ts new file mode 100644 index 0000000..211086a --- /dev/null +++ b/src/services/git/__tests__/gitlab.test.ts @@ -0,0 +1,126 @@ +import mockOrganization from '../../../types/__mocks__/mockOrganization' +import mockRepository from '../../../types/__mocks__/mockRepository' +import mockFetch from '../../__mocks__/mockFetch' +import gitlab from '../gitlab' + +describe('gitlab', () => { + describe('isProvider', () => { + it('should always return false', () => { + const provider = gitlab.isProvider('foo') + + expect(provider).toEqual(false) + }) + + it('should always return truen with gitlab ssh url', () => { + const sshUrl = 'git@gitlab.com:friedrith/contributing-generator.git' + + const provider = gitlab.isProvider(sshUrl) + + expect(provider).toEqual(true) + }) + + it('should always return truen with gitlab https url', () => { + const httpsUrl = 'https://gitlab.com/friedrith/contributing-generator.git' + + const provider = gitlab.isProvider(httpsUrl) + + expect(provider).toEqual(true) + }) + }) + + describe('getProviderName', () => { + it('should return gitlab', () => { + const providerName = gitlab.getProviderName() + + expect(providerName).toEqual('gitlab') + }) + }) + + describe('findOrganization', () => { + it('should return default organization when 404', async () => { + mockFetch('https://gitlab.com/api/v4/users?username=friedrith', 404) + const url = 'git@gitlab.com:friedrith/contributing-generator.git' + + const organization = await gitlab.findOrganization(url) + + expect(organization).toEqual({ + username: 'friedrith', + name: 'friedrith', + email: undefined, + }) + }) + + it('should return organization with ssh url', async () => { + mockFetch('https://gitlab.com/api/v4/users?username=friedrith', 200, [ + { + name: 'Thibault Friedrich', + email: null, + }, + ]) + + const url = 'git@gitlab.com:friedrith/contributing-generator.git' + + const organization = await gitlab.findOrganization(url) + + expect(organization).toEqual({ + username: 'friedrith', + name: 'Thibault Friedrich', + email: null, + }) + }) + + it('should return organization with http url', async () => { + mockFetch('https://gitlab.com/api/v4/users?username=friedrith', 200, [ + { + name: 'Thibault Friedrich', + email: null, + }, + ]) + + const url = 'https://gitlab.com/friedrith/contributing-generator.git' + + const organization = await gitlab.findOrganization(url) + + expect(organization).toEqual({ + username: 'friedrith', + name: 'Thibault Friedrich', + email: null, + }) + }) + }) + + describe('getIssueTrackerUrl', () => { + it('should return issue tracker url', async () => { + const organization = mockOrganization({ username: 'foo' }) + const repository = mockRepository({ name: 'bar' }) + const url = await gitlab.getIssueTrackerUrl(organization, repository) + + expect(url).toEqual('https://gitlab.com/foo/bar/-/issues') + }) + }) + + describe('getRepositoryInformation', () => { + it('should return empty string when 404', async () => { + mockFetch('https://gitlab.com/api/v4/users', 404) + + const username = 'username' + const name = 'repo' + const repository = await gitlab.getRepositoryInformation(username, name) + + expect(repository).toEqual({}) + }) + + it('should return empty string when 404', async () => { + mockFetch('https://api.gitlab.com/repos/username/repo', 200, { + description: 'foo', + topics: ['bar', 'baz'], + }) + + const username = 'username' + const name = 'repo' + const repository = await gitlab.getRepositoryInformation(username, name) + + expect(repository).toEqual({}) + }) + }) +}) diff --git a/src/services/git/gitlab.ts b/src/services/git/gitlab.ts new file mode 100644 index 0000000..c8b37ad --- /dev/null +++ b/src/services/git/gitlab.ts @@ -0,0 +1,71 @@ +import Organization from '../../types/Organization' +import Project from '../../types/Project' +import Repository from '../../types/Repository' +import GitProvider from './types/GitProvider' + +const getGitlabUser = async (username: string) => { + try { + const url = new URL('https://gitlab.com/api/v4/users') + url.searchParams.append('username', username) + + const response = await fetch(url.toString()) + + if (response.status === 404) { + return {} + } + + const user: { name: string; email: string } = (await response.json())[0] + + return { + name: user.name, + email: user.email, + } + } catch (error) { + return {} + } +} + +const HTTP_HOST = 'https://gitlab.com' + +const isProvider = (url: string) => + url.startsWith('git@gitlab.com:') || url.startsWith(HTTP_HOST) + +const getProviderName = () => 'gitlab' + +const getIssueTrackerUrl = async ( + organization: Organization, + repository: Repository, +) => `https://gitlab.com/${organization.username}/${repository.name}/-/issues` + +const findOrganization = async (url: string): Promise => { + const username = url.startsWith(HTTP_HOST) + ? url.replace(HTTP_HOST, '').split('/')[1] + : url.split(':')[1].split('/')[0] + + const user = await getGitlabUser(username) + + return { + username, + name: user?.name ?? username, + email: user?.email, + } +} + +const getRepositoryInformation = async ( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _username: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _name: string, +): Promise> => { + // it seems gitlab doesn't provide any description or keywords + // https://docs.gitlab.com/ee/api/repositories.html + return {} +} + +export default { + isProvider, + getProviderName, + findOrganization, + getIssueTrackerUrl, + getRepositoryInformation, +} satisfies GitProvider diff --git a/src/services/git/index.ts b/src/services/git/index.ts index 1b0b5f9..4c05211 100644 --- a/src/services/git/index.ts +++ b/src/services/git/index.ts @@ -7,6 +7,7 @@ import Repository from '../../types/Repository' import Project from '../../types/Project' import GitProvider from './types/GitProvider' import defaultProvider from './defaultProvider' +import gitlab from './gitlab' const executeCommand = async (command: string, args: string[]) => (await util.promisify(execFile)(command, args)).stdout.replace('\n', '') @@ -23,7 +24,7 @@ export const findEmail = async () => export const findName = async () => executeCommand('git', ['config', 'user.name']) -const providers = [github, defaultProvider] +const providers = [github, gitlab, defaultProvider] const findProvider = (url: string): GitProvider => providers.find(provider => provider.isProvider(url)) From be8ed827f808fc323e960e4b6e26b7dcf7a433d7 Mon Sep 17 00:00:00 2001 From: Thibault Friedrich Date: Fri, 15 Mar 2024 12:55:15 -0400 Subject: [PATCH 2/2] test: add unit test --- src/services/git/__tests__/index.test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/services/git/__tests__/index.test.ts b/src/services/git/__tests__/index.test.ts index 2bd6186..c99eb8c 100644 --- a/src/services/git/__tests__/index.test.ts +++ b/src/services/git/__tests__/index.test.ts @@ -19,6 +19,24 @@ describe('git', () => { }) }) + it('should return gitlab organization', async () => { + mockFetch('https://gitlab.com/api/v4/users?username=friedrith', 200, [ + { + name: 'Thibault Friedrich', + email: null, + }, + ]) + const url = 'git@gitlab.com:friedrith/contributing-generator.git' + + const organization = await git.findOrganization(url) + + expect(organization).toEqual({ + email: null, + name: 'Thibault Friedrich', + username: 'friedrith', + }) + }) + it('should return default organization', async () => { mockFetch('https://api.github.com/users/friedrith', 200, { name: 'Thibault Friedrich',