@suffolklitlab/alkiln 5.3.0 → 5.4.1-feat-github-server-15
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.
- package/.github/workflows/github_server.yml +85 -0
- package/CHANGELOG.md +13 -0
- package/action.yml +33 -10
- package/github_server/action.yml +165 -0
- package/lib/docassemble/docassemble_api_REST.js +10 -2
- package/lib/docassemble/docassemble_api_interface.js +15 -10
- package/lib/docassemble/get_base_interview_url.js +13 -3
- package/lib/docassemble/server_install.js +40 -0
- package/lib/docassemble/setup.js +1 -1
- package/lib/run_cucumber.js +67 -34
- package/lib/scope.js +87 -58
- package/lib/steps.js +24 -1
- package/lib/utils/log.js +1 -1
- package/lib/utils/pdf_reader.js +113 -0
- package/lib/utils/session_vars.js +157 -7
- package/npm-shrinkwrap.json +452 -2262
- package/package.json +7 -2
- package/setup.py +2 -2
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
name: GitHub server tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
# `workflow dispatch` isn't showing up in github because it's not the default branch
|
|
6
|
+
# See https://stackoverflow.com/a/75250828
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
tags:
|
|
10
|
+
required: False
|
|
11
|
+
description: 'Optional. Use a "tag expression" specify which tagged tests to run (https://cucumber.io/docs/cucumber/api/#tag-expressions)'
|
|
12
|
+
# # Only test 2 tests - one uses an env var, the other uses AL.
|
|
13
|
+
# # They test if env vars get set correctly and depencencies get
|
|
14
|
+
# # pulled in correctly.
|
|
15
|
+
# default: '@al1'
|
|
16
|
+
enable_tmate:
|
|
17
|
+
type: boolean
|
|
18
|
+
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
|
19
|
+
required: false
|
|
20
|
+
default: false
|
|
21
|
+
# TODO: alkiln_version:
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
|
|
25
|
+
github-server-tests:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
strategy:
|
|
28
|
+
matrix:
|
|
29
|
+
node-version: [18.x]
|
|
30
|
+
|
|
31
|
+
env:
|
|
32
|
+
# How would a dev make another user/password if they wanted to?
|
|
33
|
+
SECRET_VAR1: secret-var1-value
|
|
34
|
+
SECRET_VAR2: secret-var2-value
|
|
35
|
+
SECRET_FOR_MISSING_FIELD: secret for missing field
|
|
36
|
+
#_ALKILN_DANGEROUS_DEBUG: true
|
|
37
|
+
|
|
38
|
+
steps:
|
|
39
|
+
# Place the root directory in this branch
|
|
40
|
+
- uses: actions/checkout@v3
|
|
41
|
+
|
|
42
|
+
# Debugging. Maybe add a long sleep after whatever it is we want to check
|
|
43
|
+
# to give us time to explore.
|
|
44
|
+
- name: Setup tmate session
|
|
45
|
+
if: ${{ inputs.enable_tmate }}
|
|
46
|
+
uses: mxschmitt/action-tmate@v3
|
|
47
|
+
with:
|
|
48
|
+
detached: true
|
|
49
|
+
|
|
50
|
+
- name: Start the temporary GitHub server
|
|
51
|
+
id: github_server
|
|
52
|
+
uses: ./github_server
|
|
53
|
+
with:
|
|
54
|
+
CONFIG_CONTENTS: "${{ secrets.CONFIG_CONTENTS }}"
|
|
55
|
+
- run: echo "Finished starting the GitHub server"
|
|
56
|
+
|
|
57
|
+
# Debugging on failure
|
|
58
|
+
# Trying to catch that intermittent bug mentioned in ./github_server/action.yml
|
|
59
|
+
- name: Docker failure tmate session
|
|
60
|
+
if: ${{ failure() }}
|
|
61
|
+
uses: mxschmitt/action-tmate@v3
|
|
62
|
+
|
|
63
|
+
- name: Env vars from server output
|
|
64
|
+
run: |
|
|
65
|
+
echo "USER1_EMAIL=${{ steps.github_server.outputs.DA_ADMIN_EMAIL }}" >> $GITHUB_ENV
|
|
66
|
+
echo "USER1_PASSWORD=${{ steps.github_server.outputs.DA_ADMIN_PASSWORD }}" >> $GITHUB_ENV
|
|
67
|
+
#- name: Work with docker
|
|
68
|
+
# run: |
|
|
69
|
+
# # Authors can use this to do more with the docker container.
|
|
70
|
+
# # There may be a better way. Would take research. A docker action?
|
|
71
|
+
# container_name=$(docker ps --format '{{.Names}}' | head -n 1)
|
|
72
|
+
# echo "container_name is $container_name"
|
|
73
|
+
|
|
74
|
+
- name: "Run ALKiln tests"
|
|
75
|
+
if: ${{ success() }}
|
|
76
|
+
uses: ./
|
|
77
|
+
with:
|
|
78
|
+
SERVER_URL: "${{ steps.github_server.outputs.SERVER_URL }}"
|
|
79
|
+
DOCASSEMBLE_DEVELOPER_API_KEY: "${{ steps.github_server.outputs.DOCASSEMBLE_DEVELOPER_API_KEY }}"
|
|
80
|
+
INSTALL_METHOD: "server"
|
|
81
|
+
# TODO: See what happens if this is an empty string
|
|
82
|
+
ALKILN_VERSION: "5.4.1-feat-github-server-15"
|
|
83
|
+
ALKILN_TAG_EXPRESSION: "@al1"
|
|
84
|
+
- run: echo "Finished running ALKiln tests"
|
|
85
|
+
|
package/CHANGELOG.md
CHANGED
|
@@ -44,6 +44,19 @@ Format:
|
|
|
44
44
|
-->
|
|
45
45
|
<!-- ## [Unreleased] -->
|
|
46
46
|
|
|
47
|
+
## [5.4.1] - 2023-10-18
|
|
48
|
+
|
|
49
|
+
### Internal
|
|
50
|
+
- BREAKS ALKilnInThePlayground v1.2.0 and below (as we discussed) as that project is still exploratory. Can now take the location of the "sources" folder or folders as an argument in the command line. The formats are `npm run cucumber @tags -- --sources=./foo` and `alkiln-run @tags --sources=./foo`.
|
|
51
|
+
|
|
52
|
+
## [5.4.0] - 2023-10-14
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- New step to compare PDFs!
|
|
57
|
+
- An example: `I expect the baseline PDF "something-in-sources.pdf" and the new PDF "downloaded.pdf" to be the same`
|
|
58
|
+
- See [#774](https://github.com/SuffolkLITLab/ALKiln/pull/774) for more details.
|
|
59
|
+
|
|
47
60
|
## [5.3.0] - 2023-10-14
|
|
48
61
|
|
|
49
62
|
### Added
|
package/action.yml
CHANGED
|
@@ -22,6 +22,15 @@ inputs:
|
|
|
22
22
|
description: "Max amount of time to give the server to reload if it needs to"
|
|
23
23
|
required: false
|
|
24
24
|
# no default to let the custom Scenario timeouts create the reload timeout
|
|
25
|
+
# INSTALL_LOCATION? Will that get confused with the location passed into the command line?
|
|
26
|
+
# Maybe `INSTALL_LOCATION` could be "playground", "server", or a custom path and we could handle all those.
|
|
27
|
+
INSTALL_METHOD:
|
|
28
|
+
description: "Default is 'playground'. Other option: 'server'. How to install the package onto the server."
|
|
29
|
+
required: false
|
|
30
|
+
default: "playground"
|
|
31
|
+
ALKILN_TAG_EXPRESSION:
|
|
32
|
+
description: "The tag expression to use to limit which tests to run. Default is no tag expression."
|
|
33
|
+
required: false
|
|
25
34
|
|
|
26
35
|
runs:
|
|
27
36
|
using: "composite"
|
|
@@ -46,29 +55,43 @@ runs:
|
|
|
46
55
|
echo -e "\nALKiln version is $ALKILN_VERSION\nRepo is $REPO_URL\nBranch ref is $BRANCH_PATH\nMAX_SECONDS_FOR_SETUP is $MAX_SECONDS_FOR_SETUP\nSERVER_RELOAD_TIMEOUT_SECONDS is $SERVER_RELOAD_TIMEOUT_SECONDS\n"
|
|
47
56
|
shell: bash
|
|
48
57
|
|
|
49
|
-
# Install
|
|
58
|
+
# Install
|
|
50
59
|
- name: "ALKiln: Install node packages"
|
|
51
60
|
uses: actions/setup-node@v3
|
|
52
61
|
with:
|
|
53
62
|
node-version: "18"
|
|
54
63
|
- name: Install ALKiln directly from npm
|
|
55
64
|
run: |
|
|
56
|
-
npm install -g @suffolklitlab/alkiln
|
|
65
|
+
npm install -g @suffolklitlab/alkiln@$ALKILN_VERSION
|
|
57
66
|
shell: bash
|
|
58
67
|
|
|
59
|
-
#
|
|
68
|
+
# Install on playground
|
|
60
69
|
- name: "ALKiln: Create a Project and pull the package from GitHub"
|
|
70
|
+
if: ${{ inputs.INSTALL_METHOD == 'playground' }}
|
|
61
71
|
run: alkiln-setup
|
|
62
72
|
shell: bash
|
|
73
|
+
- run: echo -e "\n\n====\nALKiln could not create a project on your server's testing account or pull your package into it. Check the messages above this line.\n\n"
|
|
74
|
+
if: ${{ inputs.INSTALL_METHOD == 'playground' && failure() }}
|
|
75
|
+
shell: bash
|
|
76
|
+
|
|
77
|
+
# Install on server
|
|
78
|
+
- name: "ALKiln: Install the GitHub package onto the server"
|
|
79
|
+
if: ${{ inputs.INSTALL_METHOD == 'server' }}
|
|
80
|
+
run: alkiln-server-install
|
|
81
|
+
shell: bash
|
|
82
|
+
- run: echo -e "\n\n====\nALKiln could not install the package on the temporary GitHub docassemble server. Check the steps above this line.\n\n"
|
|
83
|
+
if: ${{ inputs.INSTALL_METHOD == 'server' && failure() }}
|
|
84
|
+
shell: bash
|
|
85
|
+
|
|
86
|
+
# run tests
|
|
63
87
|
- name: "ALKiln: Run tests"
|
|
64
88
|
if: ${{ success() }}
|
|
65
|
-
run: alkiln-run ${{ github.event.inputs.tags && format('{0}', github.event.inputs.tags) }}
|
|
66
|
-
shell: bash
|
|
67
|
-
- run: echo -e "\n\n====ALKiln could not create a project on your server's testing account or pull your package into it. Check the messages above this line.\n\n"
|
|
68
|
-
if: ${{ failure() }}
|
|
89
|
+
run: alkiln-run ${{ inputs.ALKILN_TAG_EXPRESSION || github.event.inputs.tags && format('{0}', github.event.inputs.tags) }}
|
|
69
90
|
shell: bash
|
|
70
|
-
|
|
71
|
-
|
|
91
|
+
|
|
92
|
+
# on playground, delete project
|
|
93
|
+
- name: "ALKiln: Try to delete the project from the playground of the docassemble test account."
|
|
94
|
+
if: ${{ inputs.INSTALL_METHOD == 'playground' }}
|
|
72
95
|
run: alkiln-takedown
|
|
73
96
|
shell: bash
|
|
74
97
|
|
|
@@ -81,7 +104,7 @@ runs:
|
|
|
81
104
|
path: ./alkiln-*
|
|
82
105
|
|
|
83
106
|
# ##################################
|
|
84
|
-
# # A developer's workflow should look similar to the below
|
|
107
|
+
# # A developer's workflow should look similar to the below.
|
|
85
108
|
# # In place of `uses: suffolkLITLab/ALKiln@v5`, they
|
|
86
109
|
# # might use another version.
|
|
87
110
|
#
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
name: "ALKiln/github_server: Isolated docassemble tests"
|
|
2
|
+
description: "Download and start up a temporary docassemble server on GitHub's servers, in an isolated environment. Avoids docassemble reload errors and other external factors."
|
|
3
|
+
|
|
4
|
+
# Bug? Race condition? Exit with 52 when waiting for container to finish starting
|
|
5
|
+
# https://github.com/SuffolkLITLab/ALKiln/actions/runs/5912900256/job/16036751264#step:4:188
|
|
6
|
+
# Had no completion messages, but it exited with an error anyway
|
|
7
|
+
# Took 121 seconds
|
|
8
|
+
# The rerun of the test completed that step just fine, though it took less time
|
|
9
|
+
# Took 120 and was fine: https://github.com/SuffolkLITLab/ALKiln/actions/runs/5913167036/job/16037288791
|
|
10
|
+
# 150 secs with exit code 56:
|
|
11
|
+
# https://github.com/SuffolkLITLab/ALKiln/actions/runs/5914677262/job/16040233419#step:4:196
|
|
12
|
+
# 120 sec with exit code 56:
|
|
13
|
+
# https://github.com/SuffolkLITLab/ALKiln/actions/runs/5914963614/job/16040773624#step:4:206
|
|
14
|
+
|
|
15
|
+
# If we want to control the version of the docassemble image, we'd have
|
|
16
|
+
# to make our own image:
|
|
17
|
+
# You can build your own image by doing git clone jhpyle/docassemble and then
|
|
18
|
+
# using git checkout to select a particular version, and then using docker build
|
|
19
|
+
# to build an image. https://docassemble.org/docs/docker.html#downgrading
|
|
20
|
+
# If we just want to use an older version of da (as long as it works with
|
|
21
|
+
# the latest image):
|
|
22
|
+
# If you have an existing container you can use dainstall from the command
|
|
23
|
+
# line to install docassemble.webapp==1.4.68
|
|
24
|
+
|
|
25
|
+
inputs:
|
|
26
|
+
CONFIG_CONTENTS:
|
|
27
|
+
description: "The contents of the config file of the docassemble server"
|
|
28
|
+
required: false
|
|
29
|
+
MAX_SECONDS_FOR_DOCKER:
|
|
30
|
+
description: "Maximum amount of seconds to give the docassemble docker container to serve the site."
|
|
31
|
+
required: false
|
|
32
|
+
default: 600 # 10 min
|
|
33
|
+
|
|
34
|
+
outputs:
|
|
35
|
+
DOCASSEMBLE_DEVELOPER_API_KEY:
|
|
36
|
+
description: "Use this inside `with:` for the ALKiln step. Temporary admin api key. It will be destroyed after the tests are done."
|
|
37
|
+
value: ${{ steps.api_key_output_step.outputs.DOCASSEMBLE_DEVELOPER_API_KEY }}
|
|
38
|
+
SERVER_URL:
|
|
39
|
+
description: "Use this inside `with:` for the ALKiln step. URL of the temporary docassemble server."
|
|
40
|
+
value: "http://localhost"
|
|
41
|
+
DA_ADMIN_EMAIL:
|
|
42
|
+
value: ${{ steps.api_key_output_step.outputs.DA_ADMIN_EMAIL }}
|
|
43
|
+
DA_ADMIN_PASSWORD:
|
|
44
|
+
value: ${{ steps.api_key_output_step.outputs.DA_ADMIN_PASSWORD }}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
runs:
|
|
48
|
+
using: "composite"
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v3
|
|
51
|
+
|
|
52
|
+
- name: "ALKiln github_server: Set environment variables"
|
|
53
|
+
run: |
|
|
54
|
+
echo "DA_ADMIN_API_KEY=abcd1234abcd1234abcd5678abdc5678" >> $GITHUB_ENV
|
|
55
|
+
echo "MAX_SECONDS_FOR_DOCKER=${{ inputs.MAX_SECONDS_FOR_DOCKER }}" >> $GITHUB_ENV
|
|
56
|
+
echo "DA_ADMIN_EMAIL=admin@example.com" >> $GITHUB_ENV
|
|
57
|
+
# Login will fail if this is "password"
|
|
58
|
+
echo "DA_ADMIN_PASSWORD=@123abcdefg" >> $GITHUB_ENV
|
|
59
|
+
shell: bash
|
|
60
|
+
- id: api_key_output_step
|
|
61
|
+
run: |
|
|
62
|
+
echo "DOCASSEMBLE_DEVELOPER_API_KEY=$DA_ADMIN_API_KEY" >> "$GITHUB_OUTPUT"
|
|
63
|
+
echo "DA_ADMIN_EMAIL=$DA_ADMIN_EMAIL" >> "$GITHUB_OUTPUT"
|
|
64
|
+
echo "DA_ADMIN_PASSWORD=$DA_ADMIN_PASSWORD" >> "$GITHUB_OUTPUT"
|
|
65
|
+
shell: bash
|
|
66
|
+
|
|
67
|
+
- name: "ALKiln github_server: Download and start docker"
|
|
68
|
+
run: |
|
|
69
|
+
mkdir /tmp/config
|
|
70
|
+
echo "${{ inputs.CONFIG_CONTENTS }}" > /tmp/config/myconfig.yml
|
|
71
|
+
docker pull jhpyle/docassemble
|
|
72
|
+
docker run --env DA_ADMIN_EMAIL="${{ env.DA_ADMIN_EMAIL }}" --env DA_ADMIN_PASSWORD="${{ env.DA_ADMIN_PASSWORD }}" --env DA_ADMIN_API_KEY="${{ env.DA_ADMIN_API_KEY }}" --env DA_CONFIG=/tmp/config/myconfig.yml --volume /tmp/config:/tmp/config --cap-add SYS_PTRACE --memory="4gb" -d -p 80:80 jhpyle/docassemble
|
|
73
|
+
shell: bash
|
|
74
|
+
- name: "ALKiln github_server: Wait for server to start"
|
|
75
|
+
run: |
|
|
76
|
+
# ALKiln github_server: Wait for server to start
|
|
77
|
+
|
|
78
|
+
# Reset the SECONDS variable
|
|
79
|
+
SECONDS=0
|
|
80
|
+
# Wait 1 minute before pinging the server constantly
|
|
81
|
+
sleep 60
|
|
82
|
+
|
|
83
|
+
# Use a variable instead of checking $response so we can control to avoid race
|
|
84
|
+
# conditions from preventing the success log message.
|
|
85
|
+
ready="false"
|
|
86
|
+
|
|
87
|
+
while [[ $ready == "false" ]]; do
|
|
88
|
+
echo "Time ellapsed: $SECONDS seconds"
|
|
89
|
+
|
|
90
|
+
# Check if max seconds till loading has passed
|
|
91
|
+
if [[ $SECONDS -ge $MAX_SECONDS_FOR_DOCKER ]]; then
|
|
92
|
+
echo "ALKiln GitHub Server Error: Timed out waiting for docker container to serve the site. If you want to give your docker container more timem to install, set the MAX_SECONDS_FOR_DOCKER input for this action."
|
|
93
|
+
# Exit with failure, or allow this to keep going?
|
|
94
|
+
break
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# Set the variable to a new string value in each iteration
|
|
98
|
+
response=$(curl -o /dev/null -s -w "%{http_code}\n" http://localhost:80/health_check?ready=1)
|
|
99
|
+
echo "response is $response"
|
|
100
|
+
|
|
101
|
+
# Check if the string includes the desired text
|
|
102
|
+
if [[ $response == *"200"* ]]; then
|
|
103
|
+
echo "Success! The docker container is ready!"
|
|
104
|
+
ready="true"
|
|
105
|
+
else
|
|
106
|
+
echo "The docker container isn't ready yet. Will check again in 30 seconds."
|
|
107
|
+
sleep 30
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
done
|
|
111
|
+
shell: bash
|
|
112
|
+
|
|
113
|
+
# Don't rely on the environment's version of python
|
|
114
|
+
- uses: actions/setup-python@v4
|
|
115
|
+
with:
|
|
116
|
+
python-version: '3.10'
|
|
117
|
+
- run: pip install docassemblecli
|
|
118
|
+
shell: bash
|
|
119
|
+
# Install the current directory onto the docassemble server
|
|
120
|
+
|
|
121
|
+
#- name: Setup tmate session
|
|
122
|
+
# uses: mxschmitt/action-tmate@v3
|
|
123
|
+
|
|
124
|
+
## da doesn't need to know the name of the folder
|
|
125
|
+
## alkiln does so it can locate the Project or package installed on the server
|
|
126
|
+
- run: |
|
|
127
|
+
# if `docassemble` cd `docassemble`
|
|
128
|
+
#cd docassemble
|
|
129
|
+
## Get the name of the package folder
|
|
130
|
+
#dirs2=(*/)
|
|
131
|
+
#len=${#dirs2[@]}
|
|
132
|
+
#if (( $len > 1 ));
|
|
133
|
+
#then echo There are multiple files in your repo's `docassemble` folder. ALkiln will use the first one;
|
|
134
|
+
#fi
|
|
135
|
+
#folder=5
|
|
136
|
+
#
|
|
137
|
+
#
|
|
138
|
+
## Get back out of the docassemble folder
|
|
139
|
+
#cd ..
|
|
140
|
+
## Install on server
|
|
141
|
+
|
|
142
|
+
dainstall . --apiurl http://localhost --apikey $DA_ADMIN_API_KEY
|
|
143
|
+
shell: bash
|
|
144
|
+
|
|
145
|
+
# Upload artifacts that subscribers can download on the Actions summary page
|
|
146
|
+
- name: "ALKiln github_server: Docker logs"
|
|
147
|
+
if: ${{ always() }}
|
|
148
|
+
run: |
|
|
149
|
+
docker_logs=$(docker logs $(docker ps --filter "ancestor=jhpyle/docassemble" --format {{.ID}}))
|
|
150
|
+
# Should this name have a date/time attached? The logs themselves have timestamps
|
|
151
|
+
echo "$docker_logs" > /tmp/alkiln_docker_logs.txt
|
|
152
|
+
shell: bash
|
|
153
|
+
|
|
154
|
+
# Should this date/time be created at the start of this action?
|
|
155
|
+
- name: "ALKiln github_server: Get folder name with date and time"
|
|
156
|
+
if: ${{ always() }}
|
|
157
|
+
run: |
|
|
158
|
+
echo "GITHUB_SERVER_ARTIFACT_NAME=alkiln-github-server-$(date +'%Y-%m-%d at %Hh%Mm%Ss' -u)UTC" >> $GITHUB_ENV
|
|
159
|
+
shell: bash
|
|
160
|
+
- name: "ALKiln github_server: Upload artifacts folder"
|
|
161
|
+
if: ${{ always() }}
|
|
162
|
+
uses: actions/upload-artifact@v3
|
|
163
|
+
with:
|
|
164
|
+
name: ${{ env.GITHUB_SERVER_ARTIFACT_NAME }}
|
|
165
|
+
path: /tmp/alkiln_docker_logs.txt
|
|
@@ -79,13 +79,21 @@ da.pull = async function ( timeout ) {
|
|
|
79
79
|
}; // Ends da.pull()
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
da.has_playground_task_finished = async function ( task_id, timeout ) {
|
|
83
|
+
return await da.has_task_finished( "/api/restart_status", task_id, timeout);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
da.has_package_task_finished = async function ( task_id, timeout ) {
|
|
87
|
+
return await da.has_task_finished( "/api/package_update_status", task_id, timeout);
|
|
88
|
+
}
|
|
89
|
+
|
|
82
90
|
// node -e 'require("./lib/docassemble/docassemble_api_REST.js").has_task_finished()'
|
|
83
|
-
da.has_task_finished = async function ( task_id, timeout ) {
|
|
91
|
+
da.has_task_finished = async function ( api_endpoint, task_id, timeout ) {
|
|
84
92
|
/* Check task status based on `task_id`. */
|
|
85
93
|
let options = {
|
|
86
94
|
method: 'GET',
|
|
87
95
|
headers: {'X-API-Key': session_vars.get_dev_api_key()},
|
|
88
|
-
url: `${ session_vars.get_da_server_url() }
|
|
96
|
+
url: `${ session_vars.get_da_server_url() }${ api_endpoint }`,
|
|
89
97
|
params: {
|
|
90
98
|
task_id: task_id,
|
|
91
99
|
},
|
|
@@ -329,9 +329,8 @@ da_i.pull = async function ( pull_options ) {
|
|
|
329
329
|
}
|
|
330
330
|
}; // Ends da_i.pull()
|
|
331
331
|
|
|
332
|
-
|
|
333
332
|
// node -e 'require("./lib/docassemble/docassemble_api_interface.js").has_task_finished()'
|
|
334
|
-
da_i.has_task_finished = async function ( task_id, options ) {
|
|
333
|
+
da_i.has_task_finished = async function ( api_endpoint, task_id, options ) {
|
|
335
334
|
/** Check task status if it exists. If not, return true as task did
|
|
336
335
|
* not require a server restart. See da_i.pull().
|
|
337
336
|
* See https://docassemble.org/docs/api.html#restart_status. */
|
|
@@ -342,7 +341,7 @@ da_i.has_task_finished = async function ( task_id, options ) {
|
|
|
342
341
|
try {
|
|
343
342
|
|
|
344
343
|
// If server did need to restart, check if it's done.
|
|
345
|
-
let response = await _da_REST.has_task_finished( task_id, timeout );
|
|
344
|
+
let response = await _da_REST.has_task_finished( api_endpoint, task_id, timeout );
|
|
346
345
|
log.debug({ pre: `Task status: ${ response.status }. Docassemble status text: ${ response.data.status }. Task id: ${ task_id }` });
|
|
347
346
|
|
|
348
347
|
if ( response.data.status === 'completed' ) {
|
|
@@ -353,16 +352,15 @@ da_i.has_task_finished = async function ( task_id, options ) {
|
|
|
353
352
|
return false;
|
|
354
353
|
|
|
355
354
|
} else if ( response.data.status === 'unknown' ) {
|
|
356
|
-
// Server is working, but status 'unknown'?
|
|
357
|
-
// get
|
|
358
|
-
// no default behavior. What to return? Throw?
|
|
355
|
+
// Server is working, but status 'unknown'? Bad design, but this
|
|
356
|
+
// does get returned when install packages, so return true
|
|
359
357
|
log.info({
|
|
360
358
|
pre: `\`task_id\` "${ task_id }" has expired. If it's been `
|
|
361
359
|
+ `less than 1 hour since the task started, the task is `
|
|
362
360
|
+ `complete. See https://docassemble.org/docs/api.html#restart_status.`,
|
|
363
|
-
data: response,
|
|
361
|
+
data: response.data,
|
|
364
362
|
});
|
|
365
|
-
return
|
|
363
|
+
return true;
|
|
366
364
|
} // ends status type
|
|
367
365
|
|
|
368
366
|
} catch ( error ) {
|
|
@@ -378,8 +376,15 @@ da_i.has_task_finished = async function ( task_id, options ) {
|
|
|
378
376
|
|
|
379
377
|
}; // Ends da_i.has_task_finished()
|
|
380
378
|
|
|
379
|
+
da_i.wait_for_playground_to_restart = async ( task_id, options ) => {
|
|
380
|
+
return await da_i.wait_for_server_to_restart( "/api/restart_status", task_id, options );
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
da_i.wait_for_package_to_restart = async ( task_id, options ) => {
|
|
384
|
+
return await da_i.wait_for_server_to_restart( "/api/package_update_status", task_id, options );
|
|
385
|
+
};
|
|
381
386
|
|
|
382
|
-
da_i.wait_for_server_to_restart = async ( task_id, options ) => {
|
|
387
|
+
da_i.wait_for_server_to_restart = async ( api_endpoint, task_id, options ) => {
|
|
383
388
|
/** Wait for the server to finish restarting in case a module needs to load.
|
|
384
389
|
* This is a polling function because things may be returned
|
|
385
390
|
* that aren't equivalent to the server being done with a task. */
|
|
@@ -410,7 +415,7 @@ da_i.wait_for_server_to_restart = async ( task_id, options ) => {
|
|
|
410
415
|
while ( !done_restarting && num_tries <= max_loops ) {
|
|
411
416
|
num_tries++;
|
|
412
417
|
// Wait only a short time for the task to finish, not the full default timeout
|
|
413
|
-
done_restarting = await da_i.has_task_finished( task_id, { timeout: wait_for_response } );
|
|
418
|
+
done_restarting = await da_i.has_task_finished( api_endpoint, task_id, { timeout: wait_for_response } );
|
|
414
419
|
if ( !done_restarting ) {
|
|
415
420
|
if ( num_tries < max_loops ) {
|
|
416
421
|
// Show a slightly different message after a number of tries to reassure
|
|
@@ -10,10 +10,20 @@ let get_base_interview_url = async function () {
|
|
|
10
10
|
* When finishing a test, we go to the sign out page (which is supposed to help even
|
|
11
11
|
* when the user is not logged in.) We can't use `reset=2` because it caused an
|
|
12
12
|
* 'Unable to locate interview session' error. */
|
|
13
|
+
|
|
13
14
|
let server_url = session_vars.get_da_server_url();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
|
|
16
|
+
if (session_vars.get_install_type() === `server`) {
|
|
17
|
+
|
|
18
|
+
return `${ server_url }/interview?new_session=1&i=docassemble.${session_vars.get_repo_folder_name()}%3Adata/questions/`;
|
|
19
|
+
|
|
20
|
+
} else {
|
|
21
|
+
|
|
22
|
+
let dev_id = await docassemble_api_interface.get_dev_id();
|
|
23
|
+
let project_name = session_vars.get_project_name();
|
|
24
|
+
return `${ server_url }/interview?new_session=1&i=docassemble.playground${ dev_id }${ project_name }%3A`;
|
|
25
|
+
|
|
26
|
+
}
|
|
17
27
|
};
|
|
18
28
|
|
|
19
29
|
module.exports = get_base_interview_url;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const session_vars = require('../utils/session_vars');
|
|
4
|
+
const log = require('../utils/log');
|
|
5
|
+
// const path = require('path');
|
|
6
|
+
// const fs = require('fs');
|
|
7
|
+
const fg = require('fast-glob');
|
|
8
|
+
|
|
9
|
+
const server_install = async () => {
|
|
10
|
+
session_vars.save_install_type(`server`);
|
|
11
|
+
|
|
12
|
+
let current_dir = process.cwd();
|
|
13
|
+
let path = `${ current_dir }/docassemble/*`;
|
|
14
|
+
let dirs = fg.sync(
|
|
15
|
+
[ path ],
|
|
16
|
+
{
|
|
17
|
+
suppressErrors: true,
|
|
18
|
+
onlyDirectories: true
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
console.log(`\n===========\n${dirs}\n===========\n`);
|
|
22
|
+
|
|
23
|
+
let chosen_dir = dirs[0];
|
|
24
|
+
if ( dirs.length === 0 ) {
|
|
25
|
+
let msg = `There are 0 folders at ${path}. Tests won't run.`;
|
|
26
|
+
log.error({ type: `setup`, data: msg })
|
|
27
|
+
throw new ReferenceError( msg );
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if ( dirs.length > 1 ) {
|
|
31
|
+
log.warn({
|
|
32
|
+
type: `setup`,
|
|
33
|
+
data: `There are ${dirs.length} folders at ${path}: "${dirs.join('", ')}". ALKiln chose to use the folder "${chosen_dir}".`
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
session_vars.set_repo_folder_name( chosen_dir );
|
|
38
|
+
}; // Ends server_install();
|
|
39
|
+
|
|
40
|
+
server_install();
|
package/lib/docassemble/setup.js
CHANGED
|
@@ -39,7 +39,7 @@ const setup = async () => {
|
|
|
39
39
|
let task_id = await da_i.pull();
|
|
40
40
|
// Task id will be null if server did not need to restart
|
|
41
41
|
if ( task_id ) {
|
|
42
|
-
await da_i.
|
|
42
|
+
await da_i.wait_for_playground_to_restart( task_id );
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// Finish
|
package/lib/run_cucumber.js
CHANGED
|
@@ -6,64 +6,97 @@ const session_vars = require('./utils/session_vars');
|
|
|
6
6
|
|
|
7
7
|
const cucumber_api = require('@cucumber/cucumber/api');
|
|
8
8
|
|
|
9
|
-
(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"publishQuiet": true,
|
|
16
|
-
"require": "./lib/index.js",
|
|
17
|
-
"format": ["progress", "summary:cucumber-report.txt"],
|
|
18
|
-
"paths": ["docassemble/*/data/sources/*.feature"],
|
|
19
|
-
"retry": 1,
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// If they're running it from an interview on their server instead of from GitHub
|
|
24
|
-
if ( session_vars.get_origin() === `playground` ) {
|
|
25
|
-
provided_config.paths.push(`/usr/share/docassemble/files/playgroundsources/${session_vars.get_user_id()}/${session_vars.get_user_project_name()}/*.feature`);
|
|
26
|
-
// For S3
|
|
27
|
-
provided_config.paths.push(`/tmp/playgroundsources/${session_vars.get_user_id()}/${session_vars.get_user_project_name()}/*.feature`);
|
|
9
|
+
function build_config( command_args ) {
|
|
10
|
+
// Turn all the paths to the sources folders into paths to the feature files
|
|
11
|
+
let sources_paths = session_vars.get_sources_paths();
|
|
12
|
+
let feature_paths = [];
|
|
13
|
+
for ( let source of sources_paths ) {
|
|
14
|
+
feature_paths.push( `${ source }/*.feature` );
|
|
28
15
|
}
|
|
29
16
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
const config = {
|
|
18
|
+
publishQuiet: true,
|
|
19
|
+
require: `./lib/index.js`,
|
|
20
|
+
format: [`progress`, `summary:cucumber-report.txt`],
|
|
21
|
+
paths: feature_paths,
|
|
22
|
+
retry: 1,
|
|
33
23
|
}
|
|
34
24
|
|
|
25
|
+
// Normalize tag expressions, if there are any
|
|
26
|
+
let tags = command_args[`_`];
|
|
35
27
|
if (tags.length > 0) {
|
|
36
28
|
let joined_tags = '';
|
|
37
29
|
// Cucumber expects tags in boolean expressions, like (@a0 or @a1) and @random.
|
|
38
30
|
// If we get a list of only tags, then "or" them all together
|
|
39
|
-
if (tags.every((val) => val.startsWith(
|
|
40
|
-
joined_tags = tags.join(
|
|
31
|
+
if (tags.every((val) => val.startsWith(`@`))) {
|
|
32
|
+
joined_tags = tags.join(` or `);
|
|
41
33
|
}
|
|
42
34
|
// If we get a list of seemingly no tags, assume they meant to use tags
|
|
43
|
-
else if (tags.every((val) => !val.startsWith(
|
|
44
|
-
joined_tags = tags.map((val) =>
|
|
35
|
+
else if (tags.every((val) => !val.startsWith(`@`))) {
|
|
36
|
+
joined_tags = tags.map((val) => `@` + val).join(` or `);
|
|
45
37
|
}
|
|
46
38
|
// If it's a mix of tags and no tags, it's probably a boolean expression!
|
|
47
39
|
else {
|
|
48
|
-
joined_tags = tags.join(
|
|
40
|
+
joined_tags = tags.join(` `);
|
|
49
41
|
}
|
|
50
42
|
|
|
51
|
-
|
|
52
|
-
}
|
|
43
|
+
config.tags = joined_tags;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return config;
|
|
47
|
+
}; // Ends build_config()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
(async function main() {
|
|
51
|
+
/** Create the cucumber config, run the tests, create and
|
|
52
|
+
* manipluate the output. */
|
|
53
|
+
const environment = { cwd: process.cwd() };
|
|
54
|
+
console.log("alkiln-run: environment: %o", environment);
|
|
55
|
+
|
|
56
|
+
// We'll use the user info to construct the possible paths to /sources
|
|
57
|
+
let id = session_vars.get_user_id();
|
|
58
|
+
// `project` is the name in the server docassemble "Project" page
|
|
59
|
+
let project = session_vars.get_user_project_name();
|
|
60
|
+
|
|
61
|
+
// process.argv is a list of strings of commands and args.
|
|
62
|
+
// If using `npm run cucumber`, use `--` before `--sources=./foo`
|
|
63
|
+
let argv = require(`minimist`)( process.argv.slice(2), {
|
|
64
|
+
default: {
|
|
65
|
+
sources: [
|
|
66
|
+
`./docassemble/*/data/sources`, // In GitHub folder
|
|
67
|
+
`/usr/share/docassemble/files/playgroundsources/${id}/${project}/*.feature`, // From playground without S3
|
|
68
|
+
`/tmp/playgroundsources/${id}/${project}/*.feature` // From playground with S3
|
|
69
|
+
],
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Make sure `sources` is always a list
|
|
74
|
+
let sources_list = argv.sources;
|
|
75
|
+
if ( typeof argv.sources === `string` ) {
|
|
76
|
+
sources_list = [ argv.sources ];
|
|
77
|
+
}
|
|
53
78
|
|
|
79
|
+
let one_dir_exists = session_vars.set_sources_paths( sources_list );
|
|
80
|
+
if ( !one_dir_exists ) {
|
|
81
|
+
// If we don't throw, it'll be more confusing when no tests run
|
|
82
|
+
throw new ReferenceError( `ALKiln ERROR: ALKiln can't find your "sources" folder(s). See above for details.`);
|
|
83
|
+
}
|
|
84
|
+
|
|
54
85
|
const { runConfiguration } = await cucumber_api.loadConfiguration({
|
|
55
|
-
provided:
|
|
86
|
+
provided: build_config( argv ),
|
|
56
87
|
}, environment);
|
|
88
|
+
|
|
57
89
|
console.log("alkiln-run: runConfiguration: %o", runConfiguration);
|
|
90
|
+
|
|
58
91
|
var path = require('path');
|
|
59
92
|
var codeDir = path.dirname(require.main.filename);
|
|
93
|
+
console.log("alkiln-run: working directory: %o", codeDir);
|
|
60
94
|
const support = await cucumber_api.loadSupport({ support: {
|
|
61
|
-
requireModules: [`${ codeDir }/index.js`],
|
|
62
|
-
requirePaths: [ `${ codeDir }/index.js`],
|
|
95
|
+
requireModules: [ `${ codeDir }/index.js` ],
|
|
96
|
+
requirePaths: [ `${ codeDir }/index.js` ],
|
|
63
97
|
importPaths: [],
|
|
64
98
|
}, sources: {paths: []}}, environment);
|
|
65
99
|
|
|
66
|
-
console.log("alkiln-run: working directory: %o", codeDir);
|
|
67
100
|
console.log(`\n\n`);
|
|
68
101
|
|
|
69
102
|
const { success } = await cucumber_api.runCucumber({...runConfiguration, support});
|
|
@@ -86,6 +119,6 @@ const cucumber_api = require('@cucumber/cucumber/api');
|
|
|
86
119
|
|
|
87
120
|
// Put it in the correct file in the correct folder
|
|
88
121
|
let artifacts_path = session_vars.get_artifacts_path_name();
|
|
89
|
-
fs.appendFileSync( `${ artifacts_path}/${ log.debug_log_file }`, data);
|
|
122
|
+
fs.appendFileSync( `${ artifacts_path }/${ log.debug_log_file }`, data);
|
|
90
123
|
});
|
|
91
124
|
})();
|