From d9dc112ef001eec7d76f5bc5af363d497e3825b0 Mon Sep 17 00:00:00 2001 From: Kyle Capehart Date: Fri, 4 Oct 2024 14:22:24 -0400 Subject: [PATCH] add tests, format code --- README.md | 35 ++---- package.json | 2 +- src/commands/kc/update-api.ts | 9 +- src/utils/xmlParser.ts | 92 +++++++------- test/commands/kc/update-api.nut.ts | 20 --- test/commands/kc/update-api.test.ts | 117 +++++++++++++----- test/xml/classes/AccountHelper.cls-meta.xml | 5 + .../classes/AccountHelper_Test.cls-meta.xml | 5 + test/xml/flows/Test_Opp_Flow.flow-meta.xml | 5 + .../triggers/AccountTrigger.trigger-meta.xml | 5 + 10 files changed, 167 insertions(+), 128 deletions(-) delete mode 100644 test/commands/kc/update-api.nut.ts create mode 100644 test/xml/classes/AccountHelper.cls-meta.xml create mode 100644 test/xml/classes/AccountHelper_Test.cls-meta.xml create mode 100644 test/xml/flows/Test_Opp_Flow.flow-meta.xml create mode 100644 test/xml/triggers/AccountTrigger.trigger-meta.xml diff --git a/README.md b/README.md index 46d10d4..3776e66 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,13 @@ This plugin is bundled with the [Salesforce CLI](https://developer.salesforce.com/tools/sfdxcli). For more information on the CLI, read the [getting started guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_intro.htm). +## Contents + +- [Install](#install) +- [Trigger-Framework](#trigger-framework) +- [List of Commands](#list-of-commands) +- [Build](#build) + ## Install ```bash @@ -90,33 +97,7 @@ Running the command: `sf kc trigger-framework --custom-template templates/ --sob For more template examples: https://github.com/k-capehart/kc-sf-plugin/tree/main/src/templates/ -## API Version Management - -Automatically update the API version for Salesforce components such as Apex classes, triggers, and flows. Specify a component type (or multiple) and a minimum API version that should be satisfied. - -For example, if you want to update all classes, triggers, and flows to be at least version 61.0, then run the following command within a SFDX directory. - -`sf kc update-api --type classes --type triggers --type flows --api-version 61.0` - -Specifying deprecated API versions will throw an error. - -## Preview Project Diff - -Combine the output of a retrieval preview and deploy preview into one command. The org must have source tracking enabled. Source tracking isn’t supported and can’t be enabled for Partial Copy sandboxes, Full sandboxes, or Developer Edition orgs. - -https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_setup_enable_source_tracking_sandboxes.htm - -Instead of running both of these commands: - -`sf project deploy preview` - -`sf project retrieve preview` - -You can now combine the output into one command: - -`sf kc diff` - -## Commands +## List of Commands diff --git a/package.json b/package.json index 59ca904..e07e6b0 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ ] }, "compile": { - "command": "tsc -p . --pretty --incremental && cp -r src/templates/ lib/templates/", + "command": "tsc -p . --pretty --incremental", "files": [ "src/**/*.ts", "**/tsconfig.json", diff --git a/src/commands/kc/update-api.ts b/src/commands/kc/update-api.ts index 26d1c66..6090353 100644 --- a/src/commands/kc/update-api.ts +++ b/src/commands/kc/update-api.ts @@ -38,17 +38,17 @@ export default class KcUpdateApi extends SfCommand { public async run(): Promise { const { flags } = await this.parse(KcUpdateApi); + let updatedNumber = 0; const targetDir = flags['target-dir']; if (!fs.existsSync(targetDir)) { this.warn(targetDir + ' does not exist'); return { - updatedNumber: 0, + updatedNumber, }; } const apiVersion = flags['api-version']; - let updatedNumber = 0; // multiple types can be specified, so loop through all of them flags['type'].forEach((type) => { @@ -56,15 +56,14 @@ export default class KcUpdateApi extends SfCommand { if (!fs.existsSync(componentDir)) { this.warn(componentDir + ' does not exist'); return { - updatedNumber: 0, + updatedNumber, }; } - updatedNumber = updatedNumber + updateAPIVersion(componentDir, apiVersion); }); return { - updatedNumber: 0, + updatedNumber, }; } } diff --git a/src/utils/xmlParser.ts b/src/utils/xmlParser.ts index 88bd167..c84a45b 100644 --- a/src/utils/xmlParser.ts +++ b/src/utils/xmlParser.ts @@ -7,19 +7,19 @@ type ApexClassXml = { ApexClass: { apiVersion: string; }; -} +}; type ApexTriggerXml = { ApexTrigger: { apiVersion: string; }; -} +}; type FlowXml = { Flow: { apiVersion: string; }; -} +}; type XmlData = ApexClassXml | ApexTriggerXml | FlowXml; @@ -35,55 +35,55 @@ const builderOptions = { }; export const updateAPIVersion = (targetDir: string, version: string): number => { - fs.readdir(targetDir, (err, files) => { - if (err) { - ux.error('Error reading directory:', err); - } + let files: string[]; + try { + files = fs.readdirSync(targetDir); + } catch (error: unknown) { + ux.error('Error reading directory:', error as Error); + } - const parser = new XMLParser(parserOptions); - const builder = new XMLBuilder(builderOptions); - const updatedNumber = files.length; + const parser = new XMLParser(parserOptions); + const builder = new XMLBuilder(builderOptions); + let updatedNumber = 0; - try { - for (const file of files) { - if (path.extname(file) === '.xml') { - const filePath = path.join(targetDir, file); - const data = fs.readFileSync(filePath, 'utf-8'); - const component = parser.parse(data) as XmlData; - let isUpdated = false; + try { + for (const file of files) { + if (path.extname(file) === '.xml') { + const filePath = path.join(targetDir, file); + const data = fs.readFileSync(filePath, 'utf-8'); + const component = parser.parse(data) as XmlData; + let isUpdated = false; - if ('ApexClass' in component) { - if (parseFloat(component.ApexClass.apiVersion) < parseFloat(version)) { - component.ApexClass.apiVersion = version; - isUpdated = true; - } - } else if ('ApexTrigger' in component) { - if (parseFloat(component.ApexTrigger.apiVersion) < parseFloat(version)) { - ux.stdout(parseFloat(component.ApexTrigger.apiVersion).toString()); - component.ApexTrigger.apiVersion = version; - isUpdated = true; - } - } else if ('Flow' in component) { - if (parseFloat(component.Flow.apiVersion) < parseFloat(version)) { - component.Flow.apiVersion = version; - isUpdated = true; - } + if ('ApexClass' in component) { + if (parseFloat(component.ApexClass.apiVersion) < parseFloat(version)) { + component.ApexClass.apiVersion = version; + isUpdated = true; } - - if (isUpdated) { - const updatedXml = builder.build(component) as string; - // remove blank lines - const cleanedXml = (updatedXml.replace(/\n\s*\n/g, '\n')); - fs.writeFileSync(filePath, cleanedXml); + } else if ('ApexTrigger' in component) { + if (parseFloat(component.ApexTrigger.apiVersion) < parseFloat(version)) { + ux.stdout(parseFloat(component.ApexTrigger.apiVersion).toString()); + component.ApexTrigger.apiVersion = version; + isUpdated = true; } + } else if ('Flow' in component) { + if (parseFloat(component.Flow.apiVersion) < parseFloat(version)) { + component.Flow.apiVersion = version; + isUpdated = true; + } + } + + if (isUpdated) { + const updatedXml = builder.build(component) as string; + // remove blank lines + const cleanedXml = updatedXml.replace(/\n\s*\n/g, '\n'); + fs.writeFileSync(filePath, cleanedXml); + updatedNumber++; } } - } catch (error: unknown) { - ux.error(error as Error); } + } catch (error: unknown) { + ux.error(error as Error); + } - return updatedNumber; - }); - - return 0; -}; \ No newline at end of file + return updatedNumber; +}; diff --git a/test/commands/kc/update-api.nut.ts b/test/commands/kc/update-api.nut.ts deleted file mode 100644 index 3fceff9..0000000 --- a/test/commands/kc/update-api.nut.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit'; -import { expect } from 'chai'; - -describe('kc update-api NUTs', () => { - let session: TestSession; - - before(async () => { - session = await TestSession.create({ devhubAuthStrategy: 'NONE' }); - }); - - after(async () => { - await session?.clean(); - }); - - it('should run command', () => { - const command = 'kc update-api --type classes --api-version 61.0'; - const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout; - expect(output).to.contain(0); - }); -}); diff --git a/test/commands/kc/update-api.test.ts b/test/commands/kc/update-api.test.ts index f0af2e0..21879a1 100644 --- a/test/commands/kc/update-api.test.ts +++ b/test/commands/kc/update-api.test.ts @@ -1,40 +1,99 @@ -import { TestContext } from '@salesforce/core/testSetup'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; +import * as fs from 'node:fs'; import { expect } from 'chai'; -import { stubSfCommandUx } from '@salesforce/sf-plugins-core'; +import { ux } from '@oclif/core'; import KcUpdateApi from '../../../src/commands/kc/update-api.js'; describe('kc update-api', () => { - const $$ = new TestContext(); - let sfCommandStubs: ReturnType; - - beforeEach(() => { - sfCommandStubs = stubSfCommandUx($$.SANDBOX); + it('runs update-api and throws error if no api-version is provided', async () => { + try { + const output = await KcUpdateApi.run(['--type', 'classes']); + expect.fail(`Should throw an error. Response: ${JSON.stringify(output)}`); + } catch (e) { + expect((e as Error).message).to.include('Missing required flag api-version'); + } }); - afterEach(() => { - $$.restore(); + it('runs update-api and throws error if no type is provided', async () => { + try { + const output = await KcUpdateApi.run(['--api-version', '61.0']); + expect.fail(`Should throw an error. Response: ${JSON.stringify(output)}`); + } catch (e) { + expect((e as Error).message).to.include('Missing required flag type'); + } }); - it('runs update-api', async () => { - await KcUpdateApi.run(['--type', 'classes', '--api-version', '61.0']); - const output = sfCommandStubs.log - .getCalls() - .flatMap((c) => c.args) - .join('\n'); - expect(output).to.include(''); + it('runs update-api and throws error if invalid api version given', async () => { + try { + const output = await KcUpdateApi.run(['--type', 'classes', '--api-version', '10.0']); + expect.fail(`Should throw an error. Response: ${JSON.stringify(output)}`); + } catch (e) { + expect((e as Error).message).to.include('The API version must be greater than'); + } }); - // it('runs hello with --json and no provided name', async () => { - // const result = await KcUpdateApi.run([]); - // expect(result.updatedNumber).to.equal(0); - // }); - - // it('runs hello world --name Astro', async () => { - // await KcUpdateApi.run(['--name', 'Astro']); - // const output = sfCommandStubs.log - // .getCalls() - // .flatMap((c) => c.args) - // .join('\n'); - // expect(output).to.include('hello Astro'); - // }); + describe('update xml files', () => { + const filename = fileURLToPath(import.meta.url); + const fileDirName = dirname(filename); + const xmlFilesPath = join(fileDirName, '../../xml'); + const xmlBackupPath = join(fileDirName, '../../xmlBackup'); + + beforeEach(async () => { + try { + fs.cpSync(xmlFilesPath, xmlBackupPath, { recursive: true }); + if (!fs.existsSync(xmlBackupPath)) { + throw new Error(`Backup XML directory does not exist: ${xmlFilesPath}`); + } + } catch (error: unknown) { + ux.error(error as Error); + } + }); + + afterEach(async () => { + try { + fs.rmSync(xmlFilesPath, { recursive: true, force: true }); + fs.cpSync(xmlBackupPath, xmlFilesPath, { recursive: true }); + fs.rmSync(xmlBackupPath, { recursive: true, force: true }); + } catch (error: unknown) { + ux.error(error as Error); + } + }); + + it('runs update-api to update 2 apex classes', async () => { + const result = await KcUpdateApi.run(['-d', xmlFilesPath, '--type', 'classes', '--api-version', '61.0']); + expect(result.updatedNumber).to.equal(2); + }); + + it('runs update-api to update 1 apex class', async () => { + const result = await KcUpdateApi.run(['-d', xmlFilesPath, '--type', 'classes', '--api-version', '58.0']); + expect(result.updatedNumber).to.equal(1); + }); + + it('runs update-api to update 1 trigger', async () => { + const result = await KcUpdateApi.run(['-d', xmlFilesPath, '--type', 'triggers', '--api-version', '60.0']); + expect(result.updatedNumber).to.equal(1); + }); + + it('runs update-api to update 1 flow', async () => { + const result = await KcUpdateApi.run(['-d', xmlFilesPath, '--type', 'flows', '--api-version', '60.0']); + expect(result.updatedNumber).to.equal(1); + }); + + it('runs update-api to update classes, triggers, and flows', async () => { + const result = await KcUpdateApi.run([ + '-d', + xmlFilesPath, + '--type', + 'classes', + '--type', + 'triggers', + '--type', + 'flows', + '--api-version', + '60.0', + ]); + expect(result.updatedNumber).to.equal(4); + }); + }); }); diff --git a/test/xml/classes/AccountHelper.cls-meta.xml b/test/xml/classes/AccountHelper.cls-meta.xml new file mode 100644 index 0000000..50db14e --- /dev/null +++ b/test/xml/classes/AccountHelper.cls-meta.xml @@ -0,0 +1,5 @@ + + + 57.0 + Active + \ No newline at end of file diff --git a/test/xml/classes/AccountHelper_Test.cls-meta.xml b/test/xml/classes/AccountHelper_Test.cls-meta.xml new file mode 100644 index 0000000..312966f --- /dev/null +++ b/test/xml/classes/AccountHelper_Test.cls-meta.xml @@ -0,0 +1,5 @@ + + + 58.0 + Active + \ No newline at end of file diff --git a/test/xml/flows/Test_Opp_Flow.flow-meta.xml b/test/xml/flows/Test_Opp_Flow.flow-meta.xml new file mode 100644 index 0000000..e52ddd7 --- /dev/null +++ b/test/xml/flows/Test_Opp_Flow.flow-meta.xml @@ -0,0 +1,5 @@ + + + 58.0 + Active + \ No newline at end of file diff --git a/test/xml/triggers/AccountTrigger.trigger-meta.xml b/test/xml/triggers/AccountTrigger.trigger-meta.xml new file mode 100644 index 0000000..d5b6632 --- /dev/null +++ b/test/xml/triggers/AccountTrigger.trigger-meta.xml @@ -0,0 +1,5 @@ + + + 58.0 + Active + \ No newline at end of file