|
| 1 | +/* |
| 2 | + * Copyright (c) 2025, Salesforce, Inc. |
| 3 | + * All rights reserved. |
| 4 | + * SPDX-License-Identifier: BSD-3-Clause |
| 5 | + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause |
| 6 | + */ |
| 7 | + |
| 8 | +const fs = require('fs') |
| 9 | +const path = require('path') |
| 10 | + |
| 11 | +// Mock the dependencies |
| 12 | +jest.mock('fs') |
| 13 | + |
| 14 | +// Mocking the config.js file to allow testing with smaller arrays of expected artifacts |
| 15 | +jest.mock('../config.js', () => ({ |
| 16 | + GENERATED_PROJECTS_DIR: '../generated-projects', |
| 17 | + EXPECTED_GENERATED_ARTIFACTS: { |
| 18 | + 'retail-app-demo': ['package.json', 'node_modules', 'config'], |
| 19 | + 'retail-app-ext': ['package.json', 'node_modules', 'overrides'] |
| 20 | + } |
| 21 | +})) |
| 22 | +jest.mock('./utils.js', () => ({ |
| 23 | + diffArrays: jest.fn() |
| 24 | +})) |
| 25 | + |
| 26 | +// Import the functions to test |
| 27 | +const {diffArrays} = require('./utils.js') |
| 28 | +const {validateGeneratedArtifacts} = require('./validate-generated-project.js') |
| 29 | + |
| 30 | +describe('validateGeneratedArtifacts', () => { |
| 31 | + beforeEach(() => { |
| 32 | + jest.clearAllMocks() |
| 33 | + }) |
| 34 | + |
| 35 | + test('resolves when all expected artifacts are present', async () => { |
| 36 | + const project = 'retail-app-demo' |
| 37 | + const expectedArtifacts = ['package.json', 'node_modules', 'config'] |
| 38 | + const actualArtifacts = ['package.json', 'node_modules', 'config', 'extra-file'] |
| 39 | + |
| 40 | + fs.readdirSync.mockReturnValue(actualArtifacts) |
| 41 | + diffArrays.mockReturnValue([]) |
| 42 | + |
| 43 | + const result = await validateGeneratedArtifacts(project) |
| 44 | + |
| 45 | + expect(fs.readdirSync).toHaveBeenCalledWith( |
| 46 | + // path.sep is used to handle the platform-specific path separator. (Windows uses \ and other platforms use /) |
| 47 | + expect.stringContaining(`generated-projects${path.sep}${project}`) |
| 48 | + ) |
| 49 | + expect(diffArrays).toHaveBeenCalledWith(expectedArtifacts, actualArtifacts) |
| 50 | + expect(result).toBe(`Successfully validated generated artifacts for: ${project} `) |
| 51 | + }) |
| 52 | + |
| 53 | + test('rejects when artifacts are missing', async () => { |
| 54 | + const project = 'retail-app-demo' |
| 55 | + const actualArtifacts = ['package.json', 'node_modules'] |
| 56 | + const missingArtifacts = ['config'] |
| 57 | + |
| 58 | + fs.readdirSync.mockReturnValue(actualArtifacts) |
| 59 | + diffArrays.mockReturnValue(missingArtifacts) |
| 60 | + |
| 61 | + await expect(validateGeneratedArtifacts(project)).rejects.toBe( |
| 62 | + `Generated project (${project}) is missing one or more artifacts: ${missingArtifacts}` |
| 63 | + ) |
| 64 | + }) |
| 65 | + |
| 66 | + test('rejects when project directory does not exist', async () => { |
| 67 | + const project = 'non-existent-project' |
| 68 | + const error = new Error('ENOENT: no such file or directory') |
| 69 | + |
| 70 | + fs.readdirSync.mockImplementation(() => { |
| 71 | + throw error |
| 72 | + }) |
| 73 | + |
| 74 | + await expect(validateGeneratedArtifacts(project)).rejects.toBe( |
| 75 | + `Generated project (${project}) is missing one or more artifacts: ${error}` |
| 76 | + ) |
| 77 | + }) |
| 78 | + |
| 79 | + test('handles project with no expected artifacts', async () => { |
| 80 | + const project = 'unknown-project' |
| 81 | + const actualArtifacts = ['some-file'] |
| 82 | + |
| 83 | + fs.readdirSync.mockReturnValue(actualArtifacts) |
| 84 | + diffArrays.mockReturnValue([]) |
| 85 | + |
| 86 | + const result = await validateGeneratedArtifacts(project) |
| 87 | + |
| 88 | + expect(diffArrays).toHaveBeenCalledWith([], actualArtifacts) |
| 89 | + expect(result).toBe(`Successfully validated generated artifacts for: ${project} `) |
| 90 | + }) |
| 91 | +}) |
| 92 | + |
| 93 | +// Since it requires files at runtime, we'll test the key validation logic |
| 94 | +describe('validateExtensibilityConfig validation logic', () => { |
| 95 | + test('validates Object.hasOwn usage for extensibility config', () => { |
| 96 | + // Test the core validation logic that was fixed |
| 97 | + const validConfig = { |
| 98 | + ccExtensibility: { |
| 99 | + extends: '@salesforce/retail-react-app', |
| 100 | + overridesDir: 'overrides' |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + const invalidConfigMissingProperty = { |
| 105 | + ccExtensibility: { |
| 106 | + extends: '@salesforce/retail-react-app' |
| 107 | + // missing overridesDir |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + const invalidConfigWrongExtends = { |
| 112 | + ccExtensibility: { |
| 113 | + extends: '@wrong/package', |
| 114 | + overridesDir: 'overrides' |
| 115 | + } |
| 116 | + } |
| 117 | + |
| 118 | + expect(Object.hasOwn(validConfig, 'ccExtensibility')).toBe(true) |
| 119 | + expect(Object.hasOwn(validConfig.ccExtensibility, 'extends')).toBe(true) |
| 120 | + expect(Object.hasOwn(validConfig.ccExtensibility, 'overridesDir')).toBe(true) |
| 121 | + |
| 122 | + expect(Object.hasOwn(invalidConfigMissingProperty.ccExtensibility, 'overridesDir')).toBe( |
| 123 | + false |
| 124 | + ) |
| 125 | + |
| 126 | + const isValidConfig = (pkg) => { |
| 127 | + return ( |
| 128 | + Object.hasOwn(pkg, 'ccExtensibility') && |
| 129 | + Object.hasOwn(pkg.ccExtensibility, 'extends') && |
| 130 | + Object.hasOwn(pkg.ccExtensibility, 'overridesDir') && |
| 131 | + pkg.ccExtensibility.extends === '@salesforce/retail-react-app' && |
| 132 | + pkg.ccExtensibility.overridesDir === 'overrides' |
| 133 | + ) |
| 134 | + } |
| 135 | + |
| 136 | + expect(isValidConfig(validConfig)).toBe(true) |
| 137 | + expect(isValidConfig(invalidConfigMissingProperty)).toBe(false) |
| 138 | + expect(isValidConfig(invalidConfigWrongExtends)).toBe(false) |
| 139 | + }) |
| 140 | + |
| 141 | + test('validates template version matching logic', () => { |
| 142 | + const pkg = {version: '1.0.0'} |
| 143 | + |
| 144 | + const validateVersion = (pkg, templateVersion) => { |
| 145 | + return !templateVersion || pkg.version === templateVersion |
| 146 | + } |
| 147 | + |
| 148 | + expect(validateVersion(pkg, undefined)).toBe(true) |
| 149 | + expect(validateVersion(pkg, null)).toBe(true) |
| 150 | + expect(validateVersion(pkg, '1.0.0')).toBe(true) |
| 151 | + expect(validateVersion(pkg, '2.0.0')).toBe(false) |
| 152 | + }) |
| 153 | +}) |
0 commit comments