first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
import { DeploymentConfig, SecurityConfig } from '@n8n/config';
|
||||
import { Container } from '@n8n/di';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { IExecuteFunctions } from 'n8n-workflow';
|
||||
import type { SimpleGit } from 'simple-git';
|
||||
import simpleGit from 'simple-git';
|
||||
|
||||
import { Git } from '../Git.node';
|
||||
|
||||
const mockGit = {
|
||||
log: jest.fn(),
|
||||
env: jest.fn().mockReturnThis(),
|
||||
};
|
||||
|
||||
jest.mock('simple-git');
|
||||
const mockSimpleGit = simpleGit as jest.MockedFunction<typeof simpleGit>;
|
||||
mockSimpleGit.mockReturnValue(mockGit as unknown as SimpleGit);
|
||||
|
||||
describe('Git Node', () => {
|
||||
let gitNode: Git;
|
||||
let executeFunctions: jest.Mocked<IExecuteFunctions>;
|
||||
let deploymentConfig: jest.Mocked<DeploymentConfig>;
|
||||
let securityConfig: jest.Mocked<SecurityConfig>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
deploymentConfig = mock<DeploymentConfig>({
|
||||
type: 'default',
|
||||
});
|
||||
securityConfig = mock<SecurityConfig>({
|
||||
disableBareRepos: false,
|
||||
enableGitNodeHooks: true,
|
||||
});
|
||||
Container.set(DeploymentConfig, deploymentConfig);
|
||||
Container.set(SecurityConfig, securityConfig);
|
||||
|
||||
executeFunctions = mock<IExecuteFunctions>({
|
||||
getInputData: jest.fn().mockReturnValue([{ json: {} }]),
|
||||
getNodeParameter: jest.fn(),
|
||||
helpers: {
|
||||
isFilePathBlocked: jest.fn(),
|
||||
returnJsonArray: jest
|
||||
.fn()
|
||||
.mockImplementation((data: unknown[]) => data.map((item: unknown) => ({ json: item }))),
|
||||
},
|
||||
});
|
||||
executeFunctions.getNodeParameter.mockImplementation((name: string) => {
|
||||
switch (name) {
|
||||
case 'operation':
|
||||
return 'log';
|
||||
case 'repositoryPath':
|
||||
return '/tmp/test-repo';
|
||||
case 'options':
|
||||
return {};
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
mockGit.log.mockResolvedValue({ all: [] });
|
||||
|
||||
gitNode = new Git();
|
||||
});
|
||||
|
||||
describe('Bare Repository Configuration', () => {
|
||||
it('should add safe.bareRepository=explicit when deployment type is cloud', async () => {
|
||||
deploymentConfig.type = 'cloud';
|
||||
securityConfig.disableBareRepos = false;
|
||||
|
||||
await gitNode.execute.call(executeFunctions);
|
||||
|
||||
expect(mockSimpleGit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: ['safe.bareRepository=explicit'],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should add safe.bareRepository=explicit when disableBareRepos is true', async () => {
|
||||
deploymentConfig.type = 'default';
|
||||
securityConfig.disableBareRepos = true;
|
||||
|
||||
await gitNode.execute.call(executeFunctions);
|
||||
|
||||
expect(mockSimpleGit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: ['safe.bareRepository=explicit'],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should add safe.bareRepository=explicit when both cloud and disableBareRepos are true', async () => {
|
||||
deploymentConfig.type = 'cloud';
|
||||
securityConfig.disableBareRepos = true;
|
||||
|
||||
await gitNode.execute.call(executeFunctions);
|
||||
|
||||
expect(mockSimpleGit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: ['safe.bareRepository=explicit'],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not add safe.bareRepository=explicit when neither cloud nor disableBareRepos is true', async () => {
|
||||
deploymentConfig.type = 'default';
|
||||
securityConfig.disableBareRepos = false;
|
||||
|
||||
await gitNode.execute.call(executeFunctions);
|
||||
|
||||
expect(mockSimpleGit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: [],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Hooks Configuration', () => {
|
||||
it('should add core.hooksPath=/dev/null when enableGitNodeHooks is false', async () => {
|
||||
securityConfig.enableGitNodeHooks = false;
|
||||
|
||||
await gitNode.execute.call(executeFunctions);
|
||||
|
||||
expect(mockSimpleGit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: ['core.hooksPath=/dev/null'],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not add core.hooksPath=/dev/null when enableGitNodeHooks is true', async () => {
|
||||
securityConfig.enableGitNodeHooks = true;
|
||||
|
||||
await gitNode.execute.call(executeFunctions);
|
||||
|
||||
expect(mockSimpleGit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: [],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Restricted file paths', () => {
|
||||
it('should throw an error if the repository path is blocked', async () => {
|
||||
(executeFunctions.helpers.isFilePathBlocked as jest.Mock).mockReturnValue(true);
|
||||
(executeFunctions.helpers.resolvePath as jest.Mock).mockResolvedValue('/tmp/test-repo');
|
||||
|
||||
await expect(gitNode.execute.call(executeFunctions)).rejects.toThrow(
|
||||
'Access to the repository path is not allowed',
|
||||
);
|
||||
});
|
||||
|
||||
it('should use the resolved repository path for git operations', async () => {
|
||||
const originalPath = '/tmp/link-to-repo';
|
||||
const resolvedPath = '/tmp/actual-repo';
|
||||
|
||||
executeFunctions.getNodeParameter.mockImplementation((name: string) => {
|
||||
switch (name) {
|
||||
case 'operation':
|
||||
return 'log';
|
||||
case 'repositoryPath':
|
||||
return originalPath;
|
||||
case 'options':
|
||||
return {};
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
(executeFunctions.helpers.resolvePath as jest.Mock).mockResolvedValue(resolvedPath);
|
||||
(executeFunctions.helpers.isFilePathBlocked as jest.Mock).mockReturnValue(false);
|
||||
|
||||
await gitNode.execute.call(executeFunctions);
|
||||
|
||||
// Verify git is initialized with the resolved path, not the original
|
||||
expect(mockSimpleGit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
baseDir: resolvedPath,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user