@suffolklitlab/alkiln 5.12.0 → 5.13.0-logs1

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 (38) hide show
  1. package/.env.example +29 -0
  2. package/.github/workflows/github_server.yml +59 -12
  3. package/.github/workflows/playground.yml +11 -4
  4. package/CHANGELOG.md +41 -1
  5. package/CONTRIBUTING.md +98 -83
  6. package/README.md +1 -1
  7. package/action.yml +67 -29
  8. package/action_for_github_server/action.yml +59 -31
  9. package/docs/decisions/006_which_log_level_to_pick_2024_09_5.md +46 -0
  10. package/docs/decisions/adopt_architectural_decision_records.md +58 -0
  11. package/docs/decisions/detect_sign_in.md +124 -0
  12. package/docs/decisions/pdfjs_version_2024_08_08.md +18 -0
  13. package/docs/decisions/reports_and_logs_2024_08_11.md +141 -0
  14. package/docs/decisions/update_puppeteer_2024_07_28.md +62 -0
  15. package/docs/story_tables.md +1 -1
  16. package/lib/docassemble/docassemble_api_REST.js +67 -1
  17. package/lib/docassemble/docassemble_api_interface.js +225 -18
  18. package/lib/docassemble/server_install.js +23 -9
  19. package/lib/docassemble/setup.js +33 -20
  20. package/lib/docassemble/takedown.js +27 -7
  21. package/lib/run_cucumber.js +83 -33
  22. package/lib/scope.js +222 -82
  23. package/lib/steps.js +206 -62
  24. package/lib/utils/files.js +8 -6
  25. package/lib/utils/log.js +477 -84
  26. package/lib/utils/reports.js +14 -5
  27. package/lib/utils/session_vars.js +25 -18
  28. package/lib/utils/time.js +44 -0
  29. package/npm-shrinkwrap.json +635 -193
  30. package/package.json +8 -8
  31. package/setup.py +1 -1
  32. package/tests/log_codes/check_codes.sh +163 -108
  33. package/tests/log_codes/log_code_expected_instances.txt +213 -0
  34. package/tests/unit_tests/getAllFields.test.js +5 -0
  35. package/tests/unit_tests/getMatchingRows.test.js +5 -0
  36. package/tests/unit_tests/log.test.js +20 -0
  37. package/tests/unit_tests/normalizeTable.test.js +5 -0
  38. package/tests/log_codes/deleted_codes.json +0 -8
