diff --git a/.expeditor/config.yml b/.expeditor/config.yml index 75f4400434..3ed340f24e 100644 --- a/.expeditor/config.yml +++ b/.expeditor/config.yml @@ -19,6 +19,8 @@ pipelines: - verify: description: Pull Request validation tests public: true + - end_to_end: + description: Builder end to end test - release_builder: description: Builder release process - post_habitat_release: diff --git a/.expeditor/end_to_end.pipeline.yml b/.expeditor/end_to_end.pipeline.yml new file mode 100644 index 0000000000..7f4127f0b1 --- /dev/null +++ b/.expeditor/end_to_end.pipeline.yml @@ -0,0 +1,22 @@ +expeditor: + defaults: + buildkite: + timeout_in_minutes: 30 + env: + HAB_ORIGIN: "habitat" + HAB_BLDR_CHANNEL: "unstable" + +steps: +####################################################################### +# End to end test +####################################################################### + - label: ":linux: Builder End to End" + command: + - .expeditor/scripts/end_to_end/builder_worker_test.sh + expeditor: + account: + - github + executor: + docker: + environment: + - GITHUB_USER diff --git a/.expeditor/scripts/end_to_end/builder_worker_test.sh b/.expeditor/scripts/end_to_end/builder_worker_test.sh new file mode 100755 index 0000000000..983acf7a92 --- /dev/null +++ b/.expeditor/scripts/end_to_end/builder_worker_test.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +#set -euo pipefail + +source .expeditor/scripts/post_habitat_release/shared.sh + +export BLDR_CHANNEL=${HAB_BLDR_CHANNEL:=unstable} + +export HAB_BLDR_URL=https://bldr.habitat.sh +export BLDR_ORIGIN=${HAB_BLDR_ORIGIN:=habitat} +export HAB_LICENSE=accept + +curlbash_hab "${BUILD_PKG_TARGET:=x86_64-linux}" + +echo "--- Generating signing key" +hab origin key generate ${BLDR_ORIGIN} + +# Clean out settings +sudo rm -rf /hab/user +sudo rm -rf /hab/svc + +# Run the test +.expeditor/scripts/end_to_end/run_e2e.sh + +echo "--- Terminating Habitat Supervisor" +# Terminate supervisor and remove specs files for builder +sudo hab sup term +sleep 2 + +echo "Remove builder spec files" +sudo rm -rf /hab/sup/default/specs/builder-* diff --git a/.expeditor/scripts/end_to_end/run_e2e.sh b/.expeditor/scripts/end_to_end/run_e2e.sh new file mode 100755 index 0000000000..96190122d3 --- /dev/null +++ b/.expeditor/scripts/end_to_end/run_e2e.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +set -eou pipefail +umask 0022 + +# Always run from builder root directory. +export ROOT_DIR +ROOT_DIR=$(pwd) +WORK_DIR=${ROOT_DIR}/test/end-to-end/worker-test + +source "${WORK_DIR}"/bldr-end-to-end.env +source "${WORK_DIR}"/shared.sh + +export HAB_FUNC_TEST=1 + +start_init + +start_builder + +start_jobsrv + +apply_db_password +sleep 5 + +sudo cp "${WORK_DIR}"/builder-github-app.pem /hab/svc/builder-api/files + +echo "start_worker" +start_worker + +cat < /tmp/builder_worker.toml +log_level='trace' +github.app_id = 8053 +github.webhook_secret='' +EOT + +hab config apply builder-worker.default "$(date +%s)" /tmp/builder_worker.toml +sleep 3 +echo "worker config updated" +sudo cp "${WORK_DIR}"/builder-github-app.pem /hab/svc/builder-worker/files + +# HACK To get builder-worker HAB_FUNC_TEST changes +#hab svc stop habitat/builder-worker +#sleep 5 +#cp ${WORK_DIR}/bldr-worker /hab/pkgs/habitat/builder-worker/10041/20220510143824/bin +# +#sleep 2 +#hab svc start habitat/builder-worker +# HACK To get builder-worker HAB_FUNC_TEST changes + +while hab svc status | grep --quiet down; +do + sleep 5 +done + +echo "Services have started - continuing with tests" + +# Need to test if we need this file in this location and remove if not. +sudo cp "${WORK_DIR}"/fixtures/neurosis-20171211220037.pub /hab/svc/builder-worker/files +sudo cp "${WORK_DIR}"/fixtures/neurosis-20171211220037.sig.key /hab/svc/builder-worker/files + +sudo cp /hab/svc/builder-api/files/bldr-*.pub /hab/svc/builder-worker/files +sudo cp /hab/svc/builder-api/files/bldr-*.box.key /hab/svc/builder-worker/files + +wait_for_migrations + +clean_test_artifacts + +# Install packages +hab pkg install core/openssl +hab pkg install core/node --binlink +hab pkg install core/coreutils + +cd test/end-to-end/worker-test +npm install mocha + +if ! command -v npm >/dev/null 2>&1; then + hab pkg install core/node -b +fi + +if ! [ -f /usr/bin/env ]; then + hab pkg binlink core/coreutils -d /usr/bin env +fi + +if ! [ -d node_modules/mocha ]; then + npm install mocha +fi + +if ! [ -d node_modules/chai ]; then + npm install chai +fi + +if ! [ -d node_modules/supertest ]; then + npm install supertest +fi + +if ! [ -d node_modules/superagent-binary-parser ]; then + npm install superagent-binary-parser +fi + +if npm run mocha; then + echo "Setup tests passed" +else + mocha_exit_code=$? + echo "Setup tests failed" +fi + +echo "Exiting run script" +exit ${mocha_exit_code:-0} diff --git a/test/end-to-end/worker-test/bldr-end-to-end.env b/test/end-to-end/worker-test/bldr-end-to-end.env new file mode 100644 index 0000000000..7cc99fd788 --- /dev/null +++ b/test/end-to-end/worker-test/bldr-end-to-end.env @@ -0,0 +1,122 @@ +#!/bin/bash + +# Only enable and set these if you are using externally hosted PostgreSQL(RDS, Azure Database for PostgreSql etc). +# set PG_EXT_ENABLED = true, uncomment PG_USER and update PG_PASSWORD and POSTGRES_HOST appropriatlely. +# Migrations from local PostgreSQL to RDS are curently not supported. +#export PG_EXT_ENABLED=false +#export PG_USER=hab +#export PG_PASSWORD=hab + +# The endpoint and port for your Postgresql(local, RDS, Azure Database for PostgreSql etc) +# Change only if needed +export POSTGRES_HOST=localhost +export POSTGRES_PORT=5432 + +# The endpoint, key and secret for your Minio instance (see README) +# Change these before the first install if needed +export MINIO_ENDPOINT=http://localhost:9000 +export MINIO_BUCKET=habitat-builder-artifact-store.local +export MINIO_ACCESS_KEY=depot +export MINIO_SECRET_KEY=password + +# If you'd like to use AWS S3 instead of Minio, +# set S3_ENABLED=true and change the other S3 associated variables appropriately. +export S3_ENABLED=false +export S3_REGION=us-west-2 +export S3_BUCKET=habitat-builder-artifact-store.local +export S3_ACCESS_KEY=depotaccesskey +export S3_SECRET_KEY=depotsecretkey + +export GITHUB_APP_ID=8053 +export GITHUB_API_URL="https://api.github.com" +export GITHUB_APP_URL="https://github.com/apps/habitat-builder-dev-studio" + +# If you'd like to use Arifactory instead of Minio, uncomment +# and set the following variables appropriately. +# IMPORTANT: See the README for more info +# export ARTIFACTORY_ENABLED=true +# export ARTIFACTORY_API_URL=http://localhost:8081 +# export ARTIFACTORY_API_KEY=foo +# export ARTIFACTORY_REPO=habitat-builder-artifact-store + +# Modify these as needed for the on-premise OAuth2 provider. +# The variables below are configured for GitHub by default, +# but appropriate values for Bitbucket, GitLab, Azure AD and Okta +# are also included as comments. + +# Whether SSL is enabled for the on-prem depot +export APP_SSL_ENABLED=false + +# The URL for this instance of the on-prem depot +# IMPORTANT: If SSL is enabled, APP_URL should start be https +export APP_URL=http://localhost + +# The OAUTH_PROVIDER value can be "github", "gitlab", "bitbucket", "azure-ad", +# "okta" or "chef-automate" +export OAUTH_PROVIDER=github +# export OAUTH_PROVIDER=bitbucket +# export OAUTH_PROVIDER=gitlab +# export OAUTH_PROVIDER=azure-ad +# export OAUTH_PROVIDER=okta +# export OAUTH_PROVIDER=chef-automate + +# The OAUTH_USERINFO_URL is the API endpoint that will be used for user info +export OAUTH_USERINFO_URL=https://api.github.com/user +# export OAUTH_USERINFO_URL=https://api.bitbucket.org/1.0/user +# export OAUTH_USERINFO_URL=https://gitlab.com/oauth/userinfo +# export OAUTH_USERINFO_URL=https://login.microsoftonline.com//openid/userinfo +# export OAUTH_USERINFO_URL=https://.com/oauth2/v1/userinfo +# export OAUTH_USERINFO_URL=https:///session/userinfo + +# The OAUTH_AUTHORIZE_URL is the *fully qualified* OAuth2 authorization endpoint +export OAUTH_AUTHORIZE_URL=https://github.com/login/oauth/authorize +# export OAUTH_AUTHORIZE_URL=https://bitbucket.org/site/oauth2/authorize +# export OAUTH_AUTHORIZE_URL=https://gitlab.com/oauth/authorize +# export OAUTH_AUTHORIZE_URL=https://login.microsoftonline.com//oauth2/authorize +# export OAUTH_AUTHORIZE_URL=https://.com/oauth2/v1/authorize +# export OAUTH_AUTHORIZE_URL=https:///session/new + +# The OAUTH_SIGNUP_URL is the link used to register users with the OAUTH provider +export OAUTH_SIGNUP_URL=https://github.com/join +# export OAUTH_SIGNUP_URL=https://bitbucket.org/account/signup/ +# export OAUTH_SIGNUP_URL=https://gitlab.com/users/sign_in#register-pane + +# The OAUTH_TOKEN_URL is the *fully qualified* OAuth2 token endpoint +export OAUTH_TOKEN_URL=https://github.com/login/oauth/access_token +# export OAUTH_TOKEN_URL=https://bitbucket.org/site/oauth2/access_token +# export OAUTH_TOKEN_URL=https://gitlab.com/oauth/token +# export OAUTH_TOKEN_URL=https://login.microsoftonline.com/tenant-id/oauth2/token +# export OAUTH_TOKEN_URL=https://your.okta.domain.com/oauth2/v1/token +# export OAUTH_TOKEN_URL=https:///session/token + +# The OAUTH_REDIRECT_URL is the registered OAuth2 redirect +# IMPORTANT: If SSL is enabled, the redirect URL should be https +# IF SCALING FRONTEND, POINT THIS AT YOUR LOAD BALANCER +export OAUTH_REDIRECT_URL=http://localhost/ + +# The OAUTH_CLIENT_ID is the registered OAuth2 client id +export OAUTH_CLIENT_ID=Iv1.04b78696442708f6 + +# The OAUTH_CLIENT_SECRET is the registerd OAuth2 client secret +export OAUTH_CLIENT_SECRET=b4acf029896e9c7fb6613390124e8ae3c35889aa + +export BLDR_CHANNEL=${BLDR_CHANNEL:=unstable} + +export BLDR_ORIGIN=${BLDR_ORIGIN:=habitat} + +export HAB_BLDR_URL=${HAB_BLDR_URL:=https://bldr.habitat.sh/} + +# From the Automate CLI use +# export HAB_BLDR_URL=https://MY_ON_PREM_URL/bldr/v1/ + +# Modify if you are splitting frontend and backend on separate nodes +# If so, this should include a --peer argument for each builder IP or hostname +# including all frontend and backend nodes. +# For example: export HAB_BLDR_PEER_ARG="--peer host1 --peer host2 --peer host3" +export HAB_BLDR_PEER_ARG="" + +# Help us make Habitat better! Opt into analytics by changing the ANALYTICS_ENABLED +# setting below to true, then optionally provide your company name. (Analytics is +# disabled by default. See our privacy policy at https://www.habitat.sh/legal/privacy-policy/.) +export ANALYTICS_ENABLED=false +export ANALYTICS_COMPANY_NAME="" diff --git a/test/end-to-end/worker-test/builder-github-app.pem b/test/end-to-end/worker-test/builder-github-app.pem new file mode 100644 index 0000000000..760a7227ab --- /dev/null +++ b/test/end-to-end/worker-test/builder-github-app.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAsB/t16ONW0qFa5fzVTWRBFSjc2ySIMZPxhWZ9OboMC6sccyE +ASvZZXC6r9Ogmubx7EliS6Ljwcu3L30UCAIV5KwAMt72uT3Y1EUG6/QpoWdLZD13 +NVwXgi5xKmLib16hIz5tqfk32yu44ZmXX1d9KlGkGrcxyMkqQ6XcoOBiG31fYc/b +rJwouq0/jK2zIDaXcwXe0FtV39rXmmV5bGG2mP7pUEsCP9/trtWnE/VxDh0JbB06 +Q9Ue+H3CpIqSRJyBen2rR0/tVAkN0fvmvknUosF0W3uyOTOzG6QsebfPC0qANvo4 +FTfklPMGE3kXYKMIwF2eP8kSBQoMtwNKdlTOkQIDAQABAoIBAQCm9/4ROnoeG83U +rJ8BHuE2C89Q85KtA5RJI91qsSVZ2EWfNNXIDnvwkShK3AQcFekvqnpHXksxRCGM +IbXFycRbnzwo/ftErf9LbY0cT8LPww9UHnlO2tlmjwS1vD0Se9GjieGJDD0JyK9j +IKrCtjxwzCkZMqc9xIgOzcUVtuMLTwNn/MjvtEVHZB7OiEVPfZacxMRboeR4T/y2 +9gs2uR7p0D7w4uioRwOWhaD84Yu08rYluKf4eHhkJ3Z0FgE+IABMVR+luP5Rc3XG +gqxTVzsLEsCY1J/HUx8AIENC6kUVLpvhT4Gfj1A0+GHbqtFaz/nCudBT4OCzzHnp +4WvuabvhAoGBANZLlKo8YIqVDYZH0Iin+98ZHzCKd7hnJOSZG7IyRVci7ODPlxkQ +Y3ZNKgl9l8lGfddPStfV/WPW+X+3V8YHzP8KW57Z9I47oWRszOmfkZH6k5EBNNlN +RtWS+j7CofLHXLC0WhxGnWdmwPm1lfL+0iDjVgyJad44lHvGcKA3nC19AoGBANJm +pvqRugkdHzU1LWHhtS2CSq+wU3mhqmIYW/khScf3jZnGXvmYo9VHaQP8Ol2tNVrW +NiHZgboB8mFrWLMvXV92dtzf1dRRlJXdXYVhnst2ApvdlJSF9VPKzq9cTHBromL0 +aJHzZyh45VK8D2hLwhpX82yja920U0s+9kWs1gGlAoGBAIOITK3Y9WDacDy1o7fd +c+nSH6TJuQZEuQ5fM/LCjHEZjM/IW3QU7RfV8TF2F06BqAgaMVQdkrRPZUERJhz0 ++MyqbOYp3cPXsg5UAcp8GPvDFARy+x9OIWZCGrue94xu9eO4L38fkMnA9j3PSUSn +SA7LuytixMqTbAA1IzgUKhZ5AoGADV/lIWx+DPJC95jnx58Xa0srADwPMl4qfI3b +2txY2lNMNwg7JVako3wkmtI/y5ClIZ8YVdKDwpU5GSQ/wTQzlUvshmHcL+D3twcZ +WM85zukzhUDynvZc//SWauNNIKBW9D9hHlYAqrv0p+u4dwLJ1e7dVHyW8MLzwIbC +FO/FstkCgYAdQ+f4ElBl871PqdQd2hbSpxt9sfUEoXTmmh/YHiS01iGc1mFis1gw +kW64zKbmqbwcJQdF1xNKsDjKn/NFy/1lql4VEblLx4/3BNNfrGMlzEktAN1UNocx +RUFoG5ak1BYVr9gMA+NEDG65t1gMqzTChxpFSdF8NndMPBgWsvAH6g== +-----END RSA PRIVATE KEY----- diff --git a/test/end-to-end/worker-test/fixtures/neurosis-20171211220037.pub b/test/end-to-end/worker-test/fixtures/neurosis-20171211220037.pub new file mode 100644 index 0000000000..0c024db9bc --- /dev/null +++ b/test/end-to-end/worker-test/fixtures/neurosis-20171211220037.pub @@ -0,0 +1,4 @@ +SIG-PUB-1 +neurosis-20171211220037 + +iAqYvmgFiMI6IijSWQyiLPd8hFVmqTfzRCpJdqmomFQ= \ No newline at end of file diff --git a/test/end-to-end/worker-test/fixtures/neurosis-20171211220037.sig.key b/test/end-to-end/worker-test/fixtures/neurosis-20171211220037.sig.key new file mode 100644 index 0000000000..249c8d2ba3 --- /dev/null +++ b/test/end-to-end/worker-test/fixtures/neurosis-20171211220037.sig.key @@ -0,0 +1,4 @@ +SIG-SEC-1 +neurosis-20171211220037 + +Wh4SVmUV1eAoxL/CWFQpA8YF1kawikEM0CD4pNVRQ16ICpi+aAWIwjoiKNJZDKIs93yEVWapN/NEKkl2qaiYVA== \ No newline at end of file diff --git a/test/end-to-end/worker-test/fixtures/neurosis-testapp-0.1.3-20171205003213-x86_64-linux.hart b/test/end-to-end/worker-test/fixtures/neurosis-testapp-0.1.3-20171205003213-x86_64-linux.hart new file mode 100644 index 0000000000..57b8b7d70c Binary files /dev/null and b/test/end-to-end/worker-test/fixtures/neurosis-testapp-0.1.3-20171205003213-x86_64-linux.hart differ diff --git a/test/end-to-end/worker-test/fixtures/neurosis-testapp-0.1.3-20171206004121-x86_64-linux.hart b/test/end-to-end/worker-test/fixtures/neurosis-testapp-0.1.3-20171206004121-x86_64-linux.hart new file mode 100644 index 0000000000..97cb92cf3c Binary files /dev/null and b/test/end-to-end/worker-test/fixtures/neurosis-testapp-0.1.3-20171206004121-x86_64-linux.hart differ diff --git a/test/end-to-end/worker-test/fixtures/neurosis-testapp-0.1.3-20181116180420-x86_64-linux-kernel2.hart b/test/end-to-end/worker-test/fixtures/neurosis-testapp-0.1.3-20181116180420-x86_64-linux-kernel2.hart new file mode 100644 index 0000000000..df529fee5f Binary files /dev/null and b/test/end-to-end/worker-test/fixtures/neurosis-testapp-0.1.3-20181116180420-x86_64-linux-kernel2.hart differ diff --git a/test/end-to-end/worker-test/fixtures/neurosis-testapp-0.1.3-20190327162537-x86_64-linux.hart b/test/end-to-end/worker-test/fixtures/neurosis-testapp-0.1.3-20190327162537-x86_64-linux.hart new file mode 100644 index 0000000000..95ce9fc62a Binary files /dev/null and b/test/end-to-end/worker-test/fixtures/neurosis-testapp-0.1.3-20190327162537-x86_64-linux.hart differ diff --git a/test/end-to-end/worker-test/fixtures/testapp_original/plan.ps1 b/test/end-to-end/worker-test/fixtures/testapp_original/plan.ps1 new file mode 100644 index 0000000000..764e909225 --- /dev/null +++ b/test/end-to-end/worker-test/fixtures/testapp_original/plan.ps1 @@ -0,0 +1,23 @@ +$pkg_name="testapp" +$pkg_origin="neurosis" +$pkg_version="0.1.4" +$pkg_description="This is actually just an empty package" +$pkg_upstream_url="http://www.habitat.sh/" +$pkg_license=@("Apache-2") +$pkg_build_deps=@("core/dmake") +$pkg_bin_dirs=@("bin") +$pkg_lib_dirs=@("lib") + +function Invoke-SetupEnvironment { +} + +function Invoke-Build { +} + + +function Invoke-Install { +} + + +function Invoke-Check { +} \ No newline at end of file diff --git a/test/end-to-end/worker-test/fixtures/testapp_original/plan.sh b/test/end-to-end/worker-test/fixtures/testapp_original/plan.sh new file mode 100644 index 0000000000..67bc5141e2 --- /dev/null +++ b/test/end-to-end/worker-test/fixtures/testapp_original/plan.sh @@ -0,0 +1,17 @@ +pkg_name=testapp +pkg_origin=neurosis +pkg_version="0.1.3" +pkg_maintainer="The Habitat Maintainers " +pkg_license=("Apache-2.0") +pkg_deps=(core/glibc) +pkg_description="This is a dummy app for testing builder APIs" +pkg_upstream_url="https://habitat.sh" + +do_build() { + return 0 +} + +do_install() { + return 0 +} + diff --git a/test/end-to-end/worker-test/package-lock.json b/test/end-to-end/worker-test/package-lock.json new file mode 100644 index 0000000000..a445c18525 --- /dev/null +++ b/test/end-to-end/worker-test/package-lock.json @@ -0,0 +1,932 @@ +{ + "name": "hab-worker-tests", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" + }, + "ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" + }, + "chai": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", + "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", + "requires": { + "assertion-error": "^1.0.1", + "deep-eql": "^0.1.3", + "type-detect": "^1.0.0" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "cookiejar": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==" + }, + "deep-eql": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "requires": { + "type-detect": "0.1.1" + }, + "dependencies": { + "type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=" + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" + }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "formidable": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", + "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "requires": { + "chalk": "^4.0.0" + } + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mocha": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.0.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "nanoid": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "requires": { + "randombytes": "^2.1.0" + } + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "superagent-binary-parser": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/superagent-binary-parser/-/superagent-binary-parser-1.0.1.tgz", + "integrity": "sha1-5f0KqVlg+rBdf3l3VzMdJq+n950=" + }, + "supertest": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.4.2.tgz", + "integrity": "sha512-WZWbwceHUo2P36RoEIdXvmqfs47idNNZjCuJOqDz6rvtkk8ym56aU5oglORCpPeXGxT7l9rkJ41+O1lffQXYSA==", + "requires": { + "methods": "^1.1.2", + "superagent": "^3.8.3" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "type-detect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", + "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "workerpool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==" + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + } + } +} diff --git a/test/end-to-end/worker-test/package.json b/test/end-to-end/worker-test/package.json new file mode 100644 index 0000000000..64796185ff --- /dev/null +++ b/test/end-to-end/worker-test/package.json @@ -0,0 +1,18 @@ +{ + "name": "hab-worker-tests", + "version": "0.0.1", + "description": "Habitat Worker Tests", + "main": "index.js", + "scripts": { + "mocha": "mocha --timeout 91000 src/api.js --bail" + }, + "author": "The Habitat Humans", + "license": "Apache-2.0", + "dependencies": { + "chai": "^3.5.0", + "jwt-builder": "1.1.0", + "mocha": "^8.4.0", + "superagent-binary-parser": "^1.0.1", + "supertest": "^3.4.2" + } +} diff --git a/test/end-to-end/worker-test/shared.sh b/test/end-to-end/worker-test/shared.sh new file mode 100755 index 0000000000..799bec109f --- /dev/null +++ b/test/end-to-end/worker-test/shared.sh @@ -0,0 +1,398 @@ +#!/bin/bash + +set -eou pipefail +umask 0022 + +sudo () { + [[ $EUID = 0 ]] || set -- command sudo -E "$@" + "$@" +} +export -f sudo + +user_toml_warn() { + if [ -f "/hab/svc/$1/user.toml" ]; then + mv "/hab/svc/$1/user.toml" "/hab/svc/$1/user.toml.bak" + echo "WARNING: Previous user.toml exists in deprecated location. All user.toml" + echo "files should be deposited into the path /hab/user/$1/config/user.toml." + echo "Deprecated user.toml has been renamed user.toml.bak." + fi +} +export -f user_toml_warn + +init_datastore() { + user_toml_warn builder-datastore + mkdir -p /hab/user/builder-datastore/config + cat < /hab/user/builder-datastore/config/user.toml +max_locks_per_transaction = 128 +dynamic_shared_memory_type = 'none' + +[superuser] +name = 'hab' +password = 'hab' +EOT +} +export -f init_datastore + +configure() { + export PGPASSWORD PGUSER + if [ "${PG_EXT_ENABLED:-false}" = "true" ]; then + PGUSER=${PG_USER:-hab} + PGPASSWORD=${PG_PASSWORD:-hab} + else + PGUSER="hab" + PGPASSWORD="" + fi + + export ANALYTICS_ENABLED=${ANALYTICS_ENABLED:="false"} + export ANALYTICS_COMPANY_ID + export ANALYTICS_COMPANY_NAME + export ANALYTICS_WRITE_KEY + + if [ $ANALYTICS_ENABLED = "true" ]; then + ANALYTICS_WRITE_KEY=${ANALYTICS_WRITE_KEY:="NAwVPW04CeESMW3vtyqjJZmVMNBSQ1K1"} + ANALYTICS_COMPANY_ID=${ANALYTICS_COMPANY_ID:="builder-on-prem"} + else + ANALYTICS_WRITE_KEY="" + ANALYTICS_COMPANY_ID="" + ANALYTICS_COMPANY_NAME="" + fi + + export LOAD_BALANCED="false" + if [ "${HAB_BLDR_PEER_ARG:-}" != "" ]; then + LOAD_BALANCED="true" + fi + + # don't write out the builder-minio user.toml if using S3 or Artifactory directly + if [ "${S3_ENABLED:-false}" = "false" ] && [ "${ARTIFACTORY_ENABLED:-false}" = "false" ]; then + if [ "${FRONTEND_INSTALL:-0}" != 1 ]; then + user_toml_warn builder-minio + mkdir -p /hab/user/builder-minio/config + cat < /hab/user/builder-minio/config/user.toml +key_id = "$MINIO_ACCESS_KEY" +secret_key = "$MINIO_SECRET_KEY" +bucket_name = "$MINIO_BUCKET" +EOT + fi + fi + + mkdir -p /hab/user/builder-api/config + export S3_BACKEND="minio" + if [ "${S3_ENABLED:-false}" = "true" ]; then + S3_BACKEND="aws" + MINIO_ENDPOINT=$S3_REGION + MINIO_ACCESS_KEY=$S3_ACCESS_KEY + MINIO_SECRET_KEY=$S3_SECRET_KEY + MINIO_BUCKET=$S3_BUCKET + fi + if [ "${ARTIFACTORY_ENABLED:-false}" = "true" ]; then + FEATURES_ENABLED="jobsrv ARTIFACTORY" + else + FEATURES_ENABLED="jobsrv" + ARTIFACTORY_API_URL="http://localhost:8081" + ARTIFACTORY_API_KEY="none" + ARTIFACTORY_REPO="habitat-builder-artifact-store" + fi + PG_HOST=${POSTGRES_HOST:-localhost} + PG_PORT=${POSTGRES_PORT:-5432} + user_toml_warn builder-api + cat < /hab/user/builder-api/config/user.toml +log_level="debug" +jobsrv_enabled = true + +[http] +handler_count = 10 + +[github] +app_id = 8053 +webhook_secret = "" +[api] +features_enabled = "$FEATURES_ENABLED" +targets = ["x86_64-linux", "x86_64-linux-kernel2", "x86_64-windows"] + +[depot] +jobsrv_enabled = true + +[oauth] +provider = "$OAUTH_PROVIDER" +userinfo_url = "$OAUTH_USERINFO_URL" +token_url = "$OAUTH_TOKEN_URL" +redirect_url = "$OAUTH_REDIRECT_URL" +client_id = "$OAUTH_CLIENT_ID" +client_secret = "$OAUTH_CLIENT_SECRET" + +[s3] +backend = "$S3_BACKEND" +key_id = "$MINIO_ACCESS_KEY" +secret_key = "$MINIO_SECRET_KEY" +endpoint = "$MINIO_ENDPOINT" +bucket_name = "$MINIO_BUCKET" + +[artifactory] +api_url = "$ARTIFACTORY_API_URL" +api_key = "$ARTIFACTORY_API_KEY" +repo = "$ARTIFACTORY_REPO" + +[memcache] +ttl = 15 + +[datastore] +user = "$PGUSER" +password = "$PGPASSWORD" +connection_timeout_sec = 5 +host = "$PG_HOST" +port = $PG_PORT +ssl_mode = "prefer" +EOT +user_toml_warn builder-api-proxy +mkdir -p /hab/user/builder-api-proxy/config +cat < /hab/user/builder-api-proxy/config/user.toml +log_level="info" +enable_builder = true +app_url = "${APP_URL}" +load_balanced = ${LOAD_BALANCED} + +[oauth] +provider = "$OAUTH_PROVIDER" +client_id = "$OAUTH_CLIENT_ID" +authorize_url = "$OAUTH_AUTHORIZE_URL" +redirect_url = "$OAUTH_REDIRECT_URL" +signup_url = "$OAUTH_SIGNUP_URL" + +[nginx] +max_body_size = "2048m" +proxy_send_timeout = 180 +proxy_read_timeout = 180 +enable_gzip = true +enable_caching = true +limit_req_zone_unknown = "\$limit_unknown zone=unknown:10m rate=30r/s" +limit_req_unknown = "burst=90 nodelay" +limit_req_zone_known = "\$http_x_forwarded_for zone=known:10m rate=30r/s" +limit_req_known = "burst=90 nodelay" + +[http] +keepalive_timeout = "180s" + +[server] +listen_tls = $APP_SSL_ENABLED + +[analytics] +enabled = $ANALYTICS_ENABLED +company_id = "$ANALYTICS_COMPANY_ID" +company_name = "$ANALYTICS_COMPANY_NAME" +write_key = "$ANALYTICS_WRITE_KEY" +EOT +} +export -f configure + +function psql() { + PGPASSWORD=$(cat /hab/svc/builder-datastore/config/pwfile) hab pkg exec core/postgresql psql -U hab -h 127.0.0.1 -p 5432 "$@" +} +export -f psql + +clean_test_artifacts() { + echo "Performing DB cleanup" + local sql origins + origins=( neurosis ) + + # clean origins + local origins origin_tables + origin_tables=( origin_integrations origin_project_integrations origin_secrets origin_private_encryption_keys origin_public_encryption_keys origin_members origin_channels origin_invitations origin_packages origin_projects origin_public_keys origin_secret_keys audit_package audit_package_group origin_package_settings ) + sql= + + for origin in "${origins[@]}"; do + sql+="DELETE FROM origin_channel_packages WHERE channel_id IN (SELECT id FROM origin_channels WHERE origin='$origin');" + + for table in "${origin_tables[@]}"; do + sql+="DELETE FROM $table WHERE origin='$origin';" + done + + sql+="DELETE FROM origins WHERE name='$origin';" + done + + psql builder -q -c "$sql" + + # clean users + local users account_tables + users=( bobo mystique lkennedy ) + sql= + + for user in "${users[@]}"; do + sql+="DELETE FROM accounts WHERE name='$user';" + done + + psql builder -q -c "$sql" + + # clean jobs + sql= + + for origin in "${origins[@]}"; do + sql+="DELETE FROM busy_workers WHERE job_id IN (SELECT id FROM jobs WHERE project_name LIKE '$origin%');" + sql+="DELETE FROM group_projects WHERE project_name LIKE '$origin%';" + sql+="DELETE FROM groups WHERE project_name LIKE '$origin%';" + sql+="DELETE FROM jobs WHERE project_name LIKE '$origin%';" + done + + psql builder -q -c "$sql" +} +export -f clean_test_artifacts + +start_api() { + sudo hab svc load "${BLDR_ORIGIN}/builder-api" --bind memcached:builder-memcached.default --channel "${BLDR_CHANNEL}" --force +} +export -f start_api + +start_api_proxy() { + sudo hab svc load "${BLDR_ORIGIN}/builder-api-proxy" --bind http:builder-api.default --channel "${BLDR_CHANNEL}" --force +} +export -f start_api_proxy + +start_datastore() { + sudo hab svc load "${BLDR_ORIGIN}/builder-datastore" --channel "${BLDR_CHANNEL}" --force +} +export -f start_datastore + +start_minio() { + sudo hab svc load "${BLDR_ORIGIN}/builder-minio" --channel "${BLDR_CHANNEL}" --force +} +export -f start_minio + +start_memcached() { + sudo hab svc load "${BLDR_ORIGIN}/builder-memcached" --channel "${BLDR_CHANNEL}" --force +} +export -f start_memcached + +start_jobsrv() { + sudo hab svc load "${BLDR_ORIGIN}/builder-jobsrv" --channel stable --force +} +export -f start_jobsrv + +start_worker() { + sudo hab svc load "${BLDR_ORIGIN}/builder-worker" --bind=jobsrv:builder-jobsrv.default --bind=depot:builder-api-proxy.default --channel stable --force +} +export -f start_worker + +apply_db_password() { + PW=$(cat /hab/svc/builder-datastore/config/pwfile) + echo "datastore.password='$PW'" | sudo hab config apply builder-api.default "$(date +%s)" + echo "datastore.password='$PW'" | sudo hab config apply builder-jobsrv.default "$(date +%s)" +} +export -f apply_db_password + +generate_bldr_keys() { + mapfile -t keys < <(find /hab/cache/keys -name "bldr-*.pub") + + if [ "${#keys[@]}" -gt 0 ]; then + KEY_NAME=$(echo "${keys[0]}" | grep -Po "bldr-\\d+") + echo "Re-using existing builder key: $KEY_NAME" + else + KEY_NAME=$(hab user key generate bldr | grep -Po "bldr-\\d+") + echo "Generated new builder key: $KEY_NAME" + fi + + hab file upload "builder-api.default" "$(date +%s)" "/hab/cache/keys/${KEY_NAME}.pub" + hab file upload "builder-api.default" "$(date +%s)" "/hab/cache/keys/${KEY_NAME}.box.key" +} +export -f generate_bldr_keys + +upload_ssl_certificate() { + if [ "${APP_SSL_ENABLED}" = "true" ]; then + echo "SSL enabled - uploading certificate files" + if ! [ -f "../ssl-certificate.crt" ] || ! [ -f "../ssl-certificate.key" ]; then + pwd + echo "ERROR: Certificate file(s) not found!" + exit 1 + fi + hab file upload "builder-api-proxy.default" "$(date +%s)" "../ssl-certificate.crt" + hab file upload "builder-api-proxy.default" "$(date +%s)" "../ssl-certificate.key" + fi +} +export -f upload_ssl_certificate + +start_init() { + # First shut down if already running + echo "Stopping Habitat Supervisor" + sudo hab sup term + sleep 2 + + echo "Removing builder spec files" + sudo rm -rf /hab/sup/default/specs/builder-* + + echo "Starting Habitat Supervisor in test mode" + create_users + HAB_FUNC_TEST=1 hab sup run & + sleep 2 +} +export -f start_init + +start_frontend() { + echo + echo "Starting Builder Frontend Services" + start_memcached + start_api + start_api_proxy +} +export -f start_frontend + +start_builder() { + echo + echo "Starting Builder Services" + if [ "${PG_EXT_ENABLED:-false}" = "false" ]; then + init_datastore + start_datastore + while [ ! -f /hab/svc/builder-datastore/config/pwfile ] + do + sleep 2 + done + local pg_pass + pg_pass=$(cat /hab/svc/builder-datastore/config/pwfile) + cat < pg_pass.toml +[datastore] +password = "$pg_pass" +EOT + hab config apply builder-api.default "$(date +%s)" pg_pass.toml + fi + configure + if [ "${ARTIFACTORY_ENABLED:-false}" = "false" ] && [ "${S3_ENABLED:-false}" = "false" ]; then + start_minio + fi + start_frontend + sleep 2 + generate_bldr_keys + upload_ssl_certificate +} +export -f start_builder + +create_users() { + if command -v useradd > /dev/null; then + sudo useradd --system --no-create-home hab || true + else + sudo adduser --system hab || true + fi + if command -v groupadd > /dev/null; then + sudo groupadd --system hab || true + else + sudo addgroup --system hab || true + fi +} +export -f create_users + +wait_for_migrations() { + echo "Waiting for migrations to finish" + local count=0 + # while ! command with set -e fails on the first loop, so we get this slightly + # more complex implementation + while true; do + # The status endpoint won't become available until migrations are finished + if curl --silent --fail http://localhost:9636/v1/status; then + break + fi + + ((++count)) + if [ "$count" -ge 60 ]; then + echo "--- Migrations failed to complete after one minute ---" + exit 1 + fi + sleep 1 + done +} +export -f wait_for_migrations diff --git a/test/end-to-end/worker-test/src/api.js b/test/end-to-end/worker-test/src/api.js new file mode 100644 index 0000000000..5e7a2a358e --- /dev/null +++ b/test/end-to-end/worker-test/src/api.js @@ -0,0 +1,7 @@ +// JB TODO: all of these files need to be audited to ensure that all +// possibilities are being covered. Right now most of the tests just cover the +// happy path. We should do some basic coverage of the error cases (non-200) as +// well. + +require('./helpers.js'); +require('./worker_test.js'); diff --git a/test/end-to-end/worker-test/src/helpers.js b/test/end-to-end/worker-test/src/helpers.js new file mode 100644 index 0000000000..895fd88fb3 --- /dev/null +++ b/test/end-to-end/worker-test/src/helpers.js @@ -0,0 +1,6 @@ +// Users we can authenticate as +global.boboBearer = 'Bearer bobo'; + +// accounts.name of boboBearer +global.boboAccountName = 'bobo'; +global.neurosisJobGroup = {}; diff --git a/test/end-to-end/worker-test/src/worker_test.js b/test/end-to-end/worker-test/src/worker_test.js new file mode 100644 index 0000000000..6ad41c87fa --- /dev/null +++ b/test/end-to-end/worker-test/src/worker_test.js @@ -0,0 +1,245 @@ +const expect = require("chai").expect; +const supertest = require("supertest"); +const request = supertest("http://localhost:9636/v1"); +const fs = require("fs"); +//require("dotenv").config() +const origin_name = "neurosis"; +const package_name = "testapp"; + +const release1 = '20171205003213'; +const file1 = fs.readFileSync(__dirname + `/../fixtures/${origin_name}-testapp-0.1.3-${release1}-x86_64-linux.hart`); + +const revision = '20171211220037'; +const pubFile = fs.readFileSync(__dirname + `/../fixtures/${origin_name}-${revision}.pub`, 'utf8'); +const secretFile = fs.readFileSync(__dirname + `/../fixtures/${origin_name}-${revision}.sig.key`, 'utf8'); + +// These magic values correspond to the testapp repo in the habitat-sh org +const installationId = 79953; +const repoId = 114932712; +const projectCreatePayload = { + origin: 'neurosis', + plan_path: 'plan.sh', + installation_id: installationId, + repo_id: repoId, + auto_build: true +}; + +describe("Startup/Origin Creation", function () { + it("returns the created origin", function (done) { + request + .post("/depot/origins") + .set("Authorization", global.boboBearer) + .send({ name: `${origin_name}`, default_package_visibility: "public" }) + .expect(201) + .end(function (err, res) { + expect(res.body.name).to.equal(`${origin_name}`); + expect(res.body.default_package_visibility).to.equal("public"); + global.originWorker = res.body; + done(err); + }); + }); +}); + +describe('Authenticate API', function() { + describe('Create sessions', function() { + it('returns bobo', function(done) { + request.get('/authenticate/bobo') + .expect(200) + .end(function(err, res) { + expect(res.body.name).to.equal('bobo'); + global.sessionBobo = res.body; + done(err); + }); + }); + }); +}); + +describe('Settings API', function() { + describe('Create package settings', function() { + it("returns the created package settings", function (done) { + request + .post(`/settings/${origin_name}/testapp`) + .set("Authorization", global.boboBearer) + .expect(201) + .end(function (err, res) { + expect(res.body.origin).to.equal(`${origin_name}`); + expect(res.body.name).to.equal("testapp"); + expect(res.body.visibility).to.equal("public"); + expect(res.body.owner_id).to.equal(global.sessionBobo.id); + done(err); + }); + }); + }); +}); + +describe('Create and promote a package', function () { + it('allows authenticated users to upload packages', function (done) { + request.post(`/depot/pkgs/${origin_name}/testapp/0.1.3/${release1}`) + .set('Authorization', global.boboBearer) + .set('Content-Length', file1.length) + .query({ checksum: '3138777020e7bb621a510b19c2f2630deee9b34ac11f1c2a0524a44eb977e4a8' }) + .send(file1) + .expect(201) + .end(function (err, res) { + expect(res.text).to.equal(`/pkgs/${origin_name}/testapp/0.1.3/${release1}/download`); + done(err); + }); + }); + + it('promotes package to stable channel', function (done) { + request.put(`/depot/channels/${origin_name}/stable/pkgs/testapp/0.1.3/20171205003213/promote`) + .set('Authorization', global.boboBearer) + .expect(200) + .end(function (err, res) { + expect(res.text).to.be.empty; + done(err); + }); + }); +}); + +describe('Keys API', function () { + it('uploads the public key', function (done) { + request.post(`/depot/origins/${origin_name}/keys/${revision}`) + .set('Authorization', global.boboBearer) + .send(pubFile) + .expect(201) + .end(function (err, res) { + expect(res.text).to.equal(`/origins/${origin_name}/keys/${revision}`); + expect(res.header['location']).to.equal(`/v1/depot/origins/${origin_name}/keys/${revision}`); + done(err); + }); + }); + + it('uploads the private key', function (done) { + request.post(`/depot/origins/${origin_name}/secret_keys/${revision}`) + .set('Authorization', global.boboBearer) + .send(secretFile) + .expect(201) + .end(function (err, res) { + expect(res.text).to.be.empty; + done(err); + }); + }); + + it('can download the latest key', function (done) { + request.get(`/depot/origins/${origin_name}/keys/latest`) + .expect(200) + .end(function (err, res) { + expect(res.text).to.equal(pubFile); + done(err); + }); + }); + + it('retrieves the secret key for origin', function (done) { + request.get(`/depot/origins/${origin_name}`) + .set('Authorization', global.boboBearer) + .expect(200) + .end(function (err, res) { + expect(res.body.private_key_name).to.equal(`${origin_name}-${revision}`); + done(err); + }); + }); + +}); + +describe('Projects API', function () { + describe('Creating a project', function () { + beforeEach('SLEEP 5s', async function() { + console.log("Pause 5s"); + await new Promise(resolve => setTimeout(resolve, 5000)); + }); + it('succeeds', function (done) { + request.post('/projects') + .type('application/json') + .accept('application/json') + .set('Authorization', global.boboBearer) + .set("Referer", "http://localhost:9636") + .send(projectCreatePayload) + .expect(201) + .end(function (err, res) { + expect(res.body.name).to.equal('neurosis/testapp'); + done(err); + }); + }); + }); +}); + +describe("Jobs API", function () { + describe("Scheduling jobs", function () { + var found = false; + var jobState = ""; + it("Schedules job and returns group", function (done) { + request + .post(`/depot/pkgs/schedule/${origin_name}/testapp`) + .type("application/json") + .accept("application/json") + .set("Authorization", global.boboBearer) + .set("Referer", "http://localhost:9636") + .expect(201) + .end(function (err, res) { + expect(res.body).to.not.be.empty; + expect(res.body.state).to.equal("Queued"); + expect(res.body.project_name).to.equal(`${origin_name}/testapp`); + global.neurosisJobGroup = res.body; + done(err); + }); + }); + }); +}); + +describe("Jobsrv and Worker - Dispatching", function() { + beforeEach('SLEEP 5s', async function() { + console.log("Pause 5s"); + await new Promise(resolve => setTimeout(resolve, 5000)); + }); + it("Waiting for the job group to get dispatched", function (done) { + request + .get(`/depot/pkgs/schedule/${global.neurosisJobGroup.id}`) + .type("application/json") + .accept("application/json") + .expect(200) + .end(function (err, res) { + expect(res.body.state).to.equal("Dispatching"); + global.neurosisJobGroup = res.body; + done(err); + }); + }); +}); + +describe("Projects - Get Jobs", function() { + beforeEach('SLEEP 10s', async function() { + console.log("Pause 10s"); + await new Promise(resolve => setTimeout(resolve, 10000)); + }); + it("Get Jobs for Project", function (done) { + request + .get(`/projects/${origin_name}/${package_name}/jobs`) + .set('Authorization', global.boboBearer) + .type("application/json") + .accept("application/json") + .expect(200) + .end(function (err, res) { + expect(res.body.data[0].id).is.not.empty; + done(err); + }); + }); +}); + +describe("Jobsrv and Worker - Complete", function() { + beforeEach('SLEEP 60s', async function() { + console.log("Pause 60s"); + await new Promise(resolve => setTimeout(resolve, 60000)); + }); + it("Waiting for the job group to get marked Complete", function (done) { + request + .get(`/depot/pkgs/schedule/${global.neurosisJobGroup.id}`) + .type("application/json") + .accept("application/json") + .expect(200) + .end(function (err, res) { + expect(res.body.state).to.equal("Complete"); + global.neurosisJobGroup = res.body; + done(err); + }); + }); +});