cli/new: template load tests + fixes

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2025-02-07 13:40:16 +01:00
parent ae502294d7
commit a2ad89b50c
3 changed files with 116 additions and 62 deletions
@@ -18,6 +18,7 @@ import { NewTemplateLoader } from './NewTemplateLoader';
import { NewConfig } from '../config/types';
import inquirer from 'inquirer';
import { withLogCollector } from '@backstage/test-utils';
import { createMockDirectory } from '@backstage/backend-test-utils';
describe('NewTemplateLoader.selectTemplateInteractively', () => {
const mockConfig: NewConfig = {
@@ -29,6 +30,10 @@ describe('NewTemplateLoader.selectTemplateInteractively', () => {
globals: {},
};
beforeEach(() => {
jest.resetAllMocks();
});
it('should select a template interactively', async () => {
jest.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ id: 'template1' });
@@ -84,3 +89,100 @@ describe('NewTemplateLoader.selectTemplateInteractively', () => {
});
});
});
describe('NewTemplateLoader.loadTemplate', () => {
describe('NewTemplateLoader.loadTemplate', () => {
it('should load a valid template', async () => {
const mockDir = createMockDirectory({
content: {
'path/to/template1.yaml': `
template: template1
targetPath: plugins
`,
'path/to/template1/hello.txt': 'hello world',
},
});
const result = await NewTemplateLoader.loadTemplate({
id: 'template1',
target: mockDir.resolve('path/to/template1.yaml'),
});
expect(result).toEqual({
id: 'template1',
templatePath: mockDir.resolve('path/to/template1'),
targetPath: 'plugins',
});
});
it('should throw an error if template file does not exist', async () => {
const mockDir = createMockDirectory();
await expect(
NewTemplateLoader.loadTemplate({
id: 'template1',
target: mockDir.resolve('path/to/template1.yaml'),
}),
).rejects.toThrow(
/^Failed to load template definition from '.*'; caused by Error: ENOENT/,
);
});
it('should throw an error if template definition is invalid', async () => {
const mockDir = createMockDirectory({
content: {
'path/to/template1.yaml': `invalid: definition`,
},
});
await expect(
NewTemplateLoader.loadTemplate({
id: 'template1',
target: mockDir.resolve('path/to/template1.yaml'),
}),
).rejects.toThrow(
/Invalid template definition at '.*'; caused by Validation error/,
);
});
it('should throw an error if target is a remote URL', async () => {
await expect(
NewTemplateLoader.loadTemplate({
id: 'template1',
target: 'http://example.com',
}),
).rejects.toThrow('Remote templates are not supported yet');
});
it('should throw an error if target directory does not exist', async () => {
await expect(
NewTemplateLoader.loadTemplate({
id: 'template1',
target: 'http://example.com',
}),
).rejects.toThrow('Remote templates are not supported yet');
});
it('should throw an error if template directory does not exist', async () => {
const mockDir = createMockDirectory({
content: {
'path/to/template1.yaml': `
template: template1
targetPath: plugins
`,
},
});
await expect(
NewTemplateLoader.loadTemplate({
id: 'template1',
target: mockDir.resolve('path/to/template1.yaml'),
}),
).rejects.toThrow(
`Failed to load template contents from '${mockDir.resolve(
'path/to/template1',
)}'`,
);
});
});
});
@@ -17,6 +17,7 @@
import { z } from 'zod';
import fs from 'fs-extra';
import inquirer from 'inquirer';
import { resolve as resolvePath } from 'path';
import { dirname } from 'node:path';
import { parse as parseYaml } from 'yaml';
import { paths } from '../../paths';
@@ -90,12 +91,15 @@ export class NewTemplateLoader {
if (target.match(/https?:\/\//)) {
throw new Error('Remote templates are not supported yet');
}
if (!fs.existsSync(paths.resolveTargetRoot(target))) {
throw new Error(`Your CLI template does not exist: ${target}`);
}
const rawTemplate = parseYaml(
fs.readFileSync(paths.resolveTargetRoot(target), 'utf-8'),
);
const templateContent = await fs
.readFile(paths.resolveTargetRoot(target), 'utf-8')
.catch(error => {
throw new ForwardedError(
`Failed to load template definition from '${target}'`,
error,
);
});
const rawTemplate = parseYaml(templateContent);
const parsed = templateDefinitionSchema.safeParse(rawTemplate);
if (!parsed.success) {
@@ -105,17 +109,14 @@ export class NewTemplateLoader {
);
}
const template = parsed.data;
const { template, ...templateData } = parsed.data;
const templatePath = paths.resolveTargetRoot(
dirname(target),
template.template,
);
const templatePath = resolvePath(dirname(target), template);
if (!fs.existsSync(templatePath)) {
throw new Error(
`Your CLI template skeleton does not exist: ${templatePath}`,
`Failed to load template contents from '${templatePath}'`,
);
}
return { id, templatePath, ...template };
return { id, templatePath, ...templateData };
}
}
@@ -1,49 +0,0 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { readCliConfig, verifyTemplate } from './templateSelector';
import { createMockDirectory } from '@backstage/backend-test-utils';
describe('verifyTemplate', () => {
it('throws an error if template target is a remote URL', () => {
expect(() => verifyTemplate({ id: '', target: 'http' })).toThrow(
'Remote templates are not supported yet',
);
});
it('throws an error if template yaml file does not exist', () => {
expect(() => verifyTemplate({ id: '', target: '/foo' })).toThrow(
'Your CLI template does not exist: /foo',
);
});
it('throws an error if the skeleton of the template does not exist', () => {
const mockDir = createMockDirectory({
content: { 'template.yaml': 'template: "foo"' },
});
expect(() =>
verifyTemplate({ id: '', target: mockDir.resolve('template.yaml') }),
).toThrow();
});
it('throws an error if template is missing a targetPath', () => {
const mockDir = createMockDirectory({
content: { 'template.yaml': 'template: "foo"', foo: 'bar' },
});
expect(() =>
verifyTemplate({ id: '', target: mockDir.resolve('template.yaml') }),
).toThrow();
});
});