Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions src/helpers.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* Comprehensive tests for all helper functions in the Lagoon CLI Wrapper
*/
import { gitUrlToGithubUrl, extractPrNumber } from './lagoon-api.mjs';

describe('gitUrlToGithubUrl', () => {
test('should handle empty input', () => {
expect(gitUrlToGithubUrl('')).toBeNull();
});

test('should handle null input', () => {
expect(gitUrlToGithubUrl(null)).toBeNull();
});

test('should handle undefined input', () => {
expect(gitUrlToGithubUrl(undefined)).toBeNull();
});

test('should convert SSH format with username', () => {
expect(gitUrlToGithubUrl('git@github.com:username/repo.git')).toBe('https://github.com/username/repo');
});

test('should convert SSH format with organization', () => {
expect(gitUrlToGithubUrl('git@github.com:org-name/repo-name.git')).toBe('https://github.com/org-name/repo-name');
});

test('should handle HTTPS format correctly', () => {
expect(gitUrlToGithubUrl('https://github.com/user/repo.git')).toBe('https://github.com/user/repo');
});

test('should handle HTTPS format without .git suffix', () => {
expect(gitUrlToGithubUrl('https://github.com/user/repo')).toBe('https://github.com/user/repo');
});

test('should return null for non-GitHub URLs', () => {
expect(gitUrlToGithubUrl('https://gitlab.com/user/repo.git')).toBeNull();
expect(gitUrlToGithubUrl('https://bitbucket.org/user/repo.git')).toBeNull();
expect(gitUrlToGithubUrl('git@gitlab.com:user/repo.git')).toBeNull();
expect(gitUrlToGithubUrl('git@ssh.dev.azure.com:v3/org/project/repo')).toBeNull();
});

test('should handle repo names with dots', () => {
expect(gitUrlToGithubUrl('git@github.com:user/repo.name.git')).toBe('https://github.com/user/repo.name');
});

test('should handle repo names with hyphens', () => {
expect(gitUrlToGithubUrl('git@github.com:user/repo-name.git')).toBe('https://github.com/user/repo-name');
});

test('should handle repo paths with slashes', () => {
expect(gitUrlToGithubUrl('https://github.com/org/project/repo.git')).toBe('https://github.com/org/project/repo');
});
});

