Skip to content

Commit

Permalink
#661 ALKiln in the Playground (#686)
Browse files Browse the repository at this point in the history
* Change cli, session_vars behavior for Playground testing

* Try to account for source files that are stored on S3
  • Loading branch information
plocket authored Apr 6, 2023
1 parent d1e8914 commit 4668760
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 79 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ jobs:
SECRET_VAR1: secret-var1-value
SECRET_VAR2: secret-var2-value
SECRET_FOR_MISSING_FIELD: secret for missing field
_ORIGIN: github

name: Run Puppeteer tests
steps:
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Format:
- added additional functionality to the sign method to allow developers to take a name argument to sign on canvas: issue #596
- A new script: `alkiln-run`, which acts like `npm run cucumber`, but can
be run in any directory, not just in an npm package.
- Additional environment variables and their validation to allow for tests that run on a developer's server/Playground instead of through GitHub. Also, other functionality for that purpose. [Issue #661](https://github.com/SuffolkLITLab/ALKiln/issues/661)
- Tests for new session_vars behavior and improve previous tests.

### Changed
- upgraded cucumber v8.6.0
Expand All @@ -51,6 +53,7 @@ Format:
- the github action no longer runs `npm run XYZ`; it directly calls scripts,
e.g. `alkiln-setup`, `alkiln-run`, `alkiln-takedown`
- don't print the ["publish this cucumber report" message](https://github.com/cucumber/cucumber-js/blob/main/docs/configuration.md#options)
- Adjusted validation of some environment variables to account for Playground vs. GitHub or local test runs. [Issue #661](https://github.com/SuffolkLITLab/ALKiln/issues/661)
- Projects created in da each have a unique name. https://github.com/SuffolkLITLab/ALKiln/issues/663
- Project name prefix now includes ALKiln in it for clarity

Expand Down
1 change: 1 addition & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ runs:
echo "DOCASSEMBLE_DEVELOPER_API_KEY=${{ inputs.DOCASSEMBLE_DEVELOPER_API_KEY }}" >> $GITHUB_ENV
echo "ALKILN_VERSION=${{ inputs.ALKILN_VERSION }}" >> $GITHUB_ENV
echo "MAX_SECONDS_FOR_SETUP=${{ inputs.MAX_SECONDS_FOR_SETUP }}" >> $GITHUB_ENV
echo "_ORIGIN=github" >> $GITHUB_ENV
shell: bash
- name: "ALKiln: confirm info"
run: |
Expand Down
18 changes: 12 additions & 6 deletions lib/docassemble/docassemble_api_REST.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ da.get_dev_id = async function ( timeout ) {
try {
return await axios.request( options );
} catch (error) {
throw {...error.toJSON(), data: error.response?.data};
let response = error.response || {data: null}
throw {...error.toJSON(), data: response.data};
}
}; // Ends da.get_dev_id()

Expand All @@ -48,7 +49,8 @@ da.create_project = async function ( project_name, timeout ) {
try {
return await axios.request( options );
} catch (error) {
throw {...error.toJSON(), data: error.response?.data};
let response = error.response || {data: null}
throw {...error.toJSON(), data: response.data};
}
}; // Ends da.create_project()

Expand All @@ -71,7 +73,8 @@ da.pull = async function ( timeout ) {
try {
return await axios.request( options );
} catch (error) {
throw {...error.toJSON(), data: error.response?.data};
let response = error.response || {data: null}
throw {...error.toJSON(), data: response.data};
}
}; // Ends da.pull()

Expand All @@ -92,7 +95,8 @@ da.has_task_finished = async function ( task_id, timeout ) {
try {
return await axios.request( options );
} catch (error) {
throw {...error.toJSON(), data: error.response?.data};
let response = error.response || {data: null}
throw {...error.toJSON(), data: response.data};
}
}; // Ends da.has_task_finished()

Expand All @@ -113,7 +117,8 @@ da.delete_project = async function ( timeout ) {
try {
return await axios.request( options );
} catch (error) {
throw {...error.toJSON(), data: error.response?.data};
let response = error.response || {data: null}
throw {...error.toJSON(), data: response.data};
}
}; // Ends da.delete_project()

Expand Down Expand Up @@ -142,7 +147,8 @@ da.__delete_projects_starting_with = async function ( base_name, starting_num=1,

await axios.request( options );
} catch ( error ) {
console.log( {...error.toJSON(), data: error.response?.data});
let response = error.response || {data: null}
console.log( {...error.toJSON(), data: response.data});
}
name_incrementor++;
}
Expand Down
35 changes: 28 additions & 7 deletions lib/docassemble/docassemble_api_interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,15 @@ da_i.throw_an_error_if_server_is_not_responding = async function ( options ) {

// node -e 'require("./lib/docassemble/docassemble_api_interface.js").is_server_responding()'
da_i.is_server_responding = async function ( dev_id_options ) {
/** Use _da_REST.get_dev_id() to test if server is up.
/** For non-playground interviews, use _da_REST.get_dev_id() to
* return if server is up. Otherwise, return true.
* See https://docassemble.org/docs/api.html#user. */

// Playground interview will itself timeout. No way to detect that.
if ( session_vars.get_origin() === 'playground' ) {
return true;
}

// Default timeouts
let { timeout } = dev_id_options || { timeout: DEFAULT_MAX_REQUEST_MS };
try {
Expand All @@ -141,13 +147,28 @@ da_i.is_server_responding = async function ( dev_id_options ) {

// node -e 'require("./lib/docassemble/docassemble_api_interface.js").get_dev_id()'
da_i.get_dev_id = async function ( dev_id_options ) {
/** For consistency, wrap _da_REST.get_dev_id().
* See https://docassemble.org/docs/api.html#user. */
/** Get user id, either from the server or from env vars (for Playground runs).
* See https://docassemble.org/docs/api.html#user.
*
* @param dev_id_options {obj} Options for getting dev_id
* @param dev_id_options.timeout {int} Required if options is defined. Max MS
* to allow for server request.
*/

// Default timeouts
let { timeout } = dev_id_options || { timeout: DEFAULT_MAX_REQUEST_MS };
let response = await _da_REST.get_dev_id( timeout );
return response.data.id;
let dev_id = null;

// For tests running in the dev Playground, use the appropriate env var
if ( session_vars.get_origin() === 'playground' ) {
dev_id = session_vars.get_user_id();

} else {
// Default timeouts
let { timeout } = dev_id_options || { timeout: DEFAULT_MAX_REQUEST_MS };
let response = await _da_REST.get_dev_id( timeout );
dev_id = response.data.id;
}

return dev_id;
}; // Ends da_i.get_dev_id();


Expand Down
33 changes: 23 additions & 10 deletions lib/run_cucumber.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ const cucumber_api = require('@cucumber/cucumber/api');
(async function main() {
let argv = process.argv;

let just_args = argv.slice(2);

const environment = { cwd: process.cwd() };
console.log("alkiln-run: environment: %o", environment);
const provided_config = {
Expand All @@ -20,21 +18,36 @@ const cucumber_api = require('@cucumber/cucumber/api');
"paths": ["docassemble/*/data/sources/*.feature"],
"retry": 1,
}
if (just_args.length > 0) {


// If they're running it from an interview on their server instead of from GitHub
if ( session_vars.get_origin() === `playground` ) {
provided_config.paths.push(`/usr/share/docassemble/files/playgroundsources/${session_vars.get_user_id()}/${session_vars.get_user_project_name()}/*.feature`);
// For S3
provided_config.paths.push(`/tmp/playgroundsources/${session_vars.get_user_id()}/${session_vars.get_user_project_name()}/*.feature`);
}

let tags = argv.slice(2);
if ( session_vars.get_origin() === `playground` ) {
tags = session_vars.get_tags();
}

if (tags.length > 0) {
// Cucumber expects tags in boolean expressions, like (@a0 or @a1) and @random.
// If we get a list of only tags, then "or" them all together
if (just_args.every((val) => val.startsWith("@"))) {
provided_config.tags = just_args.join(' or ');
if (tags.every((val) => val.startsWith("@"))) {
provided_config.tags = tags.join(' or ');
}
// If we get a list of seemingly no tags, assume they meant to use tags
else if (just_args.every((val) => !val.startsWith("@"))) {
provided_config.tags = just_args.map((val) => "@" + val).join(' or ');
else if (tags.every((val) => !val.startsWith("@"))) {
provided_config.tags = tags.map((val) => "@" + val).join(' or ');
}
// If it's a mix of tags and no tags, it's probably a boolean expression!
else {
provided_config.tags = just_args.join(' ');
provided_config.tags = tags.join(' ');
}
}

const { runConfiguration } = await cucumber_api.loadConfiguration({
provided: provided_config
}, environment);
Expand All @@ -47,7 +60,7 @@ const cucumber_api = require('@cucumber/cucumber/api');
importPaths: [],
}, sources: {paths: []}}, environment);

console.log("alkiln-run: codeDir: %o", codeDir);
console.log("alkiln-run: working directory: %o", codeDir);

const { success } = await cucumber_api.runCucumber({...runConfiguration, support});
console.log("alkiln-run: success: %b", success);
Expand All @@ -71,4 +84,4 @@ const cucumber_api = require('@cucumber/cucumber/api');
let artifacts_path = session_vars.get_artifacts_path_name();
fs.appendFileSync( `${ artifacts_path}/${ log.debug_log_file }`, data);
});
})();
})();
9 changes: 8 additions & 1 deletion lib/steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,14 @@ BeforeAll(async () => {
Before(async (scenario) => {
// Create browser, which can't be created in BeforeAll() where .driver doesn't exist for some reason
if (!scope.browser) {
scope.browser = await scope.driver.launch({ headless: !session_vars.get_debug(), devtools: session_vars.get_debug() });
if (session_vars.get_origin() === 'playground') {
// Will only run in the Playground outside of a sandbox. TODO: There's a
// better way to do this, though it's more complicated. See comments in
// https://github.com/SuffolkLITLab/ALKiln/issues/661
scope.browser = await scope.driver.launch({ args: ['--no-sandbox'] });
} else {
scope.browser = await scope.driver.launch({ headless: !session_vars.get_debug(), devtools: session_vars.get_debug() });
}
}
// Clean up all previously existing pages
for (const page of await scope.browser.pages()) {
Expand Down
114 changes: 78 additions & 36 deletions lib/utils/session_vars.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,27 @@ module.exports = session_vars;
// These default to null
// TODO: How to isolate debug elsewhere so this file can use the logger?
session_vars.get_debug = function () { return process.env.DEBUG || null; };
session_vars.is_dev_env = function () { return process.env.DEV || null; }

session_vars.get_dev_api_key = function () { return process.env.DOCASSEMBLE_DEVELOPER_API_KEY || null; };
session_vars.get_repo_url = function () { return process.env.REPO_URL || null; };
session_vars.is_dev_env = function () { return process.env.DEV || null; }

session_vars.get_origin = function () {
/** The location from which the tests are being run. Possible
* valid return values: 'playground', 'github', or 'local'.
* Default is 'local' for ease of our own onboarding process.
*/
return process.env._ORIGIN || 'local';
};
session_vars.get_user_project_name = function () { return process.env._PROJECT_NAME || null; };
session_vars.get_user_id = function () { return process.env._USER_ID || null; };
session_vars.get_tags = function () {
if ( process.env._TAGS ) {
return process.env._TAGS.split(` `);
} else {
return [];
}
};

// More complex logic
session_vars.get_server_reload_timeout = function () {
Expand Down Expand Up @@ -130,7 +148,10 @@ session_vars.save_project_name = function ( project_name ) {
}; // Ends save_project_name()

session_vars.get_project_name = function () {
/* Get the name of the current docassemble Project */
/* Get the name of the current docassemble Project */
if ( session_vars.get_origin() === 'playground' ) {
return session_vars.get_user_project_name();
}
let json = JSON.parse( fs.readFileSync( runtime_config_path ));
let project_name = json[ project_name_key ] || null;
if ( session_vars.get_debug() ) { console.log( `ALKiln debug: Project name from file is "${ project_name }".` ); }
Expand Down Expand Up @@ -174,7 +195,7 @@ session_vars.save_artifacts_path_name = function ( path_name ) {
console.log( `ALKiln debug: Stored name of artifacts directory "${ path_name }" locally.` );
}
return file;
}; // Ends get_project_name()
}; // Ends save_artifacts_path_name()

session_vars.get_artifacts_path_name = function () {
/* Get the name of the current artifacts path. */
Expand All @@ -187,48 +208,69 @@ session_vars.get_artifacts_path_name = function () {
} catch ( error ) {
return null;
}
}; // Ends get_project_name()

/**
* Ensure all required environment variables exist. Add each missing variable
* to an error that will be thrown at the end.
*
* @throws ReferenceError with a message for all missing variables.
*/
session_vars.validateEnvironment = function() {
/** Throw a useful error for the developer if any of the required env vars
* are missing. For most devs, these will be GitHub secrets. */

// Note: We are checking for BASE_URL, but we are referring it as SERVER_URL as that
// is the name used in GitHub secrets, which is what most devs will be using.
}; // Ends get_artifacts_path_name()

session_vars.validateEnvironment = function() {
/** Throw a useful error for the developer if any of the env vars
* required by absolutely all tests are missing. Gather all
* missing var names into one message to ease dev process.
*
* Rational for doing it all in here:
* 1. Easier to test
* 2. Will avoid piecemeal errors in separate locations
*
* TODO: Also validate format of variables?
*
* @throws ReferenceError with a message for all missing variables.
*/
let errorMessage = '';

// Since this code will most often be run in GitHub, I'm going to build a single error message that outlines all missing variables. Otherwise, a developer
// will have to run their pipeline an incredible number of times to discover all the missing variables.
if ( !session_vars.get_dev_api_key() )
{
errorMessage += `\nThe DOCASSEMBLE_DEVELOPER_API_KEY GitHub secret must be defined. The DOCASSEMBLE_DEVELOPER_API_KEY is a docassemble API key you created for your server's testing account. It should have developer permissions.`
}
if ( !session_vars.get_da_server_url() )
{
// Env vars required for any tests
if ( !session_vars.get_da_server_url() ) {
errorMessage += `\nThe SERVER_URL GitHub secret must be defined. The SERVER_URL is the URL of the Docassemble Server where the tests should be run.`;
}
if ( !session_vars.get_repo_url() )
{
errorMessage += `\nThe REPO_URL GitHub secret must be defined. The REPO_URL is the URL to the Git repo whose Actions are being run.`;
}
// The branch name is always automatically defined in Github, it'll be missing only if you're running locally and missing an env var.
if ( !session_vars.get_branch_name() )
{
errorMessage += `\nThe BRANCH_NAME environment variable must be defined. The BRANCH_NAME is the name of the repository branch that should be pulled into the docassemble project.`;
}

let origin = session_vars.get_origin();
if ( origin !== 'github' && origin !== 'local' && origin !== 'playground' ) {
errorMessage += `\nThe _ORIGIN environment variable should be set to a valid value automatically. If you see this error, file an issue at https://github.com/suffolkLITLab/docassemble-ALKilnInThePlayground. It must be defined as either 'playground', 'github', or 'local', but its value was ${origin}`;

// If origin has one of the allowed values, we can do the rest of the validations
} else {

// Env vars required for github or local runs, as both create Projects
// in the Playground and pull the repo from GitHub
if ( origin === 'github' || origin === 'local' ) {
if ( !session_vars.get_dev_api_key() ) {
errorMessage += `\nThe DOCASSEMBLE_DEVELOPER_API_KEY GitHub secret must be defined. The DOCASSEMBLE_DEVELOPER_API_KEY is a docassemble API key you created for your server's testing account. It should have developer permissions.`
}
// Github automatically gives this, but may be missing locally.
if ( !session_vars.get_repo_url() ) {
errorMessage += `\nThe REPO_URL environment variable must be defined. The REPO_URL is the URL to the GitHub repo whose Actions are being run.`;
}
// Github automatically gives this, but may be missing locally.
if ( !session_vars.get_branch_name() ) {
errorMessage += `\nThe BRANCH_NAME environment variable must be defined. The BRANCH_NAME is the name of the repository branch that should be pulled into the docassemble project.`;
}
} // ends github and local env vars

// We're the ones ensuring these exist for Playground runs
if ( origin === 'playground' ) {
if ( !session_vars.get_user_project_name() ) {
errorMessage += `\nThe _PROJECT_NAME environment variable should automatically exist. If you see this error, file an issue at https://github.com/suffolkLITLab/docassemble-ALKilnInThePlayground. It is the name of the Project the developer chose to test.`
}
if ( !session_vars.get_user_id() ) {
errorMessage += `\nThe _USER_ID environment variable should automatically exist. If you see this error, file an issue at https://github.com/suffolkLITLab/docassemble-ALKilnInThePlayground. It is the user ID of the developer on the server.`
}
// Tags are optional
} // ends Playground env vars

} // ends validation based on origin

errorMessage = errorMessage.trim();

if (errorMessage !== '') {
throw new ReferenceError(errorMessage);
throw new ReferenceError(`ALKiln error: ${errorMessage}`);
}
};

session_vars.validateEnvironment();
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@suffolklitlab/alkiln",
"version": "4.11.1",
"version": "5.0.0-playground-2",
"description": "Integrated automated end-to-end testing with docassemble, puppeteer, and cucumber.",
"main": "lib/index.js",
"scripts": {
Expand Down
Loading

0 comments on commit 4668760

Please sign in to comment.