mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-02 08:58:32 +00:00
271 lines
10 KiB
JavaScript
271 lines
10 KiB
JavaScript
const path = require('path');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const yaml = require('js-yaml');
|
|
const {
|
|
reorderWorkspaceCollections,
|
|
setCollectionGitRemote,
|
|
clearCollectionGitRemote,
|
|
getWorkspaceCollections
|
|
} = require('../../src/utils/workspace-config');
|
|
|
|
const collection = (name, pathSegment, extra = {}) => ({ name, path: pathSegment, ...extra });
|
|
|
|
describe('reorderWorkspaceCollections', () => {
|
|
let workspacePath;
|
|
|
|
/** Writes workspace.yml with the given collections (relative paths). */
|
|
const writeWorkspaceYml = (collections) => {
|
|
const content = [
|
|
'opencollection: 1.0.0',
|
|
'info:',
|
|
' name: Test',
|
|
' type: workspace',
|
|
'collections:',
|
|
...collections.flatMap((c) => [` - name: ${c.name}`, ` path: ${c.path}`]),
|
|
'specs: []',
|
|
'docs: \'\''
|
|
].join('\n');
|
|
fs.writeFileSync(path.join(workspacePath, 'workspace.yml'), content);
|
|
};
|
|
|
|
/** Returns collection paths (relative) in order as stored in workspace.yml. */
|
|
const getCollectionPathsFromYml = () => {
|
|
const raw = fs.readFileSync(path.join(workspacePath, 'workspace.yml'), 'utf8');
|
|
const config = yaml.load(raw);
|
|
return (config.collections || []).map((c) => c.path);
|
|
};
|
|
|
|
/** Resolves a relative collection path segment to an absolute path under the current workspace. */
|
|
const absPath = (relativePath) => path.resolve(workspacePath, relativePath);
|
|
|
|
beforeEach(() => {
|
|
workspacePath = fs.mkdtempSync(path.join(os.tmpdir(), 'bruno-ws-'));
|
|
});
|
|
|
|
afterEach(() => {
|
|
fs.rmSync(workspacePath, { recursive: true, force: true });
|
|
});
|
|
|
|
test('reorders collections to match given path list', async () => {
|
|
writeWorkspaceYml([
|
|
collection('API', 'collections/api'),
|
|
collection('Backend', 'collections/backend'),
|
|
collection('Frontend', 'collections/frontend')
|
|
]);
|
|
|
|
await reorderWorkspaceCollections(workspacePath, [
|
|
absPath('collections/frontend'),
|
|
absPath('collections/api'),
|
|
absPath('collections/backend')
|
|
]);
|
|
|
|
expect(getCollectionPathsFromYml()).toEqual(['collections/frontend', 'collections/api', 'collections/backend']);
|
|
});
|
|
|
|
test('deduplicates when reorder list contains duplicate paths', async () => {
|
|
writeWorkspaceYml([
|
|
collection('API', 'collections/api'),
|
|
collection('Backend', 'collections/backend')
|
|
]);
|
|
|
|
await reorderWorkspaceCollections(workspacePath, [
|
|
absPath('collections/api'),
|
|
absPath('collections/backend'),
|
|
absPath('collections/api'),
|
|
absPath('collections/api')
|
|
]);
|
|
|
|
expect(getCollectionPathsFromYml()).toEqual(['collections/api', 'collections/backend']);
|
|
});
|
|
});
|
|
|
|
describe('Git remote on workspace collections', () => {
|
|
let workspacePath;
|
|
|
|
const writeYml = (collections) => {
|
|
const lines = [
|
|
'opencollection: 1.0.0',
|
|
'info:',
|
|
' name: Test',
|
|
' type: workspace',
|
|
'collections:'
|
|
];
|
|
for (const c of collections) {
|
|
lines.push(` - name: "${c.name}"`);
|
|
lines.push(` path: "${c.path}"`);
|
|
if (c.remote) lines.push(` remote: "${c.remote}"`);
|
|
}
|
|
lines.push('specs: []');
|
|
lines.push('docs: \'\'');
|
|
fs.writeFileSync(path.join(workspacePath, 'workspace.yml'), lines.join('\n'));
|
|
};
|
|
|
|
const readCollectionsFromYml = () => {
|
|
const raw = fs.readFileSync(path.join(workspacePath, 'workspace.yml'), 'utf8');
|
|
return (yaml.load(raw).collections || []);
|
|
};
|
|
|
|
const absPath = (relativePath) => path.resolve(workspacePath, relativePath);
|
|
|
|
const ensureCollectionDir = (relativePath) => {
|
|
const dir = path.join(workspacePath, relativePath);
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
fs.writeFileSync(path.join(dir, 'bruno.json'), JSON.stringify({ name: 'x', version: '1', type: 'collection' }));
|
|
};
|
|
|
|
beforeEach(() => {
|
|
workspacePath = fs.mkdtempSync(path.join(os.tmpdir(), 'bruno-ws-git-'));
|
|
});
|
|
|
|
afterEach(() => {
|
|
fs.rmSync(workspacePath, { recursive: true, force: true });
|
|
});
|
|
|
|
test('setCollectionGitRemote sets remote on the matching entry only', async () => {
|
|
writeYml([
|
|
collection('API', 'collections/api'),
|
|
collection('Backend', 'collections/backend')
|
|
]);
|
|
|
|
await setCollectionGitRemote(workspacePath, absPath('collections/backend'), 'https://github.com/x/backend');
|
|
|
|
const entries = readCollectionsFromYml();
|
|
expect(entries[0]).toEqual({ name: 'API', path: 'collections/api' });
|
|
expect(entries[1]).toEqual({ name: 'Backend', path: 'collections/backend', remote: 'https://github.com/x/backend' });
|
|
});
|
|
|
|
test('setCollectionGitRemote rejects empty URL', async () => {
|
|
writeYml([collection('API', 'collections/api')]);
|
|
await expect(
|
|
setCollectionGitRemote(workspacePath, absPath('collections/api'), ' ')
|
|
).rejects.toThrow(/non-empty/i);
|
|
});
|
|
|
|
test('setCollectionGitRemote throws when collection is missing from workspace.yml', async () => {
|
|
writeYml([collection('API', 'collections/api')]);
|
|
await expect(
|
|
setCollectionGitRemote(workspacePath, absPath('collections/missing'), 'https://github.com/x/y')
|
|
).rejects.toThrow(/not found/i);
|
|
});
|
|
|
|
test('clearCollectionGitRemote removes only the remote field', async () => {
|
|
writeYml([
|
|
collection('API', 'collections/api', { remote: 'https://github.com/x/api' }),
|
|
collection('Backend', 'collections/backend', { remote: 'https://github.com/x/backend' })
|
|
]);
|
|
|
|
await clearCollectionGitRemote(workspacePath, absPath('collections/api'));
|
|
|
|
const entries = readCollectionsFromYml();
|
|
expect(entries[0]).toEqual({ name: 'API', path: 'collections/api' });
|
|
expect(entries[1]).toEqual({ name: 'Backend', path: 'collections/backend', remote: 'https://github.com/x/backend' });
|
|
});
|
|
|
|
test('getWorkspaceCollections keeps git-backed entries even when local folder is missing', () => {
|
|
ensureCollectionDir('collections/api');
|
|
writeYml([
|
|
collection('API', 'collections/api'),
|
|
collection('Missing', 'collections/missing', { remote: 'https://github.com/x/missing' })
|
|
]);
|
|
|
|
const result = getWorkspaceCollections(workspacePath);
|
|
|
|
expect(result).toHaveLength(2);
|
|
const api = result.find((r) => r.name === 'API');
|
|
const missing = result.find((r) => r.name === 'Missing');
|
|
expect(api.notFoundLocally).toBeUndefined();
|
|
expect(missing.notFoundLocally).toBe(true);
|
|
expect(missing.remote).toBe('https://github.com/x/missing');
|
|
});
|
|
|
|
test('getWorkspaceCollections still drops missing entries that have no remote', () => {
|
|
writeYml([collection('Missing', 'collections/missing')]);
|
|
expect(getWorkspaceCollections(workspacePath)).toHaveLength(0);
|
|
});
|
|
|
|
test('setCollectionGitRemote adds the collection path to .gitignore', async () => {
|
|
ensureCollectionDir('collections/api');
|
|
writeYml([collection('API', 'collections/api')]);
|
|
|
|
await setCollectionGitRemote(workspacePath, absPath('collections/api'), 'https://github.com/x/api');
|
|
|
|
const gitignore = fs.readFileSync(path.join(workspacePath, '.gitignore'), 'utf8');
|
|
expect(gitignore.split('\n')).toContain('collections/api/');
|
|
});
|
|
|
|
test('setCollectionGitRemote does not duplicate the .gitignore entry on repeated calls', async () => {
|
|
ensureCollectionDir('collections/api');
|
|
writeYml([collection('API', 'collections/api')]);
|
|
|
|
await setCollectionGitRemote(workspacePath, absPath('collections/api'), 'https://github.com/x/api');
|
|
await setCollectionGitRemote(workspacePath, absPath('collections/api'), 'https://github.com/x/api-renamed');
|
|
|
|
const gitignore = fs.readFileSync(path.join(workspacePath, '.gitignore'), 'utf8');
|
|
const matches = gitignore.split('\n').filter((line) => line.trim() === 'collections/api/');
|
|
expect(matches).toHaveLength(1);
|
|
});
|
|
|
|
test('setCollectionGitRemote preserves existing .gitignore content', async () => {
|
|
ensureCollectionDir('collections/api');
|
|
writeYml([collection('API', 'collections/api')]);
|
|
fs.writeFileSync(path.join(workspacePath, '.gitignore'), '# user notes\nnode_modules\n.env\n');
|
|
|
|
await setCollectionGitRemote(workspacePath, absPath('collections/api'), 'https://github.com/x/api');
|
|
|
|
const gitignore = fs.readFileSync(path.join(workspacePath, '.gitignore'), 'utf8');
|
|
expect(gitignore).toContain('# user notes');
|
|
expect(gitignore).toContain('node_modules');
|
|
expect(gitignore).toContain('.env');
|
|
expect(gitignore).toContain('collections/api/');
|
|
});
|
|
|
|
test('clearCollectionGitRemote removes the collection path from .gitignore', async () => {
|
|
ensureCollectionDir('collections/api');
|
|
writeYml([collection('API', 'collections/api', { remote: 'https://github.com/x/api' })]);
|
|
fs.writeFileSync(path.join(workspacePath, '.gitignore'), [
|
|
'node_modules',
|
|
'# Bruno managed collection remotes',
|
|
'collections/api/',
|
|
'# End Bruno managed collection remotes',
|
|
''
|
|
].join('\n'));
|
|
|
|
await clearCollectionGitRemote(workspacePath, absPath('collections/api'));
|
|
|
|
const gitignore = fs.readFileSync(path.join(workspacePath, '.gitignore'), 'utf8');
|
|
expect(gitignore.split('\n')).not.toContain('collections/api/');
|
|
expect(gitignore).toContain('node_modules');
|
|
});
|
|
|
|
test('clearCollectionGitRemote preserves user-owned .gitignore entries', async () => {
|
|
ensureCollectionDir('collections/api');
|
|
writeYml([collection('API', 'collections/api')]);
|
|
fs.writeFileSync(path.join(workspacePath, '.gitignore'), 'node_modules\ncollections/api/\n');
|
|
|
|
await setCollectionGitRemote(workspacePath, absPath('collections/api'), 'https://github.com/x/api');
|
|
await clearCollectionGitRemote(workspacePath, absPath('collections/api'));
|
|
|
|
const gitignore = fs.readFileSync(path.join(workspacePath, '.gitignore'), 'utf8');
|
|
expect(gitignore.split('\n')).toContain('collections/api/');
|
|
expect(gitignore).toContain('node_modules');
|
|
});
|
|
|
|
test('setCollectionGitRemote skips .gitignore for collections outside the workspace', async () => {
|
|
const outsideDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bruno-outside-'));
|
|
fs.writeFileSync(path.join(outsideDir, 'bruno.json'), JSON.stringify({ name: 'x', version: '1', type: 'collection' }));
|
|
try {
|
|
writeYml([collection('External', outsideDir)]);
|
|
await setCollectionGitRemote(workspacePath, outsideDir, 'https://github.com/x/external');
|
|
|
|
const gitignorePath = path.join(workspacePath, '.gitignore');
|
|
if (fs.existsSync(gitignorePath)) {
|
|
const gitignore = fs.readFileSync(gitignorePath, 'utf8');
|
|
expect(gitignore).not.toContain(outsideDir);
|
|
}
|
|
} finally {
|
|
fs.rmSync(outsideDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|