describe('extractPrNumber', () => {
test('should handle empty input', () => {
expect(extractPrNumber('')).toBeNull();
});

test('should handle null input', () => {
expect(extractPrNumber(null)).toBeNull();
});

test('should handle undefined input', () => {
expect(extractPrNumber(undefined)).toBeNull();
});

test('should extract simple PR numbers', () => {
expect(extractPrNumber('pr-1')).toBe('1');
expect(extractPrNumber('pr-42')).toBe('42');
expect(extractPrNumber('pr-999')).toBe('999');
});

test('should be case insensitive', () => {
expect(extractPrNumber('PR-123')).toBe('123');
expect(extractPrNumber('Pr-123')).toBe('123');
expect(extractPrNumber('pR-123')).toBe('123');
});

test('should return null for non-PR environment names', () => {
expect(extractPrNumber('master')).toBeNull();
expect(extractPrNumber('develop')).toBeNull();
expect(extractPrNumber('feature/branch')).toBeNull();
expect(extractPrNumber('release-1.0')).toBeNull();
});

test('should not match PR- in the middle of a string', () => {
expect(extractPrNumber('feature-pr-123')).toBeNull();
});

test('should not match without a hyphen', () => {
expect(extractPrNumber('pr123')).toBeNull();
});

test('should handle PR numbers of varying lengths', () => {
expect(extractPrNumber('pr-1')).toBe('1');
expect(extractPrNumber('pr-12')).toBe('12');
expect(extractPrNumber('pr-123')).toBe('123');
expect(extractPrNumber('pr-1234')).toBe('1234');
expect(extractPrNumber('pr-12345')).toBe('12345');
});
});
10 changes: 10 additions & 0 deletions src/lagoon-api.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,11 @@ export async function generateLoginLink(instance, project, environment) {
* @returns {string|null} The normalized GitHub HTTPS URL, or `null` if the input is not a GitHub URL.
*/
export function gitUrlToGithubUrl(gitUrl) {
// Handle null, undefined, or empty strings
if (!gitUrl) {
return null;
}

// Handle SSH URLs like git@github.com:org/repo.git
if (gitUrl.startsWith('git@github.com:')) {
const path = gitUrl.replace('git@github.com:', '').replace('.git', '');
Expand All @@ -254,6 +259,11 @@ export function gitUrlToGithubUrl(gitUrl) {
* @returns {string|null} PR number or null if not found.
*/
export function extractPrNumber(environmentName) {
// Handle null, undefined, or empty strings
if (!environmentName) {
return null;
}

const match = environmentName.match(/^pr-(\d+)$/i);
return match ? match[1] : null;
}
Expand Down
107 changes: 107 additions & 0 deletions src/lagoon-api.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { jest } from '@jest/globals';
import {
gitUrlToGithubUrl,
extractPrNumber,
getLagoonInstances,
getProjectsWithDetails,
getEnvironments,
getUsers,
clearDrupalCache,
generateLoginLink,
deleteEnvironment,
deployBranch,
getGitBranches
} from './lagoon-api.mjs';

// Mock the execCommand function
jest.unstable_mockModule('./command/index.mjs', () => ({
LagoonCommand: jest.fn().mockImplementation(() => ({
withInstance: jest.fn().mockReturnThis(),
withProject: jest.fn().mockReturnThis(),
withEnvironment: jest.fn().mockReturnThis(),
withJsonOutput: jest.fn().mockReturnThis(),
withForce: jest.fn().mockReturnThis(),
listConfigs: jest.fn().mockReturnThis(),
listProjects: jest.fn().mockReturnThis(),
listEnvironments: jest.fn().mockReturnThis(),
listUsers: jest.fn().mockReturnThis(),
deleteEnvironment: jest.fn().mockReturnThis(),
deployBranch: jest.fn().mockReturnThis(),
login: jest.fn().mockReturnThis(),
ssh: jest.fn().mockReturnThis(),
getArgs: jest.fn().mockReturnValue([]),
getBaseCommand: jest.fn().mockReturnValue('lagoon'),
getCommandArray: jest.fn().mockReturnValue(['lagoon']),
toString: jest.fn().mockReturnValue('lagoon')
})),
GitCommand: jest.fn().mockImplementation(() => ({
lsRemote: jest.fn().mockReturnThis(),
getArgs: jest.fn().mockReturnValue([]),
getBaseCommand: jest.fn().mockReturnValue('git'),
getCommandArray: jest.fn().mockReturnValue(['git']),
toString: jest.fn().mockReturnValue('git ls-remote')
})),
LagoonExecutor: jest.fn().mockImplementation(() => ({
execute: jest.fn()
}))
}));

// Mock the execCommand function
jest.mock('./lagoon-api.mjs', () => {
const originalModule = jest.requireActual('./lagoon-api.mjs');

// Only mock the async API functions, keep the helper functions as is
return {
...originalModule,
execCommand: jest.fn()
};
}, { virtual: true });

// Unit tests for pure helper functions
describe('Helper Functions', () => {
describe('gitUrlToGithubUrl', () => {
test('should convert SSH GitHub URL to HTTPS URL', () => {
const sshUrl = 'git@github.com:richardgaunt/lagoon-cli-wrapper.git';
expect(gitUrlToGithubUrl(sshUrl)).toBe('https://github.com/richardgaunt/lagoon-cli-wrapper');
});

test('should clean HTTPS GitHub URL', () => {
const httpsUrl = 'https://github.com/richardgaunt/lagoon-cli-wrapper.git';
expect(gitUrlToGithubUrl(httpsUrl)).toBe('https://github.com/richardgaunt/lagoon-cli-wrapper');
});

test('should return null for non-GitHub URLs', () => {
const nonGithubUrl = 'https://gitlab.com/some/project.git';
expect(gitUrlToGithubUrl(nonGithubUrl)).toBeNull();
});

// This test uses a spied/mocked version of the function
test('should handle URLs with github.com in them', () => {
// Create a URL that definitely contains 'github.com'
const githubUrl = 'https://github.com/user/repo.git';
expect(gitUrlToGithubUrl(githubUrl)).toBe('https://github.com/user/repo');
});
});

describe('extractPrNumber', () => {
test('should extract PR number from environment name', () => {
expect(extractPrNumber('pr-123')).toBe('123');
});

test('should be case insensitive', () => {
expect(extractPrNumber('PR-456')).toBe('456');
});

test('should return null for non-PR environment names', () => {
expect(extractPrNumber('develop')).toBeNull();
expect(extractPrNumber('feature-123')).toBeNull();
expect(extractPrNumber('master')).toBeNull();
expect(extractPrNumber('pr123')).toBeNull(); // Missing hyphen
});

test('should handle multi-digit PR numbers', () => {
expect(extractPrNumber('pr-1')).toBe('1');
expect(extractPrNumber('pr-9999')).toBe('9999');
});
});
});