@suffolklitlab/alkiln 5.13.2 → 5.13.3-feat-random-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.github/workflows/github_server.yml +10 -8
  2. package/.github/workflows/playground.yml +5 -2
  3. package/.github/workflows/unit_tests.yml +1 -1
  4. package/CHANGELOG.md +32 -1
  5. package/_alkiln_meta/shift_logs.js +75 -0
  6. package/action.yml +89 -31
  7. package/action_for_github_server/action.yml +65 -29
  8. package/docs/4_environments.md +243 -0
  9. package/docs/artifacts_folder_name.md +135 -0
  10. package/docs/constrained_random_tests.md +254 -0
  11. package/docs/decisions/006_which_log_level_to_pick_2024_09_5.md +70 -0
  12. package/docs/decisions/007_getting_and_storing_logs.md +179 -0
  13. package/docs/decisions/move_runtime_config_to_sources_folder.md +5 -0
  14. package/docs/decisions/reports_and_logs_2024_08_11.md +161 -0
  15. package/docs/kinds_of_errors.md +26 -0
  16. package/docs/prioritize_constrained_random_tests.md +60 -0
  17. package/lib/docassemble/docassemble_api_interface.js +77 -51
  18. package/lib/docassemble/server_install.js +32 -9
  19. package/lib/docassemble/setup.js +40 -25
  20. package/lib/docassemble/takedown.js +33 -10
  21. package/lib/globals.js +10 -0
  22. package/lib/run_cucumber.js +141 -69
  23. package/lib/scope.js +101 -77
  24. package/lib/setup/TestGenerator.js +1755 -0
  25. package/lib/setup/artifacts.js +20 -0
  26. package/lib/setup/generate.js +42 -0
  27. package/lib/setup/get_Gherkin_AST.js +26 -0
  28. package/lib/setup/validate.js +89 -0
  29. package/lib/steps.js +132 -54
  30. package/lib/utils/Log.js +1145 -0
  31. package/lib/utils/_alkiln-constrained_random_tests_old.js +1776 -0
  32. package/lib/utils/files.js +18 -11
  33. package/lib/utils/get_error_stacks.js +16 -0
  34. package/lib/utils/reports.js +100 -34
  35. package/lib/utils/session_vars.js +107 -73
  36. package/lib/utils/set_sources_paths.js +98 -0
  37. package/lib/utils/time.js +44 -0
  38. package/package.json +18 -7
  39. package/tests/log_codes/check_codes.sh +18 -13
  40. package/tests/log_codes/log_code_expected_instances.txt +91 -9
  41. package/tests/unit_tests/constrained_random_answers/invalid.fixtures.js +177 -0
  42. package/tests/unit_tests/constrained_random_answers/invalid.test.js +251 -0
  43. package/tests/unit_tests/constrained_random_answers/valid.fixtures.js +936 -0
  44. package/tests/unit_tests/constrained_random_answers/valid.test.js +541 -0
  45. package/tests/unit_tests/example_of_chai_test_lifecycle.js +78 -0
  46. package/tests/unit_tests/getAllFields.test.js +5 -0
  47. package/tests/unit_tests/getMatchingRows.test.js +5 -0
  48. package/tests/unit_tests/log.test.js +557 -0
  49. package/tests/unit_tests/normalizeTable.test.js +5 -0
  50. package/lib/utils/log.js +0 -96
  51. /package/docs/{expected_inernal_results.md → expected_internal_results.md} +0 -0