@@ -0,0 +1,18 @@
1
+ # Leave pdfjs at its current version
2
+
3
+ ## Context and Problem Statement
4
+
5
+ pdfjs has a security vulnerability (that does not affect us) where malicious PDFs can inject code into an unsuspecting project and cause havoc.
6
+
7
+ Our users are downloading and manipulating their own PDFs. We will assume they're not putting malicious code into their PDFs.
8
+
9
+ The update changes pdfjs .js files to be .mjs files. It might take some bother to configure our repo to use .mjs files.
10
+
11
+ ## Considered Options
12
+
13
+ - Update pdfjs
14
+ - Leave pdfjs at its current version, 3.2.146
15
+
16
+ ## Decision Outcome
17
+
18
+ Leave pdfjs at its current version, 3.2.146. The vulnerability is irrelevant for our situation and we don't have bandwidth to set up the configurations right now.
@@ -0,0 +1,141 @@
1
+ # Shape and behavior of the report and log objects
2
+
3
+ ## Context and scope
4
+
5
+ We have reports and logs we show to the user in the console and as artifacts. We have logs we use for development. cucumber also has logs we want to include in the above until we are confident we are handling all cases ourselves.
6
+
7
+ Right now, the system for logging is fractured and scattered - reports log to the console and save completely separately from other logs. Logs that are not from the report don't get saved anywhere. Some logs only get shown in debug mode, which can be frustrating and unhelpful.
8
+
9
+ ## Goals
10
+
11
+ Overview:
12
+
13
+ - Unify the logging and report systems
14
+ - Clarify their flow
15
+ - Make log information more visible
16
+
17
+ Details:
18
+
19
+ - Log objects have statuses, icons, log codes (e.g. ALK0105), contextual info (e.g. `setup`, `takedown`, `invalid answer`), timestamps, descriptive text, and data (like full error traces).
20
+ - These are the broad purposes of various logs:
21
+ 1. Setup and takedown messages.
22
+ 2. Test progress (a compact way to see what's happening/happened in each test):
23
+
24
+ Test1: .............F--
25
+
26
+ Test 2: ..
27
+ 3. A pretty report at the end with details about each test. It has sections, like "failed" and "passed", headings, etc.
28
+ 4. cucumber's various console logs
29
+ 5. Lots of other informational logs for internal development and debugging purposes.
30
+ I want to do different things with different logs:
31
+ - During the tests, always log items 1, 2, 3, and 4 to the console.
32
+ - During the tests, save items 2, 3, and 4 to a log as they come with all the info in the log objects - `report_log.txt`/`running_report.txt`/`report_run.txt` maybe. If we have to stop the tests early because of an infinite loop or something, this will at least show some information.
33
+ - At the end of the tests, format items 2, 3, and 4, often with only part of the log object info, in a file called `report.txt`.
34
+ - During the tests, save all of the items to a `verbose_log.txt` as they happen.
35
+ - In debug mode, log all that stuff to the console while the tests are running.
36
+ Right now, a few of the logs, like for the test progress (item 2), are printed with `stdout` inline while all others are logged with `console.log`.
37
+ - Logs should accept multiple arguments to match console.log/etc. behavior.
38
+
39
+ Note: The progress logs printed to the console have color codes. We also may want a plain text version without any colors to save in the report log and show in the Playground[^1] "console" and/or a version converted to HTML that does have styles. The latter may be for something easier to digest in a browser and/or to show visually in the Playground version of the tests.
40
+
41
+ Questions:
42
+ - Should `log` throw errors? Otherwise everywhere we need to error, we need to use `log` and `throw`, but I am unable to find a way to remove the current function from an error stack/trace.
43
+ - How do we capture "errors" that are more subtle? Keep the word "error" in the "context"/"types" list? For example, expected errors:
44
+ - Invalid answers on the form that are then assessed as valid behavior?
45
+ - `AbortError`s of promises that should indeed have been aborted.
46
+ - Should we allow "in-between"s for all parts of the log? All together: pre-everything (new lines, dividers, etc), icon/context/etc., pre-logs, logs (the actual messages to print), pre-data, data, post-data? We can default to nothing for all of those.
47
+ - How do we differentiate between methods that log to the console vs. methods that just store data?
48
+
49
+ [^1]: Don't go down this rabbit hole. Broadly - with the "Playground" version, users see the results as a web page instead of in the GitHub job console or in the artifacts. The users run the tests on their own server. This has to do with the platform we built ALKiln for - [docassemble](https://docassemble.org/). It gives users a faster iteration cycle.
50
+
51
+ ## Non/anti-goals
52
+
53
+ - Spend a month on this
54
+ - Create a complex and heavy system to try to abstract absolutely everything
55
+
56
+ ## The actual design
57
+
58
+ This design still seems a little complex and potentially too coupled with reports.
59
+
60
+ ### System-context diagram
61
+
62
+ ```
63
+ logic code -> logger -> report -----------> console
64
+ ^ |-> console |-> stdout/progress
65
+ cucumber ---| |-> running report |-> final report.txt
66
+ |-> verbose log
67
+ ```
68
+
69
+ ```
70
+ logic code -> logger ------> report ------> final report.txt
71
+ ^ |-> console
72
+ cucumber ---| |-> stdout/inline/progress
73
+ |-> running report
74
+ |-> verbose log
75
+ ```
76
+
77
+ ```
78
+ |------------------------------------<|
79
+ v |
80
+ logic code -> logger -> report -----------> format prettily
81
+ ^ |-> console
82
+ cucumber ---| |-> running report
83
+ |-> verbose log
84
+ |-> stdout/progress
85
+ |-> final report.txt
86
+ ```
87
+
88
+ ### APIs
89
+
90
+ Everything saves to the "verbose"/"debug" log. Aside from that:
91
+
92
+ `log.info/warning/success/error` methods log to console. This includes, e.g, setup, takedown which show up in GitHub workflow job logs.
93
+
94
+ `log.report` saves to the running report then uses a report obj focused on cosmetics that has `.heading/table/header/error/etc` methods. Maybe a `.stdout/inlne` method, which then prints to the console immediately.
95
+
96
+ `log.verbose/debug` (name?) - Only save to verbose/debug log with log codes, etc.
97
+
98
+ ### Data storage
99
+
100
+ - `report.txt` - final pretty output for the user with headings, etc.
101
+ - Each Scenario's `report.txt` - pretty output in each test's folder.
102
+ - `running_report_log.txt` (name?) - ugly, but still sparse, giving the user some info about what happened during the tests without overwhelming them.
103
+ - `verbose.txt` (name? `debug`?) - very ugly and full of all the information from everywhere else and more. Useful for internal development and troubleshooting. Includes setup and takedown.
104
+
105
+ It may be complex to store setup and takedown output in the verbose/debug file. `console.Console` cannot append to a file[^2], so we would have to figure out a work-around. One approach could be to always first copy from the existing verbose file and paste those contents back into the file as the first log. That may take setting up some fallbacks to avoid errors.
106
+
107
+ [^2]: `fs.appendFile()` only takes strings and doesn't log exactly like the console. I want to log exactly like the console to make it easy to search for words I and others see in the console output.
108
+
109
+ ### Code and pseudo-code
110
+
111
+ For capturing console output, look into how to get the `stdin` for capturing various console output. https://stackoverflow.com/a/54202970:
112
+
113
+ ```js
114
+ const stdin = process.openStdin()
115
+
116
+ process.stdout.write('Enter name: ')
117
+
118
+ stdin.addListener('data', text => {
119
+ const name = text.toString().trim()
120
+ console.log('Your name is: ' + name)
121
+
122
+ stdin.pause() // stop reading
123
+ })
124
+ ```
125
+
126
+ ### Degree of constraint
127
+
128
+ I think this can basically be greenfield (designed from scratch). We do have a current system, but I think we can easily convert our existing code to a new system.
129
+
130
+ ## Alternatives considered
131
+
132
+ - The `report` object saves to the running report and then sends that same information to the `log` object. Also prints the final report. This includes `stdout` logs. `log` remains otherwise the same as described above.
133
+ - `log` handles `stdout` logs and leaves them out of the report.
134
+ - For report content, `log` would call to `report`, which would do pretty formatting and then hand the format text back to `log` to be saved and/or printed to the console. I'm not sure how `log` could then know what to do with the output - leave out the codes/etc. when it logs to the console and/or saves to the final report.txt.
135
+ - Avoid the complexity of storing setup and takedown output in the verbose/debug file. In GitHub, they're in the GitHub console. Locally, we see the results immediately. On the other hand, storing them ourselves lets us look for information in just one place.
136
+
137
+ ## Cross-cutting concerns
138
+
139
+ _Security, privacy, observability, etc._
140
+
141
+ The same ones we've already dealt with for our current logs.
@@ -0,0 +1,62 @@
1
+ # Use response data to detect sign-in success
2
+
3
+ ## Context and Problem Statement
4
+
5
+ Should we update to the newest version of puppeteer, [22.15.0](https://github.com/puppeteer/puppeteer/blob/main/packages/puppeteer-core/CHANGELOG.md#22150-2024-07-31)?
6
+
7
+ See https://github.com/SuffolkLITLab/ALKiln/issues/930.
8
+
9
+ ## Considered Options
10
+
11
+ - Update to 22.15.0
12
+ - Update to 22.12.0, which is the lowest version that would fix our current problems
13
+ - Stay with 20.8.2
14
+
15
+ See pros and cons
16
+
17
+ ## Decision Outcome
18
+
19
+ Update puppeteer from 20.8.2 to 22.15.0
20
+
21
+ ## Pros and Cons of the Options
22
+
23
+ ### Update at all {#update}
24
+
25
+ **Pros:**
26
+
27
+ - Makes the current fix possible
28
+
29
+ **Cons:**
30
+
31
+ - Change in the middle of a complex PR
32
+ - [A few breaking changes](https://github.com/puppeteer/puppeteer/blob/main/packages/puppeteer-core/CHANGELOG.md#2200-2024-02-05), which includes for us at the very least:
33
+ - "Removes the deprecated `$x` (replace with `$$`) and `waitForXpath` (replace with `waitForSelector`)." We need to add [extra syntax to the start of our selector strings](https://github.com/puppeteer/puppeteer/pull/11782): "xpath//."
34
+ - Replace any `page.waitForTimeout` with cucumber's version or ours.
35
+
36
+ ### Update to to 22.15.0
37
+
38
+ **Pros:**
39
+
40
+ - Has all the most recent bug fixes and features
41
+
42
+ **Cons:**
43
+
44
+ - Exposes us to possible unknown new bugs
45
+
46
+
47
+ ### Update to to 22.12.0
48
+
49
+ This is the lowest version that would fix our problem
50
+
51
+ **Pros:**
52
+
53
+ - Avoids possibly unknown new bugs
54
+
55
+ **Cons:**
56
+
57
+ - Doesn't have fixes to already known bugs
58
+
59
+
60
+ ### Stay with 20.8.2
61
+
62
+ Opposite of first entry ["Update at all"](#update).
@@ -4,7 +4,7 @@ The Story Table is a very flexible tool. It lets the author answer interview (fo
4
4
 
5
5
  When the robot arrives on each interview page, it finds all the fields on the page and loops through the whole Story Table trying to match rows with fields.
6
6
 
7
- Here's some [documentation on the use of Story Table rows](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#story-tables).
7
+ Here's some [documentation on the use of Story Table rows](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/writing#story-table).
8
8
 
9
9
  ## Tracking use of rows
10
10
 
@@ -31,7 +31,28 @@ da.get_dev_id = async function ( timeout ) {
31
31
  }; // Ends da.get_dev_id()
32
32
 
33
33
 
34
- // node -e 'require("./lib/docassemble/docassemble_api_REST.js").delete_project( "create1" )'
34
+ da.get_api_key_info = async function ( timeout, { api_key = null, server_url = null }) {
35
+ /** Get information about an API key owned by the user of the given API key. */
36
+ let options = {
37
+ method: 'GET',
38
+ headers: {'X-API-Key': api_key},
39
+ url: `${ server_url }/api/user/api`,
40
+ params: {
41
+ api_key: api_key,
42
+ },
43
+ timeout: timeout,
44
+ };
45
+
46
+ try {
47
+ return await axios.request( options );
48
+ } catch (error) {
49
+ let response = error.response || { data: null }
50
+ throw {...error.toJSON(), data: response.data};
51
+ }
52
+ }; // Ends da.get_api_key_info()
53
+
54
+
55
+ // node -e 'require("./lib/docassemble/docassemble_api_REST.js").delete_project()'
35
56
  da.delete_project = async function ( timeout ) {
36
57
  /* Delete a project on the user's da server. */
37
58
  let options = {
@@ -53,6 +74,51 @@ da.delete_project = async function ( timeout ) {
53
74
  }; // Ends da.delete_project()
54
75
 
55
76
 
77
+ // node -e 'require("./lib/docassemble/docassemble_api_REST.js").delete_interview(...)'
78
+ da.delete_interview = async function ( timeout, { session = null, api_key = null }) {
79
+ /* Delete an interview with the given session id. */
80
+ let options = {
81
+ method: 'DELETE',
82
+ headers: { 'X-API-Key': api_key },
83
+ // Can't use `api/session` that uses the API key to identify the user's
84
+ // interview sessions. The test may have signed in with an account
85
+ // that's different than the ALKiln dev account that sends this request.
86
+ url: `${ session_vars.get_da_server_url() }/api/user/interviews`,
87
+ params: { session },
88
+ timeout: timeout,
89
+ };
90
+
91
+ try {
92
+ return await axios.request( options );
93
+ } catch (error) {
94
+ let response = error.response || {data: null}
95
+ throw {...error.toJSON(), data: response.data};
96
+ }
97
+ }; // Ends da.delete_interview()
98
+
99
+ // node -e 'require("./lib/docassemble/docassemble_api_REST.js").get_interview(...)'
100
+ da.get_interview = async function ( timeout, { session = null, api_key = null }) {
101
+ /* Get a specific interview. */
102
+ let options = {
103
+ method: 'GET',
104
+ headers: { 'X-API-Key': api_key },
105
+ // Can't use `/api/user/interviews` that uses the API key to identify the user's
106
+ // interview sessions. The test may have signed in with an account
107
+ // that's different than the ALKiln dev account that sends this request.
108
+ url: `${ session_vars.get_da_server_url() }/api/user/interviews`,
109
+ params: { session },
110
+ timeout: timeout,
111
+ };
112
+
113
+ try {
114
+ return await axios.request( options );
115
+ } catch ( error ) {
116
+ let response = error.response || { data: null }
117
+ throw { ...error.toJSON(), data: response.data };
118
+ }
119
+ }; // Ends da.get_interview()
120
+
121
+
56
122
  // node -e 'require("./lib/docassemble/docassemble_api_REST.js").__delete_projects_starting_with( "testingdocassembleALAutomatedTestingTestsmain", 1, 1 )'
57
123
  da.__delete_projects_starting_with = async function ( base_name, starting_num=1, ending_num ) {
58
124
  /** FOR DEVELOPMENT ONLY. Deletes a set of Projects that start with a shared
@@ -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
 
@@ -15,6 +15,9 @@ const time = require('../utils/time');
15
15
  // to specific functions, which can run in a very small
16
16
  // window.
17
17
 
18
+ // TODO: Move get_dev_id() and such into here instead of in
19
+ // docassemble_REST - args should be the same vals as the request expects
20
+
18
21
  /** Notes:
19
22
  * - Checking for a busy server is vulnerable to race conditions, but
20
23
  * there's nothing we can do about that.
@@ -58,6 +61,10 @@ const time = require('../utils/time');
58
61
  let da_i = {};
59
62
  module.exports = da_i;
60
63
 
64
+ const artifacts_path = session_vars.get_artifacts_path_name();
65
+ const log = new Log({ path: artifacts_path, context: `api` });
66
+ const context = `da api`;
67
+
61
68
 
62
69
  // Constants
63
70
  const DEFAULT_MAX_REQUEST_MS = session_vars.get_setup_timeout_ms();
@@ -89,7 +96,9 @@ da_i.throw_an_error_if_server_is_not_responding = async function ( options ) {
89
96
 
90
97
  // Defaults
91
98
  let { timeout } = options || { timeout: DEFAULT_MAX_REQUEST_MS };
92
- 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: `da api`, },
100
+ `Waiting for the docassemble server to respond. Will wait for ${ timeout/1000 } seconds at most.`
101
+ );
93
102
 
94
103
  try {
95
104
  let start_time = Date.now()
@@ -97,7 +106,9 @@ da_i.throw_an_error_if_server_is_not_responding = async function ( options ) {
97
106
  let response = await _da_REST.get_dev_id( timeout );
98
107
  let end_time = Date.now()
99
108
  if ( response && response.data && response.data.id ) {
100
- 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: `da api`, },
110
+ `The docassemble server responded after ${ (end_time - start_time)/1000 } seconds.`
111
+ );
101
112
  return false;
102
113
  }
103
114
 
@@ -106,17 +117,16 @@ da_i.throw_an_error_if_server_is_not_responding = async function ( options ) {
106
117
  if ( is_timeout_error( error ) ) {
107
118
  // Anything that's waiting for the server to not be busy does not want
108
119
  // to move forward without a free server.
109
- log.info({ type: `da api`, code: `ALK0168`, pre: `The docassemble server was still busy after ${ timeout/1000 } seconds.` });
120
+ log.info({ code: `ALK0168`, context: `da api`, },
121
+ `The docassemble server was still busy after ${ timeout/1000 } seconds.`
122
+ );
110
123
  } else {
111
124
  // Not sure what to do for other errors. It could be a server
112
125
  // error, like an 'Access Denied' error, which should be handled.
113
- log.error({
114
- type: `da api`,
115
- code: `ALK0169`,
116
- pre: `Tried to check if the docassemble server was busy, but `
117
- + `ran into an unexpected error. The error didn't indicate a busy server, though. Error message:\n`
118
- + error
119
- });
126
+ log.warning({ code: `ALK0169`, context: `da api` },
127
+ `Checked if the server was busy and ran into a different error`,
128
+ error
129
+ );
120
130
  }
121
131
 
122
132
  throw error;
@@ -174,25 +184,222 @@ da_i.get_dev_id = async function ( dev_id_options ) {
174
184
  return dev_id;
175
185
  }; // Ends da_i.get_dev_id();
176
186
 
187
+ da_i.get_api_key_access_status = async function ({ timeout = null, api_key = null }) {
188
+ /** Check if the API key has minimal authorization on the server.
189
+ * See https://docassemble.org/docs/api.html#api_user_api_get.
190
+ *
191
+ * @param options {obj} Options for getting authorization of the API key
192
+ * @param options.api_key {str | null} Required. The API key to test.
193
+ * @param options.timeout {int | null} Optional. Max MS
194
+ * to allow for server request.
195
+ */
196
+
197
+ let auth_data = {
198
+ codes: [ 1 ],
199
+ has_auth: null,
200
+ messages: [`API key authorization is not yet determined.`],
201
+ error: null
202
+ }
203
+
204
+ // Skip this for tests run from ALKilnInThePlayground
205
+ if ( session_vars.get_origin() === 'playground' ) {
206
+ auth_data.codes.push( 2 );
207
+ auth_data.messages.push( `API keys are unnecessary for tests run in the Playground.` );
208
+ }
209
+
210
+ if ( typeof api_key !== `string` ) {
211
+ auth_data.codes.push( 3 );
212
+ auth_data.messages.push( `The given API key is not a string. Type: ${ typeof api_key }`);
213
+ }
214
+
215
+ if ( auth_data.codes.length === 1 ) {
216
+ // Default timeouts
217
+ timeout = timeout || DEFAULT_MAX_REQUEST_MS;
218
+ try {
219
+ let response = await _da_REST.get_api_key_info( timeout, {
220
+ api_key,
221
+ server_url: session_vars.get_da_server_url(),
222
+ });
223
+ auth_data.has_auth = true;
224
+ auth_data.codes = [ 6 ];
225
+ auth_data.messages = [ `This API key has at least basic authorization on this server.` ];
226
+
227
+ } catch ( error ) {
228
+ auth_data.error = error;
229
+ if ( error.status === 403 ) {
230
+ auth_data.has_auth = false;
231
+ auth_data.codes = [ 4 ];
232
+ auth_data.messages = [ `This API is unverified on this server. It is probably missing or has been deleted.` ];
233
+ } else {
234
+ // Note on 404, non-existent key: 404 == 403 - Since we're using the
235
+ // key itself to get its own info, if it doesn't exist, we'll get 403
236
+ auth_data.codes = [ 5 ];
237
+ auth_data.messages = [ `This API key threw a non-403 error.` ];
238
+ } // ends error status
239
+ } // ends try
240
+ } // ends auth_data.codes only has the first code
241
+
242
+ // log.debug({ type: `da api info`, code: `ALKx0202`,
243
+ // pre: `API key authentication info`, data: auth_data });
244
+ log.debug({ code: `ALK0202`, context: `da api`, },
245
+ `API key authentication info`,
246
+ auth_data
247
+ );
248
+
249
+ return auth_data;
250
+ }; // Ends da_i.get_api_key_access_status()
251
+
177
252
  // node -e 'require("./lib/docassemble/docassemble_api_interface.js").delete_project( "create1" )'
178
253
  da_i.delete_project = async function ( delete_options ) {
179
254
  /** Delete a project on the user's da server. Do we need to watch
180
255
  * for a timeout? */
181
256
 
182
257
  // Defaults
183
- let { timeout } = delete_options || { timeout: DEFAULT_MAX_REQUEST_MS };
258
+ let { timeout } = delete_options || { timeout: DEFAULT_MAX_REQUEST_MS };
184
259
 
185
260
  let project_name = session_vars.get_project_name()
186
- log.info({ type: `da api`, code: `ALK0170`, pre: `Trying to delete Project ${ project_name }` });
261
+ log.info({ code: `ALK0170`, context: `da api`, },
262
+ `Trying to delete Project ${ project_name }`
263
+ );
187
264
 
188
265
  try {
189
266
  let response = await _da_REST.delete_project( timeout );
190
- log.info({ type: `da api`, code: `ALK0171`, pre: `Deleted Project "${ project_name }"` });
267
+ log.info({ code: `ALK0171`, context: `da api`, },
268
+ `Deleted Project "${ project_name }"`
269
+ );
191
270
  } catch ( error ) {
192
- let msg = `Ran into an error when deleting Project "${ project_name }". See https://docassemble.org/docs/api.html#playground_delete_project.`
193
- log.error({ type: `da api`, code: `ALK0172`, pre: msg });
194
- log.debug({ type: `da api`, code: `ALK0173`, pre: msg });
195
- throw error;
271
+ log.throw({ code: `ALK0172`, context: `da api`, error: error },
272
+ `Ran into an error when deleting Project "${ project_name }". See https://docassemble.org/docs/api.html#playground_delete_project.`
273
+ );
274
+ // throw error;
196
275
  }
197
276
 
198
277
  }; // Ends da_i.delete_project()
278
+
279
+ // node -e 'require("./lib/docassemble/docassemble_api_interface.js").quietly_try_to_delete_interviews(...)'
280
+ da_i.quietly_try_to_delete_interviews = async function ({ interviews = {}, api_keys = [], options = {} }) {
281
+ /** Try to delete the given interviews. Fail silently. Example failure: the test
282
+ * did not log in any users and the testing account's role prevents
283
+ * access to other user's interviews. Do we need to watch for a timeout? */
284
+
285
+ // Defaults
286
+ let { timeout } = options || { timeout: DEFAULT_MAX_REQUEST_MS };
287
+
288
+ let responses = [];
289
+ for ( let id in interviews ) {
290
+ let interview = interviews[ id ];
291
+ if ( interview && interview.uid ) {
292
+ let sub_responses = await da_i.quietly_delete_one_interview( timeout, {
293
+ interview,
294
+ api_keys
295
+ });
296
+ responses.push( sub_responses );
297
+ } else {
298
+ log.debug({ code: `ALK0191`, context: `da api` },
299
+ `There is no interview to delete.`
300
+ );
301
+ }
302
+ }
303
+ return responses;
304
+ }; // Ends da_i.quietly_try_to_delete_interviews()
305
+
306
+ // node -e 'require("./lib/docassemble/docassemble_api_interface.js").quietly_delete_one_interview(...)'
307
+ da_i.quietly_delete_one_interview = async function ( timeout, { interview = {}, api_keys = [] }) {
308
+ /** Try to delete an interview using all available API keys. Fail silently.
309
+ * Example failure: the test did not log in any users and the testing
310
+ * account's role prevents access to other user's interviews. */
311
+
312
+ let responses = [];
313
+ for ( let api_key of api_keys ) {
314
+ try {
315
+ log.debug({ code: `ALK0188`, context: `da api` },
316
+ `Trying to delete the interview with the id ${ interview.uid } and an API key.`
317
+ );
318
+
319
+ // Actual work:
320
+ let response = await _da_REST.delete_interview( timeout, {
321
+ session: interview.uid,
322
+ api_key
323
+ });
324
+ responses.push( response );
325
+
326
+ log.debug({ code: `ALK0189`, context: `da api` },
327
+ `No error when trying to delete interview ${ interview.uid } at ${ interview.i }.`
328
+ );
329
+ } catch ( error ) {
330
+ error.interview = interview;
331
+ log.debug({ code: `ALK0190`, context: `da api` },
332
+ `Error when deleting the interview. See https://docassemble.org/docs/api.html#session_delete.`,
333
+ error
334
+ );
335
+ }
336
+ } // ends for api keys
337
+ return responses;
338
+ }; // Ends da_i.quietly_delete_one_interview()
339
+
340
+ // node -e 'require("./lib/docassemble/docassemble_api_interface.js").get_interviews(...)'
341
+ da_i.get_interviews = async function ({ interviews = {}, api_keys = [], options = {} }) {
342
+ /** Return an object of interviews that exist on the server
343
+ * that match the given interviews. Keys are the session ids. */
344
+
345
+ let fetched_interviews = {};
346
+ if ( interviews ) {
347
+ for ( let id in interviews ) {
348
+ let interview = interviews[ id ];
349
+ let fetched = await da_i.get_interview({ interview, api_keys, options });
350
+ if ( fetched && fetched.session ) {
351
+ fetched_interviews[ fetched.session ] = fetched;
352
+ }
353
+ }
354
+ } // ends if interviews
355
+
356
+ return fetched_interviews;
357
+ }; // Ends da_i.get_interviews()
358
+
359
+ // node -e 'require("./lib/docassemble/docassemble_api_interface.js").get_interview(...)'
360
+ da_i.get_interview = async function ({ interview = {}, api_keys = [], options = {} }) {
361
+ /** Try to get an interview from the server that matches the given interview data. */
362
+
363
+ // Defaults
364
+ let { timeout } = options || { timeout: DEFAULT_MAX_REQUEST_MS };
365
+
366
+ if ( interview && interview.uid ) {
367
+ for ( let api_key of api_keys ) {
368
+ try {
369
+ log.debug({ code: `ALK0192`, context: `da api` },
370
+ `Trying to get the interview with the id ${ interview.uid } and an API key.`
371
+ );
372
+
373
+ // Actual work:
374
+ let response = await _da_REST.get_interview( timeout, {
375
+ session: interview.uid,
376
+ api_key
377
+ });
378
+
379
+ if ( response && response.data && response.data.items && response.data.items[0] && response.data.items[0].session ) {
380
+ log.debug({ code: `ALK0193`, context: `da api` },
381
+ `Status ${ response.status }. Got ${ response.data.items[0].session }.`
382
+ );
383
+ return response.data.items[0];
384
+ } else {
385
+ log.debug({ code: `ALK0194`, context: `da api` },
386
+ `Status ${ response.status }. Cannot find ${ interview.uid }`
387
+ );
388
+ }
389
+
390
+ } catch ( error ) {
391
+ error.interview = interview;
392
+ log.debug({ code: `ALK0195`, context: `da api` },
393
+ `${ 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.`,
394
+ error
395
+ );
396
+ }
397
+ } // ends for api_keys
398
+ } else {
399
+ log.debug({ code: `ALK0196`, context: `da api` },
400
+ `There is no interview to get.`
401
+ );
402
+ }
403
+
404
+ return null;
405
+ }; // Ends da_i.get_interview()
@@ -1,10 +1,11 @@
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
7
  const server_install = async () => {
8
+ /** Finds out where in this directory the package's folders are. */
8
9
  session_vars.save_install_method(`server`);
9
10
 
10
11
  // Get path to folders inside repo main "docassemble" folder
@@ -23,20 +24,33 @@ const server_install = async () => {
23
24
  // Get names of all folders in repo "docassemble" folder
24
25
  let chosen_folder = folders[0];
25
26
  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 );
27
+ let msg = `ALKiln cannot find the package's files. There are 0 folders at "${ path }". Stopping tests.`;
28
+ log.throw({ code: `ALK0044`, context: `server_install`,
29
+ error: new ReferenceError( msg ); // no logs
30
+ });
29
31
  }
30
32
 
31
33
  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
- })
34
+ log.warn({ code: `ALK0045`, context: `setup`, },
35
+ `There are ${ folders.length } folders at "${ path }": "${ folders.join('", "') }". ALKiln chose to use the folder "${ chosen_folder }".`
36
+ );
37
+ } else {
38
+ log.info({ code: `ALK0218`, context: `setup`, },
39
+ `ALKiln found the package folder at "${ chosen_folder }".`
40
+ );
37
41
  }
38
42
 
39
43
  session_vars.save_repo_folder_name( chosen_folder );
40
44
  }; // Ends server_install();
41
45
 
46
+ // 1. session_vars - make sure all the required env vars exist
47
+ session_vars.validateEnvironment();
48
+
49
+ // 2. Prepare logs
50
+ // Local devs: This will be separate from your test paths
51
+ // unless you pass a `path` argument into the cucumber run command
52
+ const argv = require(`minimist`)(process.argv.slice(2));
53
+ const log = new Log({ path: argv.path, context: `server_install` });
54
+
55
+ // 3. run
42
56
  server_install();