Skip to content

Commit

Permalink
feat: add TestProject, zipDir, and session updates
Browse files Browse the repository at this point in the history
  • Loading branch information
shetzel committed Feb 3, 2021
1 parent c17a39d commit 4b013ad
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 26 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"@salesforce/kit": "^1.3.4",
"@salesforce/ts-types": "^1.4.4",
"debug": "^4.3.1",
"shelljs": "^0.8.4"
"shelljs": "^0.8.4",
"archiver": "^5.2.0"
},
"devDependencies": {
"@salesforce/dev-config": "^2.0.0",
Expand All @@ -62,6 +63,7 @@
"@types/debug": "^4.1.5",
"@types/shelljs": "^0.8.8",
"@types/sinon": "^9.0.10",
"@types/archiver": "^5.1.0",
"@typescript-eslint/eslint-plugin": "^4.14.1",
"@typescript-eslint/parser": "^4.14.1",
"chai": "^4.2.0",
Expand Down
36 changes: 25 additions & 11 deletions src/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,20 @@ import { debug, Debugger } from 'debug';
import * as shell from 'shelljs';
import { genUniqueString } from './genUniqueString';
import { execCmd } from './execCmd';
import { zipDir } from './zip';

export interface TestProjectConfig {
sourceDir?: string;
gitClone?: string;
name?: string;
dir?: string;
destinationDir?: string;
}

/**
* A SFDX project for use with testing.
* A SFDX project for use with testing. The project can be defined by:
* 1. Copied from a project on the filesystem to a destination dir
* 2. Cloned using a git url
* 3. Created by name using the force:project:create command
*/
export class TestProject {
public createdDate: Date;
Expand All @@ -30,19 +34,23 @@ export class TestProject {
this.debug = debug('testkit:project');
this.createdDate = new Date();

const dir = options.dir || path.join(process.cwd(), 'tmp');
const dir = options.destinationDir || path.join(process.cwd(), 'tmp');

// Copy a dir containing a SFDX project to a dir for testing.
if (options?.sourceDir) {
shell.cp(options.sourceDir, dir);
const rv = shell.cp(options.sourceDir, dir);
this.debug('project copy result=', rv);
if (rv.code !== 0) {
throw new Error(`project copy failed \n${rv.stderr}`);
}
this.path = path.join(dir, path.dirname(options.sourceDir));
}
// Clone a git repo containing a SFDX project in a dir for testing.
else if (options?.gitClone) {
const rc = shell.exec(`git clone ${options.gitClone} ${dir}`, { silent: true });
this.debug('git clone rc=', rc);
if (rc.code !== 0) {
throw new Error(`git clone failed \n${rc.stderr}`);
const rv = shell.exec(`git clone ${options.gitClone} ${dir}`, { silent: true });
this.debug('git clone result=', rv);
if (rv.code !== 0) {
throw new Error(`git clone failed \n${rv.stderr}`);
}
this.path = path.join(dir, 'changeme');
}
Expand All @@ -56,9 +64,15 @@ export class TestProject {
}

/**
* Return a list of org usernames this project knows about.
* Zip the test project contents
*
* @name The name of the zip file to create. Default is the project dirname.
* @destDir The zip file will be written to this path. Default is `process.cwd()`.
* @returns The created zip file path.
*/
public getOrgs(): string {
return 'NOT YET IMPLEMENTED';
public async zip(name?: string, destDir?: string): Promise<string> {
name ??= path.dirname(this.path);
destDir ??= process.cwd();
return zipDir({ name, sourceDir: this.path, destDir });
}
}
34 changes: 24 additions & 10 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,24 @@ import { fs as fsCore } from '@salesforce/core';
import { env } from '@salesforce/kit';
import { createSandbox, SinonStub } from 'sinon';
import { genUniqueString } from './genUniqueString';
import { zipDir } from './zip';
import { TestProject, TestProjectConfig } from './project';

export interface SessionOptions {
sessionDir?: string;
project?: TestProjectConfig;
sessionDir?: string; // if a plugin dev wants to control where a test session lives
project?: TestProjectConfig; // Define a test project to use for tests
verifyEnv?: string[]; // ensure your test has env vars required to run successfully???
setupCommands?: string[]; // run these commands before your tests run. all must have exit code 0;
}

/*
*** ENV VARS ***
TESTKIT_SESSION_DIR = path to a directory for all testkit artifacts to live
TESTKIT_SFDX_DIR = path to a .sfdx directory
TESTKIT_SFDX_DIR = path to a .sfdx directory that the tests should use.
TESTKIT_ORG_USERNAME = an org username to use for test commands. tests will use this org rather than creating new orgs.
TESTKIT_PROJECT_DIR = a SFDX project to use for testing. the tests will use this project directly.
TESTKIT_SAVE_ARTIFACTS = prevents a test session from deleting orgs
TESTKIT_ZIP_SESSION = zips the session dir when a test fails; enum of ON_ERROR | ALWAYS
*/

// Map of existing test sessions
Expand Down Expand Up @@ -97,26 +104,33 @@ export class Session {
}

/**
* Clean the test session by restoring the sandbox and
* deleting all orgs created during the test.
* Clean the test session by restoring the sandbox, deleting
* all orgs created during the test, and deleting the test session dir.
*/
public clean(): void {
this.sandbox.restore();
// delete test orgs unless TESTKIT_SAVE_ARTIFACTS set
// this.projects.
// delete the test session
}

/**
* Zip the test session contents
* Zip the contents of a test session directory.
*
* @name The name of the zip file to create. Default is the test session dirname.
* @destDir The zip file will be written to this path. Default is `../this.dir`.
* @returns The created zip file path.
*/
public zip(): string {
return 'NOT YET IMPLEMENTED';
public zip(name?: string, destDir?: string): Promise<string> {
name ??= path.dirname(this.dir);
destDir ??= path.resolve('..', this.dir);
return zipDir({ name, sourceDir: this.dir, destDir });
}

/**
* Add another SFDX project to the test session.
*
* @param config a test project config to use for the new project
* @param config a test project config to use for the new project.
* @returns The added TestProject
*/
public addProject(config: TestProjectConfig): TestProject {
const projConfig = { ...{ dir: this.dir }, ...config };
Expand Down
57 changes: 57 additions & 0 deletions src/zip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2021, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import * as path from 'path';
import { create as createArchive } from 'archiver';
import { debug as Debug } from 'debug';
import { fs as fsCore } from '@salesforce/core';

interface ZipDirConfig {
sourceDir: string;
destDir: string;
name: string;
}

/**
* Zip the contents of a directory to a file.
*
* @param config what and where to zip
* @returns The created zip file path
*/
export const zipDir = async (config: ZipDirConfig): Promise<string> => {
const debug = Debug('testkit:zipDir');
const { sourceDir, destDir, name } = config;
const zip = createArchive('zip', { zlib: { level: 3 } });
const zipFilePath = path.join(destDir, name);
const output = fsCore.createWriteStream(zipFilePath);
debug(`Zipping contents of ${sourceDir} to ${zipFilePath}`);

return new Promise((resolve, reject) => {
output.on('close', () => {
debug(`Zip ${zipFilePath} is closed`);
});
output.on('end', () => {
debug(`Zip data has drained for ${zipFilePath}`);
resolve(zipFilePath);
});
zip.on('warning', (err) => {
if (err.code === 'ENOENT') {
debug(`Zip warning for ${zipFilePath}\n${err.message}`);
} else {
reject(err);
}
});
zip.on('error', (err) => {
reject(err);
});
zip.pipe(output);
zip.directory(sourceDir, path.dirname(sourceDir));
zip.finalize().catch((err: unknown) => {
debug(`Zip finalize error with: ${(err as Error).message}`);
});
});
};
Loading

0 comments on commit 4b013ad

Please sign in to comment.