@@ -0,0 +1,26 @@
1
+ # Kinds of Errors
2
+
3
+ An error, or even an `Error` instance, can mean a lot of different things in the context of this testing framework. Throwing errors for failing tests is what we are most familiar with, but it is just one kind of error.
4
+
5
+ ## Interview `Error`s
6
+
7
+ 1. **Interview bugs**: For example, the interview says ALKiln's answer is invalid. The author expected that answer to be valid. ALKiln saves an error message to the report and throws an error to tell cucumber to mark the test as failed. Currently, these logs errors come with the 🤕 icon. When all the tests are done, the test suite will show as failing in the console, the report, and the various logs. In future, ALKiln will use a method like `report.throw()` to save to the various log files with the `error` level and then throw the error to let cucumber know the test failed.
8
+ 1. **Interview expected errors**: For example, the interview says ALKiln's answer is invalid. The author was testing their interview's field validation rules. The test passes.
9
+
10
+ ## Test file `Error`s
11
+
12
+ 1. **Test file syntax error**: For example, a row in a table in a test file has too many columns. cucumberjs throws an error and no tests are run. ALKiln should show the tests as failed. ALKiln is unable to get the output of that error into a log, but there should be an error in the console.
13
+ 1. **Test Step typo**: For example, `And I ta p to contcontinue`. cucumberjs will cause this test to fail, giving an "undefined" Step as the reason. The test suite will show as failing in the console, the report, and the various logs.
14
+ 1. **Test bugs**: For example, a test has old values that need to be updated. The test fails. This will look like an interview bug, as this document describes.
15
+
16
+ ## ALKiln `Error`s
17
+
18
+ 1. **ALKiln un-handled bugs**: For example, we have forgotten to define a variable and none of our tests has yet triggered that code. These are unexpected errors that force processes to abort or force individual tests to fail in uninformative ways. To an author that runs into that error, it looks like their test has failed, but the fault is ours and we need to troubleshoot it. We should make that clear.
19
+ 1. **ALKiln silent bugs**: For example, a debug log has an object that causes an error when ALKiln tries to turn it into a string. ALKiln should record the failure somewhere, but should avoid throwing it. We can troubleshoot those if we see them and add new internal tests. See decisions docs on when to use which kind of log level.
20
+ 1. **ALKiln expected errors**: For example, one promise won a `Promise.race()`, so ALKiln aborted the other promises. That causes those aborted promises to error. That is why ALKiln saves the "informational" Error to the debug files and it has a level of "info" or "debug".
21
+
22
+ ## ALKiln's internal tests
23
+
24
+ 1. **ALKiln internal success-handling test failures**: For example, the test fails to fill out radio buttons correctly.
25
+ 1. **ALKiln internal failure-handling test successes**: For example, we test that the summary report shows a specific log code when an interview fails to load and the log code shows up correctly. The "test" fails - the interview fails to load - but *our* test passes - the right log code was in the summary report.
26
+ 1. **ALKiln internal failure-handling test failures**: For example, we test that the report shows a specific log code when an interview fails to load, but the log code is missing. The "test" fails and *our* test fails.
@@ -0,0 +1,60 @@
1
+ # Prioritize constrained random answers tests over pure random answers tests
2
+
3
+ ## Context and Problem Statement
4
+
5
+ We could develop constrained randomness first or pure randomness first. Is there a reason to prioritize one over another?
6
+
7
+ ## Considered Options
8
+
9
+ - Purely random answers. They fill in fields without any guidance.
10
+ - Constrained random answers tests. They make the test author list all the variable names and all the choices of answers they want to allow for those variables.
11
+ - Hybrid approach. Allow an author to specify some values while letting other values stay completely random.
12
+
13
+ ## Decision Outcome
14
+
15
+ Prioritize constrained random answers tests over pure random answers tests. Strangely, they seem to be the more flexible of the two options.
16
+
17
+ ## Pros and Cons of the Options
18
+
19
+ The discussion about the general pros and cons of random answers tests will be in the `constrained_random_tests` doc for now.
20
+
21
+ ### Purely random answers
22
+
23
+ Pros:
24
+
25
+ - Very little work for test authors to write.
26
+ - Because they're so free-form, they may do a better job finding unexpected behavior that authors didn't think to test for.
27
+
28
+ Cons:
29
+
30
+ - Unless we can find better ways of generating purely random test files for the authors, copying a random answers test to keep permanently might be very challenging.
31
+ - Fields might get nonsensical answers. For example, a very large number for age or a very small number for income.
32
+ - Impossible to use for fields that need very specific answers, like a real lawyer's bar number or a specific file name for uploading exhibits.
33
+
34
+ ### Constrained random answers
35
+
36
+ Pros:
37
+
38
+ - Useful for fields that need very specific answers, like a real lawyer's bar number or a specific file name for uploading exhibits.
39
+ - An author can focus the tests' answers on important user stories. For example, they might want to focus some tests on users that have children while keeping other answers more random.
40
+ - An author might be able to make more efficient random tests. For example, a multiple choice question may have multiple answers that, behind the scenes, set the same value. Authors might do this to help users who are confused about their options give more accurate answers. A purely random test would pick that repeated value too often and waste testing time.
41
+
42
+ Cons:
43
+
44
+ - They take more time to write.
45
+ - They take more work to maintain.
46
+
47
+ <!-- TODO: Add to docs - each `possible_values` cell should contain 1 or more possible answers. -->
48
+
49
+ ### Hybrid
50
+
51
+ In future, we believe we can figure out how to let users have partially constrained tests where unspecified fields get purely random answers while constrained fields choose from a set of answers. It would need to be its own Step.
52
+
53
+ Pros:
54
+
55
+ - Offers the most flexibility and control.
56
+
57
+ Cons:
58
+
59
+ - Might be hard to avoid confusion about test behavior.
60
+ - Means developing both methods of testing. That's out of scope for one PR.
@@ -1,5 +1,5 @@
1
1
  const _da_REST = require('./docassemble_api_REST');
