Skip to content

Commit

Permalink
Merge pull request #195 from HubSpot/add/cms-tests
Browse files Browse the repository at this point in the history
chore: Add tests for CMS utils
  • Loading branch information
kemmerle authored Oct 2, 2024
2 parents 2e20b69 + ac17f42 commit f5e28c8
Show file tree
Hide file tree
Showing 5 changed files with 496 additions and 3 deletions.
146 changes: 146 additions & 0 deletions lib/__tests__/functions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import fs, { PathLike } from 'fs-extra';
import findup from 'findup-sync';
import { getCwd } from '../path';
import { downloadGithubRepoContents } from '../github';
import {
createFunction,
isObjectOrFunction,
createEndpoint,
createConfig,
} from '../cms/functions';

jest.mock('fs-extra');
jest.mock('findup-sync');
jest.mock('../path');
jest.mock('../github');

const mockedGetCwd = getCwd as jest.MockedFunction<typeof getCwd>;
const mockedFindup = findup as jest.MockedFunction<typeof findup>;
const mockedFsExistsSync = fs.existsSync as jest.MockedFunction<
typeof fs.existsSync
>;
const mockedFsReadFileSync = fs.readFileSync as jest.MockedFunction<
typeof fs.readFileSync
>;

describe('lib/cms/functions', () => {
describe('createFunction', () => {
const mockFunctionInfo = {
functionsFolder: 'testFolder',
filename: 'testFunction',
endpointPath: '/api/test',
endpointMethod: 'GET',
};
const mockDest = '/mock/dest';

beforeEach(() => {
mockedGetCwd.mockReturnValue('/mock/cwd');
mockedFindup.mockReturnValue(null);

// Set up fs.existsSync to return different values for different paths
mockedFsExistsSync.mockImplementation((path: PathLike) => {
if (path === '/mock/dest/testFolder.functions/testFunction.js') {
return false;
}
if (path === '/mock/dest/testFolder.functions/serverless.json') {
return true;
}
if (path === '/mock/dest/testFolder.functions') {
return true;
}
return false;
});

// Mock fs.readFileSync to return a valid JSON for the config file
mockedFsReadFileSync.mockReturnValue(
JSON.stringify({
endpoints: {},
})
);
});

it('should create a new function successfully', async () => {
await createFunction(mockFunctionInfo, mockDest);

expect(fs.mkdirp).not.toHaveBeenCalled();

expect(downloadGithubRepoContents).toHaveBeenCalledWith(
'HubSpot/cms-sample-assets',
'functions/sample-function.js',
'/mock/dest/testFolder.functions/testFunction.js'
);

// Check that the config file was updated
expect(fs.writeFileSync).toHaveBeenCalledWith(
'/mock/dest/testFolder.functions/serverless.json',
expect.any(String)
);
});
});

describe('isObjectOrFunction', () => {
it('should return true for objects', () => {
expect(isObjectOrFunction({})).toBe(true);
});

it('should return true for functions', () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
expect(isObjectOrFunction(() => {})).toBe(true);
});

it('should return false for null', () => {
// @ts-expect-error test case
expect(isObjectOrFunction(null)).toBe(false);
});

it('should return false for primitives', () => {
// @ts-expect-error test case
expect(isObjectOrFunction(5)).toBe(false);
// @ts-expect-error test case
expect(isObjectOrFunction('string')).toBe(false);
// @ts-expect-error test case
expect(isObjectOrFunction(true)).toBe(false);
});
});

describe('createEndpoint', () => {
it('should create an endpoint object', () => {
const result = createEndpoint('POST', 'test.js');
expect(result).toEqual({
method: 'POST',
file: 'test.js',
});
});

it('should default to GET method if not provided', () => {
const result = createEndpoint('', 'test.js');
expect(result).toEqual({
method: 'GET',
file: 'test.js',
});
});
});

describe('createConfig', () => {
it('should create a config object', () => {
const result = createConfig({
endpointPath: '/api/test',
endpointMethod: 'POST',
functionFile: 'test.js',
});

expect(result).toEqual({
runtime: 'nodejs18.x',
version: '1.0',
environment: {},
secrets: [],
endpoints: {
'/api/test': {
method: 'POST',
file: 'test.js',
},
},
});
});
});
});
88 changes: 88 additions & 0 deletions lib/__tests__/themes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import findup from 'findup-sync';
import { getHubSpotWebsiteOrigin } from '../urls';
import { getThemeJSONPath, getThemePreviewUrl } from '../cms/themes';
import { getEnv } from '../../config';
import { ENVIRONMENTS } from '../../constants/environments';

jest.mock('findup-sync');
jest.mock('../urls');
jest.mock('../../config');
jest.mock('../../constants/environments', () => ({
ENVIRONMENTS: {
PROD: 'https://prod.hubspot.com',
QA: 'https://qa.hubspot.com',
},
}));

const mockedFindup = findup as jest.MockedFunction<typeof findup>;
const mockedGetEnv = getEnv as jest.MockedFunction<typeof getEnv>;
const mockedGetHubSpotWebsiteOrigin =
getHubSpotWebsiteOrigin as jest.MockedFunction<
typeof getHubSpotWebsiteOrigin
>;