2
- const log = require('../utils/log');
2
+ const Log = require('../utils/Log');
3
3
  const session_vars = require('../utils/session_vars' );
4
4
  const time = require('../utils/time');
5
5
 
@@ -61,6 +61,10 @@ const time = require('../utils/time');
61
61
  let da_i = {};
62
62
  module.exports = da_i;
63
63
 
64
+ const context = `da api`;
65
+ const artifacts_path = session_vars.get_artifacts_path_name();
66
+ const log = new Log({ path: artifacts_path, context });
67
+
64
68
 
65
69
  // Constants
66
70
  const DEFAULT_MAX_REQUEST_MS = session_vars.get_setup_timeout_ms();
@@ -92,7 +96,9 @@ da_i.throw_an_error_if_server_is_not_responding = async function ( options ) {
92
96
 
93
97
  // Defaults
94
98
  let { timeout } = options || { timeout: DEFAULT_MAX_REQUEST_MS };
95
- log.info({ type: `da api`, code: `ALK0166`, pre: `Waiting for the docassemble server to respond. Will wait for ${ timeout/1000 } seconds at most.` });
99
+ log.info({ code: `ALK0166`, context, },
100
+ `Waiting for the docassemble server to respond. Will wait for ${ timeout/1000 } seconds at most.`
101
+ );
96
102
 
97
103
  try {
98
104
  let start_time = Date.now()
@@ -100,7 +106,9 @@ da_i.throw_an_error_if_server_is_not_responding = async function ( options ) {
100
106
  let response = await _da_REST.get_dev_id( timeout );
101
107
  let end_time = Date.now()
102
108
  if ( response && response.data && response.data.id ) {
103
- log.info({ type: `da api`, code: `ALK0167`, pre: `The docassemble server responded after ${ (end_time - start_time)/1000 } seconds.` });
109
+ log.info({ code: `ALK0167`, context, },
110
+ `The docassemble server responded after ${ (end_time - start_time)/1000 } seconds.`
111
+ );
104
112
  return false;
105
113
  }
106
114
 
@@ -109,17 +117,16 @@ da_i.throw_an_error_if_server_is_not_responding = async function ( options ) {
109
117
  if ( is_timeout_error( error ) ) {
110
118
  // Anything that's waiting for the server to not be busy does not want
111
119
  // to move forward without a free server.
112
- log.info({ type: `da api`, code: `ALK0168`, pre: `The docassemble server was still busy after ${ timeout/1000 } seconds.` });
120
+ log.info({ code: `ALK0168`, context, },
121
+ `The docassemble server was still busy after ${ timeout/1000 } seconds.`
122
+ );
113
123
  } else {
114
124
  // Not sure what to do for other errors. It could be a server
115
125
  // error, like an 'Access Denied' error, which should be handled.
116
- log.error({
117
- type: `da api`,
118
- code: `ALK0169`,
119
- pre: `Tried to check if the docassemble server was busy, but `
120
- + `ran into an unexpected error. The error didn't indicate a busy server, though. Error message:\n`
121
- + error
122
- });
126
+ log.warning({ code: `ALK0169`, context, },
127
+ `Checked if the server was busy and ran into a different error`,
128
+ error
129
+ );
123
130
  }
124
131
 
125
132
  throw error;
@@ -187,7 +194,7 @@ da_i.get_api_key_access_status = async function ({ timeout = null, api_key = nul
187
194
  * to allow for server request.
188
195
  */
189
196
 
190
- let auth_data = {
197
+ let auth_feedback = {
191
198
  codes: [ 1 ],
192
199
  has_auth: null,
193
200
  messages: [`API key authorization is not yet determined.`],
@@ -196,16 +203,16 @@ da_i.get_api_key_access_status = async function ({ timeout = null, api_key = nul
196
203
 
197
204
  // Skip this for tests run from ALKilnInThePlayground
198
205
  if ( session_vars.get_origin() === 'playground' ) {
199
- auth_data.codes.push( 2 );
200
- auth_data.messages.push( `API keys are unnecessary for tests run in the Playground.` );
206
+ auth_feedback.codes.push( 2 );
207
+ auth_feedback.messages.push( `API keys are unnecessary for tests run in the Playground.` );
201
208
  }
202
209
 
203
210
  if ( typeof api_key !== `string` ) {
204
- auth_data.codes.push( 3 );
205
- auth_data.messages.push( `The given API key is not a string. Type: ${ typeof api_key }`);
211
+ auth_feedback.codes.push( 3 );
212
+ auth_feedback.messages.push( `The given API key is not a string. Type: ${ typeof api_key }`);
206
213
  }
207
214
 
208
- if ( auth_data.codes.length === 1 ) {
215
+ if ( auth_feedback.codes.length === 1 ) {
209
216
  // Default timeouts
210
217
  timeout = timeout || DEFAULT_MAX_REQUEST_MS;
211
218
  try {
@@ -213,29 +220,31 @@ da_i.get_api_key_access_status = async function ({ timeout = null, api_key = nul
213
220
  api_key,
214
221
  server_url: session_vars.get_da_server_url(),
215
222
  });
216
- auth_data.has_auth = true;
217
- auth_data.codes = [ 6 ];
218
- auth_data.messages = [ `This API key has at least basic authorization on this server.` ];
223
+ auth_feedback.has_auth = true;
224
+ auth_feedback.codes = [ 6 ];
225
+ auth_feedback.messages = [ `This API key has at least basic authorization on this server.` ];
219
226
 
220
227
  } catch ( error ) {
221
- auth_data.error = error;
228
+ auth_feedback.error = error;
222
229
  if ( error.status === 403 ) {
223
- auth_data.has_auth = false;
224
- auth_data.codes = [ 4 ];
225
- auth_data.messages = [ `This API is unverified on this server. It is probably missing or has been deleted.` ];
230
+ auth_feedback.has_auth = false;
231
+ auth_feedback.codes = [ 4 ];
232
+ auth_feedback.messages = [ `This API is unverified on this server. It is probably missing or has been deleted.` ];
226
233
  } else {
227
234
  // Note on 404, non-existent key: 404 == 403 - Since we're using the
228
235
  // key itself to get its own info, if it doesn't exist, we'll get 403
229
- auth_data.codes = [ 5 ];
230
- auth_data.messages = [ `This API key threw a non-403 error.` ];
236
+ auth_feedback.codes = [ 5 ];
237
+ auth_feedback.messages = [ `This API key threw a non-403 error.` ];
231
238
  } // ends error status
232
239
  } // ends try
233
- } // ends auth_data.codes only has the first code
240
+ } // ends auth_feedback.codes only has the first code
234
241
 
235
- log.debug({ type: `da api info`, code: `ALK0202`,
236
- pre: `API key authentication info`, data: auth_data });
242
+ log.debug({ code: `ALK0202`, context, },
243
+ `API key - what happened during authentication:`,
244
+ auth_feedback
245
+ );
237
246
 
238
- return auth_data;
247
+ return auth_feedback;
239
248
  }; // Ends da_i.get_api_key_access_status()
240
249
 
241
250
  // node -e 'require("./lib/docassemble/docassemble_api_interface.js").delete_project( "create1" )'
@@ -247,16 +256,19 @@ da_i.delete_project = async function ( delete_options ) {
247
256
  let { timeout } = delete_options || { timeout: DEFAULT_MAX_REQUEST_MS };
248
257
 
249
258
  let project_name = session_vars.get_project_name()
250
- log.info({ type: `da api`, code: `ALK0170`, pre: `Trying to delete Project ${ project_name }` });
259
+ log.info({ code: `ALK0170`, context, },
260
+ `Trying to delete Project ${ project_name }`
261
+ );
251
262
 
252
263
  try {
253
264
  let response = await _da_REST.delete_project( timeout );
254
- log.info({ type: `da api`, code: `ALK0171`, pre: `Deleted Project "${ project_name }"` });
265
+ log.info({ code: `ALK0171`, context, },
266
+ `Deleted Project "${ project_name }"`
267
+ );
255
268
  } catch ( error ) {
256
- let msg = `Ran into an error when deleting Project "${ project_name }". See https://docassemble.org/docs/api.html#playground_delete_project.`
257
- log.error({ type: `da api`, code: `ALK0172`, pre: msg });
258
- log.debug({ type: `da api`, code: `ALK0173`, pre: msg });
259
- throw error;
269
+ log.throw({ code: `ALK0172`, context, error: error },
270
+ `Ran into an error when deleting Project "${ project_name }". See https://docassemble.org/docs/api.html#playground_delete_project.`
271
+ );
260
272
  }
261
273
 
262
274
  }; // Ends da_i.delete_project()
@@ -280,7 +292,9 @@ da_i.quietly_try_to_delete_interviews = async function ({ interviews = {}, api_k
280
292
  });
281
293
  responses.push( sub_responses );
282
294
  } else {
283
- log.debug({ type: `da api info`, code: `ALK0191`, pre: `There is no interview to delete.` });
295
+ log.debug({ code: `ALK0191`, context, },
296
+ `There is no interview to delete.`
297
+ );
284
298
  }
285
299
  }
286
300
  return responses;
@@ -295,9 +309,9 @@ da_i.quietly_delete_one_interview = async function ( timeout, { interview = {},
295
309
  let responses = [];
296
310
  for ( let api_key of api_keys ) {
297
311
  try {
298
- log.debug({ type: `da api info`, code: `ALK0188`,
299
- pre: `Trying to delete the interview with the id ${ interview.uid } and an API key.`
300
- });
312
+ log.debug({ code: `ALK0188`, context, },
313
+ `Trying to delete the interview with the id ${ interview.uid } and an API key.`
314
+ );
301
315
 
302
316
  // Actual work:
303
317
  let response = await _da_REST.delete_interview( timeout, {
@@ -306,13 +320,15 @@ da_i.quietly_delete_one_interview = async function ( timeout, { interview = {},
306
320
  });
307
321
  responses.push( response );
308
322
 
309
- log.debug({ type: `da api info`, code: `ALK0189`,
310
- pre: `No error when trying to delete interview ${ interview.uid } at ${ interview.i }.`
311
- });
323
+ log.debug({ code: `ALK0189`, context, },
324
+ `No error when trying to delete interview ${ interview.uid } at ${ interview.i }.`
325
+ );
312
326
  } catch ( error ) {
313
327
  error.interview = interview;
314
- let msg = `Error when deleting the interview. See https://docassemble.org/docs/api.html#session_delete.`
315
- log.debug({ type: `da api info`, code: `ALK0190`, pre: msg, data: error });
328
+ log.debug({ code: `ALK0190`, context, },
329
+ `Error when deleting the interview. See https://docassemble.org/docs/api.html#session_delete.`,
330
+ error
331
+ );
316
332
  }
317
333
  } // ends for api keys
318
334
  return responses;
@@ -347,7 +363,9 @@ da_i.get_interview = async function ({ interview = {}, api_keys = [], options =
347
363
  if ( interview && interview.uid ) {
348
364
  for ( let api_key of api_keys ) {
349
365
  try {
350
- log.debug({ type: `da api info`, code: `ALK0192`, pre: `Trying to get the interview with the id ${ interview.uid } and an API key.` });
366
+ log.debug({ code: `ALK0192`, context, },
367
+ `Trying to get the interview with the id ${ interview.uid } and an API key.`
368
+ );
351
369
 
352
370
  // Actual work:
353
371
  let response = await _da_REST.get_interview( timeout, {
@@ -356,20 +374,28 @@ da_i.get_interview = async function ({ interview = {}, api_keys = [], options =
356
374
  });
357
375
 
358
376
  if ( response && response.data && response.data.items && response.data.items[0] && response.data.items[0].session ) {
359
- log.debug({ type: `da api info`, code: `ALK0193`, pre: `Status ${ response.status }. Got ${ response.data.items[0].session }.`});
377
+ log.debug({ code: `ALK0193`, context, },
378
+ `Status ${ response.status }. Got ${ response.data.items[0].session }.`
379
+ );
360
380
  return response.data.items[0];
361
381
  } else {
362
- log.debug({ type: `da api info`, code: `ALK0194`, pre: `Status ${ response.status }. Cannot find ${ interview.uid }`});
382
+ log.debug({ code: `ALK0194`, context, },
383
+ `Status ${ response.status }. Cannot find ${ interview.uid }`
384
+ );
363
385
  }
364
386
 
365
387
  } catch ( error ) {
366
388
  error.interview = interview;
367
- let msg = `${ error.status || `No error.status.` } ${ error.statusText || error.data || `No error.statusText.` } Ran into an error when getting the interview ${ interview.uid }. See https://docassemble.org/docs/api.html#interviews.`
368
- log.debug({ type: `da api info`, code: `ALK0195`, pre: msg, data: error });
389
+ log.debug({ code: `ALK0195`, context, },
390
+ `${ error.status || `No error.status.` } ${ error.statusText || error.data || `No error.statusText.` } Ran into an error when getting the interview ${ interview.uid }. See https://docassemble.org/docs/api.html#interviews.`,
391
+ error
392
+ );
369
393
  }
370
394
  } // ends for api_keys
371
395
  } else {
372
- log.debug({ type: `da api info`, code: `ALK0196`, pre: `There is no interview to get.` });
396
+ log.debug({ code: `ALK0196`, context, },
397
+ `There is no interview to get.`
398
+ );
373
399
  }
374
400
 
375
401
  return null;
@@ -1,10 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const session_vars = require('../utils/session_vars');
4
- const log = require('../utils/log');
4
+ const Log = require('../utils/Log');
5
5
  const fg = require('fast-glob');
6
6
 
7
+ const context = `server_install`;
8
+
7
9
  const server_install = async () => {
10
+ /** For packages we are installing on a temporary GitHub docassemble server.
11
+ *
12
+ * - Store that the package is being installed globally on a server as
13
+ * opposed to on a specific author's account (their Playground).
14
+ * - In the repo, find and store the path to directory that stores the
15
+ * package itself instead of the whole repo. For example, EvictionDefense
16
+ * in the docassemble-EvictionDefense repo.
17
+ * */
8
18
  session_vars.save_install_method(`server`);
9
19
 
10
20
  // Get path to folders inside repo main "docassemble" folder
@@ -23,20 +33,33 @@ const server_install = async () => {
23
33
  // Get names of all folders in repo "docassemble" folder
24
34
  let chosen_folder = folders[0];
25
35
  if ( folders.length === 0 ) {
26
- let msg = `There are 0 folders at ${path}. Stopping tests.`;
27
- log.error({ type: `setup`, code: `ALK0044`, data: msg })
28
- throw new ReferenceError( msg );
36
+ let msg = `ALKiln cannot find the package's files. There are 0 folders at "${ path }". Stopping tests.`;
37
+ log.throw({ code: `ALK0044`, context,
38
+ error: new ReferenceError( msg ) // no logs
39
+ });
29
40
  }
30
41
 
31
42
  if ( folders.length > 1 ) {
32
- log.warn({
33
- type: `setup`,
34
- code: `ALK0045`,
35
- data: `There are ${folders.length} folders at ${path}: "${folders.join('", "')}". ALKiln chose to use the folder "${chosen_folder}".`
36
- })
43
+ log.warn({ code: `ALK0045`, context, },
44
+ `There are ${ folders.length } folders at "${ path }": "${ folders.join('", "') }". ALKiln chose to use the folder "${ chosen_folder }".`
45
+ );
46
+ } else {
47
+ log.info({ code: `ALK0218`, context, },
48
+ `ALKiln found the package folder at "${ chosen_folder }".`
49
+ );
37
50
  }
38
51
 
39
52
  session_vars.save_repo_folder_name( chosen_folder );
40
53
  }; // Ends server_install();
41
54
 
55
+ // 1. session_vars - make sure all the required env vars exist
56
+ session_vars.validateEnvironment();
57
+
58
+ // 2. Prepare logs
59
+ // Local devs: This will be separate from your test paths
60
+ // unless you pass a `path` argument into the cucumber run command
61
+ const argv = require(`minimist`)(process.argv.slice(2));
62
+ const log = new Log({ path: argv.path, context, });
63
+
64
+ // 3. run
42
65
  server_install();
@@ -1,26 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // TODO: Rename to playground_install?
4
-
5
3
  const child_process = require(`child_process`);
6
4
 
7
- const log = require(`../utils/log`);
5
+ const Log = require(`../utils/Log`);
8
6
  const session_vars = require(`../utils/session_vars` );
7
+ const files = require(`../utils/files` );
8
+ const time = require(`../utils/time` );
9
+
10
+ const context = `setup`;
9
11
 
10
12
  const setup = async () => {
11
- /** In the docassemble testing account identified by the
12
- * DOCASSEMBLE_DEVELOPER_API_KEY secret, create a new Project,
13
- * pull code into that project, and, if needed, wait for the
14
- * server to reload.
15
- */
13
+ /** This is for a package being installed in an author's account (Playground)
14
+ * on their docassemble server. Using the author's
15
+ * DOCASSEMBLE_DEVELOPER_API_KEY secret, create a new Project, pull code
16
+ * into that project, and, if needed, wait for the server to reload.
17
+ */
16
18
 
17
19
  // Make and save project name
18
20
  let project_name = session_vars.get_project_base_name().replace(/[^A-Za-z0-9]/g, '') + `${Date.now()}`;
19
21
  session_vars.save_project_name( project_name );
20
22
 
21
- log.info({ type: `setup`, code: `ALK0046`, pre: `Starting to upload this package to `
23
+ const starting_msg = `Starting to upload this package to `
22
24
  + `a new Project called ${project_name} in the Playground `
23
- + `of your server's testing account. It may take a couple of minutes.` });
25
+ + `of your server's testing account. It may take a couple of minutes.`;
26
+ log.info({ code: `ALK0046`, context, },
27
+ starting_msg
28
+ );
29
+
30
+ // TODO: 1. Start listening to the normal console here (for debug logging)
24
31
 
25
32
  // dainstall . --apiurl https://site.org --apikey $API_KEY --playground --project=name
26
33
  // I think this won't run on windows? Why do I think this? Where on the
@@ -34,29 +41,37 @@ const setup = async () => {
34
41
  `--playground`, `--project=${ project_name }`
35
42
  ],
36
43
  // Prints messages from the process to the console while it's running
44
+ // result.stderr, result.stdout, etc. will be `null`
37
45
  { stdio: 'inherit', }
38
46
  );
39
47
 
40
- // 403 is invalid API key, but I'm not sure how to output
41
- // the response to find out the error code
48
+ // TODO: 2. Stop listening to the normal console here
42
49
 
50
+ // 403 is invalid API key, but I'm not sure how to output
51
+ // the response to find out the error code. stderr is `null`.
43
52
  if ( result.status !== 0 ) {
44
- log.error({
45
- type: `setup`,
46
- code: `ALK0047`,
47
- pre: `Status code ${ result.status }. Could not dainstall this package on your server. See above for more details.`
48
- });
49
- throw(result.stderr);
53
+ const install_err_msg = `Status code ${ result.status }. Could not `
54
+ + `dainstall this package on your server. See above for more details.`;
55
+ log.throw({ code: `ALK0047`, context,
56
+ before: `☝️ ☝️ ☝️ Error data ☝️ ☝️ ☝️\n`,
57
+ error: new Error(`Error while installing project on your docassemble server`) },
58
+ install_err_msg
59
+ );
50
60
  }
51
61
 
52
- log.success({
53
- type: `setup`,
54
- code: `ALK0048`,
55
- pre: `ALKiln created a Project in your Playground named "${ project_name }"!`
56
- });
62
+ log.success({ code: `ALK0048`, context, },
63
+ `ALKiln created a Project in your Playground named "${ project_name }"!`
64
+ );
57
65
  }; // Ends setup();
58
66
 
59
-
60
- // Before everything, make sure all the required env vars exist
67
+ // 1. session_vars - make sure all the required env vars exist
61
68
  session_vars.validateEnvironment();
69
+
70
+ // 2. Prepare logs
71
+ // Local devs: This will be separate from your test paths unless you pass a
72
+ // `path` argument into the cucumber run command
73
+ const argv = require(`minimist`)(process.argv.slice(2));
74
+ const log = new Log({ path: argv.path, context, });
75
+
76
+ // 3. run
62
77
  setup();
@@ -1,26 +1,49 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const da_i = require('./docassemble_api_interface');
4
- const log = require('../utils/log');
5
- const session_vars = require('../utils/session_vars' );
4
+ const session_vars = require('../utils/session_vars');
5
+ const Log = require('../utils/Log');
6
+
7
+ const context = `takedown`;
6
8
 
7
9
  const takedown = async () => {
8
- /* In the docassemble testing account identified by the
9
- * DOCASSEMBLE_DEVELOPER_API_KEY secret, delete the
10
- * Project created for this test.
11
- * For local development, clean up project name file. */
12
- log.info({ type: `takedown`, code: `ALK0177`, pre: `Trying to remove the docassemble Project from the testing account.` });
10
+ /** In the docassemble testing account identified by the
11
+ * DOCASSEMBLE_DEVELOPER_API_KEY secret, delete the Project created for this
12
+ * test. For local development, clean up Project name in the runtime_config
13
+ * file.
14
+ * */
15
+ log.info({ code: `ALK0177`, context, },
16
+ `Trying to remove the docassemble Project from the testing account.`
17
+ );
13
18
  // Explicit try-catch prevents "Unhandled promise rejection error"
14
19
  // avoids showing confusing node errors to users that they shouldn't have to fix
15
20
  try {
16
21
  await da_i.delete_project();
17
22
  session_vars.delete_project_name();
18
- log.success({ type: `takedown`, code: `ALK0178`, pre: `Test takedown has finished successfully!` });
23
+ log.info({ code: `ALK0178`, context, },
24
+ `Test takedown finished successfully!`
25
+ );
19
26
  } catch (error) {
20
- log.error({type: `takedown`, code: `ALK0179`, pre: `Could not delete project`, data: JSON.stringify(error, null, 4)})
21
- // we don't re-throw the exception, so make sure to exit with 1 to let GitHub Actions know the script failed
27
+ log.throw({ code: `ALK0179`, context, error: error },
28
+ `Unable to delete project`,
29
+ );
30
+ // we don't re-throw the exception, so make sure to exit
31
+ // with 1 to let GitHub Actions know the script failed
22
32
  process.exitCode = 1;
23
33
  }
34
+
35
+ // TODO: ? Build the final setup, run, & takedown verbose log
36
+ // file in here. Add to it in the GitHub action ?
24
37
  };
25
38
 
39
+ // 1. session_vars - make sure all the required env vars exist
40
+ session_vars.validateEnvironment();
41
+
42
+ // 2. Prepare logs
43
+ // Local devs: This will be separate from your test artifacts paths unless you
44
+ // pass a `path` argument into the `cucumber` run command
45
+ const argv = require(`minimist`)( process.argv.slice(2) );
46
+ const log = new Log({ path: argv.path, context, });
47
+
48
+ // 3. run
26
49
  takedown();
package/lib/globals.js ADDED
@@ -0,0 +1,10 @@
1
+ let globals = {
2
+ // File name prefix ALKiln can use to identify random answer generator files
3
+ generators_prefix: `alkiln_random`,
4
+ generated_files_folder_name: `_alkiln_generated`,
5
+ }
6
+
7
+ module.exports = globals;
8
+
9
+ // Would this be clearer as a config file instead?
10
+ // If not, change name to `constants`?