describe('lib/cms/themes', () => {
describe('getThemeJSONPath', () => {
it('should return the theme.json path if found', () => {
mockedFindup.mockReturnValue('/path/to/theme.json');

const result = getThemeJSONPath('/some/path');

expect(findup).toHaveBeenCalledWith('theme.json', {
cwd: '/some/path',
nocase: true,
});
expect(result).toBe('/path/to/theme.json');
});

it('should return null if theme.json is not found', () => {
mockedFindup.mockReturnValue(null);

const result = getThemeJSONPath('/some/path');

expect(findup).toHaveBeenCalledWith('theme.json', {
cwd: '/some/path',
nocase: true,
});
expect(result).toBeNull();
});
});

describe('getThemePreviewUrl', () => {
it('should return the correct theme preview URL for PROD environment', () => {
mockedFindup.mockReturnValue('/src/my-theme/theme.json');
mockedGetEnv.mockReturnValue('prod');
mockedGetHubSpotWebsiteOrigin.mockReturnValue('https://prod.hubspot.com');

const result = getThemePreviewUrl('/path/to/file', 12345);

expect(getEnv).toHaveBeenCalledWith(12345);
expect(getHubSpotWebsiteOrigin).toHaveBeenCalledWith(ENVIRONMENTS.PROD);
expect(result).toBe(
'https://prod.hubspot.com/theme-previewer/12345/edit/my-theme'
);
});

it('should return the correct theme preview URL for QA environment', () => {
mockedFindup.mockReturnValue('/src/my-theme/theme.json');
mockedGetEnv.mockReturnValue('qa');
mockedGetHubSpotWebsiteOrigin.mockReturnValue('https://qa.hubspot.com');

const result = getThemePreviewUrl('/path/to/file', 12345);

expect(getEnv).toHaveBeenCalledWith(12345);
expect(getHubSpotWebsiteOrigin).toHaveBeenCalledWith(ENVIRONMENTS.QA);
expect(result).toBe(
'https://qa.hubspot.com/theme-previewer/12345/edit/my-theme'
);
});

it('should return undefined if theme.json is not found', () => {
mockedFindup.mockReturnValue(null);

const result = getThemePreviewUrl('/invalid/path', 12345);

expect(result).toBeUndefined();
});
});
});
115 changes: 115 additions & 0 deletions lib/__tests__/validate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import fs, { Stats } from 'fs-extra';
import { validateHubl } from '../../api/validateHubl';
import { walk } from '../fs';
import { lint } from '../cms/validate';
import { LintResult, Validation } from '../../types/HublValidation';
import { AxiosPromise } from 'axios';

jest.mock('fs-extra');
jest.mock('../../api/validateHubl');
jest.mock('../fs');

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockedFsStat = fs.stat as jest.MockedFunction<any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockedFsReadFile = fs.readFile as jest.MockedFunction<any>;
const mockedWalk = walk as jest.MockedFunction<typeof walk>;
const mockedValidateHubl = validateHubl as jest.MockedFunction<
typeof validateHubl
>;

const mockFsStats = jest.createMockFromModule<Stats>('fs-extra');

mockFsStats.isDirectory = jest.fn(() => true);

const mockValidation: Validation = {
meta: {
all_widgets: [],
widgets_in_rich_text: [],
editable_flex_areas: [],
editable_layout_sections: null,
email_style_settings: null,
sms_flex_area: [],
google_font_variations: null,
custom_font_variations: [],
has_style_tag: false,
has_header_tag: false,
output_html: '',
has_menu_tag: false,
has_theme_setting_function: false,
template_source: '',
attribute_defaults: null,
template_errors: [],
path_dependencies: [],
theme_field_dependencies: [],
template_type_ids: null,
exact_path_references: [],
required_scopes_to_render: [],
},
renderingErrors: [],
html: '',
};

describe('lib/cms/validate', () => {
const accountId = 123;
const filePath = 'test.html';

it('should return an empty array if directory has no files', async () => {
mockedFsStat.mockResolvedValue(mockFsStats);
mockedWalk.mockResolvedValue([]);

const result = await lint(accountId, filePath);
expect(result).toEqual([]);
});

it('should return the correct object if a file has no content', async () => {
mockedFsStat.mockResolvedValue({ isDirectory: () => false });
mockedFsReadFile.mockResolvedValue(' ');

const result = await lint(accountId, filePath);
expect(result).toEqual([{ file: filePath, validation: null }]);
});

it('should call validateHubl with the correct parameters', async () => {
const mockSource = 'valid HUBL content';
mockedFsStat.mockResolvedValue({ isDirectory: () => false });
mockedFsReadFile.mockResolvedValue(mockSource);
mockedValidateHubl.mockResolvedValue({
data: mockValidation,
} as unknown as AxiosPromise<Validation>);
const result = await lint(accountId, filePath);
expect(validateHubl).toHaveBeenCalledWith(accountId, mockSource);
expect(result).toEqual([{ file: filePath, validation: mockValidation }]);
});

it('should filter out files with invalid extensions', async () => {
const invalidFile = 'test.txt';
mockedFsStat.mockResolvedValue({ isDirectory: () => true });
mockedWalk.mockResolvedValue([invalidFile, filePath]);
mockedFsReadFile.mockResolvedValue('valid HUBL content');
mockedValidateHubl.mockResolvedValue({
data: mockValidation,
} as unknown as AxiosPromise<Validation>);

const result = await lint(accountId, filePath);

expect(result).toHaveLength(1);
expect((result as Partial<LintResult>[])[0].file).toBe(filePath);
});

it('should execute callback if provided', async () => {
const mockCallback = jest.fn();
const mockSource = 'valid HUBL content';
mockedFsStat.mockResolvedValue({ isDirectory: () => false });
mockedFsReadFile.mockResolvedValue(mockSource);
mockedValidateHubl.mockResolvedValue({
data: mockValidation,
} as unknown as AxiosPromise<Validation>);

await lint(accountId, filePath, mockCallback);
expect(mockCallback).toHaveBeenCalledWith({
file: filePath,
validation: mockValidation,
});
});
});
Loading

0 comments on commit f5e28c8

Please sign in to comment.