Merge branch 'request_form_styles' of github.com:dod-ccpo/atst into request_form_styles
This commit is contained in:
commit
d722dff561
156
.bandit_config
Normal file
156
.bandit_config
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
### This config may optionally select a subset of tests to run or skip by
|
||||||
|
### filling out the 'tests' and 'skips' lists given below. If no tests are
|
||||||
|
### specified for inclusion then it is assumed all tests are desired. The skips
|
||||||
|
### set will remove specific tests from the include set.
|
||||||
|
### Note that the same test ID should not appear in both 'tests' and 'skips',
|
||||||
|
### this would be nonsensical and is detected by Bandit at runtime.
|
||||||
|
|
||||||
|
# (optional) list included test IDs here, eg '[B101, B406]':
|
||||||
|
tests:
|
||||||
|
|
||||||
|
# (optional) list skipped test IDs here, eg '[B101, B406]':
|
||||||
|
skips:
|
||||||
|
|
||||||
|
### (optional) plugin settings - some test plugins require configuration data
|
||||||
|
### that may be given here, per-plugin. All bandit test plugins have a built in
|
||||||
|
### set of sensible defaults and these will be used if no configuration is
|
||||||
|
### provided. It is not necessary to provide settings for every (or any) plugin
|
||||||
|
### if the defaults are acceptable.
|
||||||
|
|
||||||
|
any_other_function_with_shell_equals_true:
|
||||||
|
no_shell: [os.execl, os.execle, os.execlp, os.execlpe, os.execv, os.execve, os.execvp,
|
||||||
|
os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve,
|
||||||
|
os.spawnvp, os.spawnvpe, os.startfile]
|
||||||
|
shell: [os.system, os.popen, os.popen2, os.popen3, os.popen4, popen2.popen2, popen2.popen3,
|
||||||
|
popen2.popen4, popen2.Popen3, popen2.Popen4, commands.getoutput, commands.getstatusoutput]
|
||||||
|
subprocess: [subprocess.Popen, subprocess.call, subprocess.check_call, subprocess.check_output,
|
||||||
|
utils.execute, utils.execute_with_timeout]
|
||||||
|
execute_with_run_as_root_equals_true:
|
||||||
|
function_names: [ceilometer.utils.execute, cinder.utils.execute, neutron.agent.linux.utils.execute,
|
||||||
|
nova.utils.execute, nova.utils.trycmd]
|
||||||
|
hardcoded_tmp_directory:
|
||||||
|
tmp_dirs: [/tmp, /var/tmp, /dev/shm]
|
||||||
|
linux_commands_wildcard_injection:
|
||||||
|
no_shell: [os.execl, os.execle, os.execlp, os.execlpe, os.execv, os.execve, os.execvp,
|
||||||
|
os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve,
|
||||||
|
os.spawnvp, os.spawnvpe, os.startfile]
|
||||||
|
shell: [os.system, os.popen, os.popen2, os.popen3, os.popen4, popen2.popen2, popen2.popen3,
|
||||||
|
popen2.popen4, popen2.Popen3, popen2.Popen4, commands.getoutput, commands.getstatusoutput]
|
||||||
|
subprocess: [subprocess.Popen, subprocess.call, subprocess.check_call, subprocess.check_output,
|
||||||
|
utils.execute, utils.execute_with_timeout]
|
||||||
|
password_config_option_not_marked_secret:
|
||||||
|
function_names: [oslo.config.cfg.StrOpt, oslo_config.cfg.StrOpt]
|
||||||
|
ssl_with_bad_defaults:
|
||||||
|
bad_protocol_versions: [PROTOCOL_SSLv2, SSLv2_METHOD, SSLv23_METHOD, PROTOCOL_SSLv3,
|
||||||
|
PROTOCOL_TLSv1, SSLv3_METHOD, TLSv1_METHOD]
|
||||||
|
ssl_with_bad_version:
|
||||||
|
bad_protocol_versions: [PROTOCOL_SSLv2, SSLv2_METHOD, SSLv23_METHOD, PROTOCOL_SSLv3,
|
||||||
|
PROTOCOL_TLSv1, SSLv3_METHOD, TLSv1_METHOD]
|
||||||
|
start_process_with_a_shell:
|
||||||
|
no_shell: [os.execl, os.execle, os.execlp, os.execlpe, os.execv, os.execve, os.execvp,
|
||||||
|
os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve,
|
||||||
|
os.spawnvp, os.spawnvpe, os.startfile]
|
||||||
|
shell: [os.system, os.popen, os.popen2, os.popen3, os.popen4, popen2.popen2, popen2.popen3,
|
||||||
|
popen2.popen4, popen2.Popen3, popen2.Popen4, commands.getoutput, commands.getstatusoutput]
|
||||||
|
subprocess: [subprocess.Popen, subprocess.call, subprocess.check_call, subprocess.check_output,
|
||||||
|
utils.execute, utils.execute_with_timeout]
|
||||||
|
start_process_with_no_shell:
|
||||||
|
no_shell: [os.execl, os.execle, os.execlp, os.execlpe, os.execv, os.execve, os.execvp,
|
||||||
|
os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve,
|
||||||
|
os.spawnvp, os.spawnvpe, os.startfile]
|
||||||
|
shell: [os.system, os.popen, os.popen2, os.popen3, os.popen4, popen2.popen2, popen2.popen3,
|
||||||
|
popen2.popen4, popen2.Popen3, popen2.Popen4, commands.getoutput, commands.getstatusoutput]
|
||||||
|
subprocess: [subprocess.Popen, subprocess.call, subprocess.check_call, subprocess.check_output,
|
||||||
|
utils.execute, utils.execute_with_timeout]
|
||||||
|
start_process_with_partial_path:
|
||||||
|
no_shell: [os.execl, os.execle, os.execlp, os.execlpe, os.execv, os.execve, os.execvp,
|
||||||
|
os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve,
|
||||||
|
os.spawnvp, os.spawnvpe, os.startfile]
|
||||||
|
shell: [os.system, os.popen, os.popen2, os.popen3, os.popen4, popen2.popen2, popen2.popen3,
|
||||||
|
popen2.popen4, popen2.Popen3, popen2.Popen4, commands.getoutput, commands.getstatusoutput]
|
||||||
|
subprocess: [subprocess.Popen, subprocess.call, subprocess.check_call, subprocess.check_output,
|
||||||
|
utils.execute, utils.execute_with_timeout]
|
||||||
|
subprocess_popen_with_shell_equals_true:
|
||||||
|
no_shell: [os.execl, os.execle, os.execlp, os.execlpe, os.execv, os.execve, os.execvp,
|
||||||
|
os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve,
|
||||||
|
os.spawnvp, os.spawnvpe, os.startfile]
|
||||||
|
shell: [os.system, os.popen, os.popen2, os.popen3, os.popen4, popen2.popen2, popen2.popen3,
|
||||||
|
popen2.popen4, popen2.Popen3, popen2.Popen4, commands.getoutput, commands.getstatusoutput]
|
||||||
|
subprocess: [subprocess.Popen, subprocess.call, subprocess.check_call, subprocess.check_output,
|
||||||
|
utils.execute, utils.execute_with_timeout]
|
||||||
|
subprocess_without_shell_equals_true:
|
||||||
|
no_shell: [os.execl, os.execle, os.execlp, os.execlpe, os.execv, os.execve, os.execvp,
|
||||||
|
os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve,
|
||||||
|
os.spawnvp, os.spawnvpe, os.startfile]
|
||||||
|
shell: [os.system, os.popen, os.popen2, os.popen3, os.popen4, popen2.popen2, popen2.popen3,
|
||||||
|
popen2.popen4, popen2.Popen3, popen2.Popen4, commands.getoutput, commands.getstatusoutput]
|
||||||
|
subprocess: [subprocess.Popen, subprocess.call, subprocess.check_call, subprocess.check_output,
|
||||||
|
utils.execute, utils.execute_with_timeout]
|
||||||
|
try_except_continue: {check_typed_exception: false}
|
||||||
|
try_except_pass: {check_typed_exception: false}
|
||||||
|
|
||||||
|
### Reference of Available tests:
|
||||||
|
# B101 : assert_used
|
||||||
|
# B102 : exec_used
|
||||||
|
# B103 : set_bad_file_permissions
|
||||||
|
# B104 : hardcoded_bind_all_interfaces
|
||||||
|
# B105 : hardcoded_password_string
|
||||||
|
# B106 : hardcoded_password_funcarg
|
||||||
|
# B107 : hardcoded_password_default
|
||||||
|
# B108 : hardcoded_tmp_directory
|
||||||
|
# B109 : password_config_option_not_marked_secret
|
||||||
|
# B110 : try_except_pass
|
||||||
|
# B111 : execute_with_run_as_root_equals_true
|
||||||
|
# B112 : try_except_continue
|
||||||
|
# B201 : flask_debug_true
|
||||||
|
# B301 : pickle
|
||||||
|
# B302 : marshal
|
||||||
|
# B303 : md5
|
||||||
|
# B304 : ciphers
|
||||||
|
# B305 : cipher_modes
|
||||||
|
# B306 : mktemp_q
|
||||||
|
# B307 : eval
|
||||||
|
# B308 : mark_safe
|
||||||
|
# B309 : httpsconnection
|
||||||
|
# B310 : urllib_urlopen
|
||||||
|
# B311 : random
|
||||||
|
# B312 : telnetlib
|
||||||
|
# B313 : xml_bad_cElementTree
|
||||||
|
# B314 : xml_bad_ElementTree
|
||||||
|
# B315 : xml_bad_expatreader
|
||||||
|
# B316 : xml_bad_expatbuilder
|
||||||
|
# B317 : xml_bad_sax
|
||||||
|
# B318 : xml_bad_minidom
|
||||||
|
# B319 : xml_bad_pulldom
|
||||||
|
# B320 : xml_bad_etree
|
||||||
|
# B321 : ftplib
|
||||||
|
# B322 : input
|
||||||
|
# B401 : import_telnetlib
|
||||||
|
# B402 : import_ftplib
|
||||||
|
# B403 : import_pickle
|
||||||
|
# B404 : import_subprocess
|
||||||
|
# B405 : import_xml_etree
|
||||||
|
# B406 : import_xml_sax
|
||||||
|
# B407 : import_xml_expat
|
||||||
|
# B408 : import_xml_minidom
|
||||||
|
# B409 : import_xml_pulldom
|
||||||
|
# B410 : import_lxml
|
||||||
|
# B411 : import_xmlrpclib
|
||||||
|
# B412 : import_httpoxy
|
||||||
|
# B501 : request_with_no_cert_validation
|
||||||
|
# B502 : ssl_with_bad_version
|
||||||
|
# B503 : ssl_with_bad_defaults
|
||||||
|
# B504 : ssl_with_no_version
|
||||||
|
# B505 : weak_cryptographic_key
|
||||||
|
# B506 : yaml_load
|
||||||
|
# B601 : paramiko_calls
|
||||||
|
# B602 : subprocess_popen_with_shell_equals_true
|
||||||
|
# B603 : subprocess_without_shell_equals_true
|
||||||
|
# B604 : any_other_function_with_shell_equals_true
|
||||||
|
# B605 : start_process_with_a_shell
|
||||||
|
# B606 : start_process_with_no_shell
|
||||||
|
# B607 : start_process_with_partial_path
|
||||||
|
# B608 : hardcoded_sql_expressions
|
||||||
|
# B609 : linux_commands_wildcard_injection
|
||||||
|
# B701 : jinja2_autoescape_false
|
||||||
|
# B702 : use_of_mako_templates
|
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[submodule "script/include"]
|
||||||
|
path = script/include
|
||||||
|
url = git@github.com:dod-ccpo/scriptz.git
|
||||||
|
branch = master
|
12
.travis.yml
12
.travis.yml
@ -2,20 +2,28 @@ sudo: required
|
|||||||
language: python
|
language: python
|
||||||
python: "3.6"
|
python: "3.6"
|
||||||
services: docker
|
services: docker
|
||||||
|
git:
|
||||||
|
submodules: false
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- TESTER_IMAGE_NAME=atst-tester
|
- TESTER_IMAGE_NAME=atst-tester
|
||||||
- PROD_IMAGE_NAME=atst-prod
|
- PROD_IMAGE_NAME=atst-prod
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
|
# Use sed to replace the SSH URL with the public URL
|
||||||
|
- sed -i 's/git@github.com:/https:\/\/github.com\//' .gitmodules
|
||||||
|
# Manually initialize submodules
|
||||||
|
- git submodule update --init --recursive
|
||||||
|
|
||||||
|
before_script:
|
||||||
- docker login -u $ATAT_DOCKER_REGISTRY_USERNAME -p $ATAT_DOCKER_REGISTRY_PASSWORD $ATAT_DOCKER_REGISTRY_URL
|
- docker login -u $ATAT_DOCKER_REGISTRY_USERNAME -p $ATAT_DOCKER_REGISTRY_PASSWORD $ATAT_DOCKER_REGISTRY_URL
|
||||||
- docker build --tag "${TESTER_IMAGE_NAME}" . -f docker/tester/Dockerfile
|
- docker build --tag "${TESTER_IMAGE_NAME}" . -f deploy/docker/tester/Dockerfile
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- docker run "${TESTER_IMAGE_NAME}"
|
- docker run "${TESTER_IMAGE_NAME}"
|
||||||
|
|
||||||
before_deploy:
|
before_deploy:
|
||||||
- docker build --tag "${PROD_IMAGE_NAME}" . -f docker/prod/Dockerfile
|
- docker build --tag "${PROD_IMAGE_NAME}" . -f deploy/docker/prod/Dockerfile
|
||||||
- git_sha="$(git rev-parse --short HEAD)"
|
- git_sha="$(git rev-parse --short HEAD)"
|
||||||
- remote_image_name="${ATAT_DOCKER_REGISTRY_URL}/${PROD_IMAGE_NAME}:${git_sha}"
|
- remote_image_name="${ATAT_DOCKER_REGISTRY_URL}/${PROD_IMAGE_NAME}:${git_sha}"
|
||||||
- docker tag "${PROD_IMAGE_NAME}" "${remote_image_name}"
|
- docker tag "${PROD_IMAGE_NAME}" "${remote_image_name}"
|
||||||
|
1
Pipfile
1
Pipfile
@ -19,6 +19,7 @@ ipython = "*"
|
|||||||
ipdb = "*"
|
ipdb = "*"
|
||||||
pylint = "*"
|
pylint = "*"
|
||||||
black = "*"
|
black = "*"
|
||||||
|
pytest-watch = "*"
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3.6"
|
python_version = "3.6"
|
||||||
|
47
Pipfile.lock
generated
47
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "53a2048f5dc853a0ac2d565164c23dcdb2df802e055c1530864bfc8390af3c3e"
|
"sha256": "3bea02ccdb0e3877f2595d7fb405408114ec8947e0484d5b4aaf14a4c8ff78b2"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -119,6 +119,13 @@
|
|||||||
"markers": "sys_platform == 'darwin'",
|
"markers": "sys_platform == 'darwin'",
|
||||||
"version": "==0.1.0"
|
"version": "==0.1.0"
|
||||||
},
|
},
|
||||||
|
"argh": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:a9b3aaa1904eeb78e32394cd46c6f37ac0fb4af6dc488daa58971bdc7d7fcaf3",
|
||||||
|
"sha256:e9535b8c84dc9571a48999094fda7f33e63c3f1b74f3e5f3ac0105a58405bb65"
|
||||||
|
],
|
||||||
|
"version": "==0.26.2"
|
||||||
|
},
|
||||||
"astroid": {
|
"astroid": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a8d8c7fe34e34e868426b9bafce852c355a3951eef60bc831b2ed541558f8d37",
|
"sha256:a8d8c7fe34e34e868426b9bafce852c355a3951eef60bc831b2ed541558f8d37",
|
||||||
@ -170,6 +177,13 @@
|
|||||||
],
|
],
|
||||||
"version": "==6.7"
|
"version": "==6.7"
|
||||||
},
|
},
|
||||||
|
"colorama": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda",
|
||||||
|
"sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"
|
||||||
|
],
|
||||||
|
"version": "==0.3.9"
|
||||||
|
},
|
||||||
"decorator": {
|
"decorator": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2c51dff8ef3c447388fe5e4453d24a2bf128d3a4c32af3fabef1f01c6851ab82",
|
"sha256:2c51dff8ef3c447388fe5e4453d24a2bf128d3a4c32af3fabef1f01c6851ab82",
|
||||||
@ -177,6 +191,12 @@
|
|||||||
],
|
],
|
||||||
"version": "==4.3.0"
|
"version": "==4.3.0"
|
||||||
},
|
},
|
||||||
|
"docopt": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"
|
||||||
|
],
|
||||||
|
"version": "==0.6.2"
|
||||||
|
},
|
||||||
"gitdb2": {
|
"gitdb2": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:b60e29d4533e5e25bb50b7678bbc187c8f6bcff1344b4f293b2ba55c85795f09",
|
"sha256:b60e29d4533e5e25bb50b7678bbc187c8f6bcff1344b4f293b2ba55c85795f09",
|
||||||
@ -280,10 +300,16 @@
|
|||||||
},
|
},
|
||||||
"parso": {
|
"parso": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:8105449d86d858e53ce3e0044ede9dd3a395b1c9716c696af8aa3787158ab806",
|
"sha256:35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2",
|
||||||
"sha256:d250235e52e8f9fc5a80cc2a5f804c9fefd886b2e67a2b1099cf085f403f8e33"
|
"sha256:895c63e93b94ac1e1690f5fdd40b65f07c8171e3e53cbd7793b5b96c0e0a7f24"
|
||||||
],
|
],
|
||||||
"version": "==0.3.0"
|
"version": "==0.3.1"
|
||||||
|
},
|
||||||
|
"pathtools": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0"
|
||||||
|
],
|
||||||
|
"version": "==0.1.2"
|
||||||
},
|
},
|
||||||
"pbr": {
|
"pbr": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -370,6 +396,13 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.5.0"
|
"version": "==0.5.0"
|
||||||
},
|
},
|
||||||
|
"pytest-watch": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:06136f03d5b361718b8d0d234042f7b2f203910d8568f63df2f866b547b3d4b9"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==4.2.0"
|
||||||
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb",
|
"sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb",
|
||||||
@ -460,6 +493,12 @@
|
|||||||
"markers": "python_version < '3.7' and implementation_name == 'cpython'",
|
"markers": "python_version < '3.7' and implementation_name == 'cpython'",
|
||||||
"version": "==1.1.0"
|
"version": "==1.1.0"
|
||||||
},
|
},
|
||||||
|
"watchdog": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7e65882adb7746039b6f3876ee174952f8eaaa34491ba34333ddf1fe35de4162"
|
||||||
|
],
|
||||||
|
"version": "==0.8.3"
|
||||||
|
},
|
||||||
"wcwidth": {
|
"wcwidth": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
|
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
|
||||||
|
55
README.md
55
README.md
@ -3,35 +3,74 @@
|
|||||||
|
|
||||||
[](https://travis-ci.org/dod-ccpo/atst)
|
[](https://travis-ci.org/dod-ccpo/atst)
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This is the main user-facing web application for the ATAT stack. All end-user
|
||||||
|
requests are handled by ATST, with it making backend calls to various
|
||||||
|
microservices when appropriate.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
See the [scriptz](https://github.com/dod-ccpo/scriptz) repository for the shared
|
||||||
|
requirements and guidelines for all ATAT applications.
|
||||||
|
Additionally, ATST requires a redis instance for session management. Have redis
|
||||||
|
installed and running. By default, ATST will try to connect to a redis instance
|
||||||
|
running on localhost on its default port, 6379.
|
||||||
|
|
||||||
|
### Cloning
|
||||||
|
This project contains git submodules. Here is an example clone command that will
|
||||||
|
automatically initialize and update those modules:
|
||||||
|
|
||||||
|
git clone --recurse-submodules git@github.com:dod-ccpo/atst.git
|
||||||
|
|
||||||
|
If you have an existing clone that does not yet contain the submodules, you can
|
||||||
|
set them up with the following command:
|
||||||
|
|
||||||
|
git submodule update --init --recursive
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
This application uses Pipenv to manage Python dependencies and a virtual
|
||||||
|
environment. Instead of the classic `requirements.txt` file, pipenv uses a
|
||||||
|
Pipfile and Pipfile.lock, making it more similar to other modern package managers
|
||||||
|
like yarn or mix.
|
||||||
|
|
||||||
|
To perform the installation, run the setup script:
|
||||||
|
|
||||||
script/setup
|
script/setup
|
||||||
|
|
||||||
The setup script installs pipenv, which is what this application uses to manage its dependences and virtualenv. Instead of the classic `requirements.txt` file, pipenv uses a Pipfile and Pipfile.lock, making it more similar to other modern package managers like yarn or mix.
|
The setup script creates the virtual environment, and then calls script/bootstrap
|
||||||
|
to install all of the Python and Node dependencies.
|
||||||
|
|
||||||
To enter the virtualenv manually (a la `source .venv/bin/activate`):
|
To enter the virtualenv manually (a la `source .venv/bin/activate`):
|
||||||
|
|
||||||
pipenv shell
|
pipenv shell
|
||||||
|
|
||||||
If you want to automatically load the virtual environment whenever you enter the project directory, take a look at [direnv](https://direnv.net/). An `.envrc` file is included in this repository. direnv will activate and deactivate virtualenvs for you when you enter and leave the directory.
|
If you want to automatically load the virtual environment whenever you enter the
|
||||||
|
project directory, take a look at [direnv](https://direnv.net/). An `.envrc`
|
||||||
|
file is included in this repository. direnv will activate and deactivate
|
||||||
|
virtualenvs for you when you enter and leave the directory.
|
||||||
|
|
||||||
Additionally, ATST requires a redis instance for session management. Have redis installed and running. By default, ATST will try to connect to a redis instance running on localhost on its default port, 6379.
|
|
||||||
|
|
||||||
## Running (development)
|
## Running (development)
|
||||||
|
|
||||||
To start the app and watch for changes:
|
To start the app locally in the foreground and watch for changes:
|
||||||
|
|
||||||
DEBUG=1 script/server
|
script/dev_server
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
To run unit tests:
|
To run lint, static analysis, and unit tests:
|
||||||
|
|
||||||
script/test
|
script/test
|
||||||
|
|
||||||
or
|
To run only the unit tests:
|
||||||
|
|
||||||
python -m pytest
|
pipenv run python -m pytest
|
||||||
|
|
||||||
|
To re-run tests each time a file is changed:
|
||||||
|
|
||||||
|
pipenv run ptw
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
33
atst/app.py
33
atst/app.py
@ -4,12 +4,13 @@ import tornado.web
|
|||||||
from tornado.web import url
|
from tornado.web import url
|
||||||
from redis import StrictRedis
|
from redis import StrictRedis
|
||||||
|
|
||||||
from atst.handlers.main import MainHandler
|
from atst.handlers.main import Main
|
||||||
from atst.handlers.home import Home
|
from atst.handlers.root import Root
|
||||||
from atst.handlers.login import Login
|
from atst.handlers.login_redirect import LoginRedirect
|
||||||
from atst.handlers.workspace import Workspace
|
from atst.handlers.workspace import Workspace
|
||||||
from atst.handlers.request import Request
|
from atst.handlers.request import Request
|
||||||
from atst.handlers.request_new import RequestNew
|
from atst.handlers.request_new import RequestNew
|
||||||
|
from atst.handlers.request_submit import RequestsSubmit
|
||||||
from atst.handlers.dev import Dev
|
from atst.handlers.dev import Dev
|
||||||
from atst.home import home
|
from atst.home import home
|
||||||
from atst.api_client import ApiClient
|
from atst.api_client import ApiClient
|
||||||
@ -21,23 +22,23 @@ ENV = os.getenv("TORNADO_ENV", "dev")
|
|||||||
def make_app(config, deps, **kwargs):
|
def make_app(config, deps, **kwargs):
|
||||||
|
|
||||||
routes = [
|
routes = [
|
||||||
url(r"/", Home, {"page": "login"}, name="main"),
|
url(r"/", Root, {"page": "root"}, name="root"),
|
||||||
url(
|
url(
|
||||||
r"/login",
|
r"/login-redirect",
|
||||||
Login,
|
LoginRedirect,
|
||||||
{"sessions": deps["sessions"], "authnid_client": deps["authnid_client"]},
|
{"sessions": deps["sessions"], "authnid_client": deps["authnid_client"]},
|
||||||
name="login",
|
name="login_redirect",
|
||||||
),
|
),
|
||||||
url(r"/home", MainHandler, {"page": "home"}, name="home"),
|
url(r"/home", Main, {"page": "home"}, name="home"),
|
||||||
url(
|
url(
|
||||||
r"/styleguide",
|
r"/styleguide",
|
||||||
MainHandler,
|
Main,
|
||||||
{"page": "styleguide"},
|
{"page": "styleguide"},
|
||||||
name="styleguide",
|
name="styleguide",
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
r"/workspaces/blank",
|
r"/workspaces/blank",
|
||||||
MainHandler,
|
Main,
|
||||||
{"page": "workspaces_blank"},
|
{"page": "workspaces_blank"},
|
||||||
name="workspaces_blank",
|
name="workspaces_blank",
|
||||||
),
|
),
|
||||||
@ -71,9 +72,15 @@ def make_app(config, deps, **kwargs):
|
|||||||
{"page": "requests_new", "requests_client": deps["requests_client"]},
|
{"page": "requests_new", "requests_client": deps["requests_client"]},
|
||||||
name="request_form_update",
|
name="request_form_update",
|
||||||
),
|
),
|
||||||
url(r"/users", MainHandler, {"page": "users"}, name="users"),
|
url(
|
||||||
url(r"/reports", MainHandler, {"page": "reports"}, name="reports"),
|
r"/requests/submit/(\S+)",
|
||||||
url(r"/calculator", MainHandler, {"page": "calculator"}, name="calculator"),
|
RequestsSubmit,
|
||||||
|
{"requests_client": deps["requests_client"]},
|
||||||
|
name="requests_submit",
|
||||||
|
),
|
||||||
|
url(r"/users", Main, {"page": "users"}, name="users"),
|
||||||
|
url(r"/reports", Main, {"page": "reports"}, name="reports"),
|
||||||
|
url(r"/calculator", Main, {"page": "calculator"}, name="calculator"),
|
||||||
]
|
]
|
||||||
|
|
||||||
if not ENV == "production":
|
if not ENV == "production":
|
||||||
|
@ -15,10 +15,14 @@ class FinancialForm(Form):
|
|||||||
"Unique Item Identifier (UII)s related to your application(s) if you already have them."
|
"Unique Item Identifier (UII)s related to your application(s) if you already have them."
|
||||||
)
|
)
|
||||||
|
|
||||||
pe_id = NewlineListField(
|
pe_id = StringField(
|
||||||
"Program Element (PE) Numbers related to your request"
|
"Program Element (PE) Number related to your request"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
treasury_code = StringField("Please provide your Program Treasury Code")
|
||||||
|
|
||||||
|
ba_code = StringField("Please provide your Program BA Code")
|
||||||
|
|
||||||
fname_co = StringField("Contracting Officer First Name", validators=[Required()])
|
fname_co = StringField("Contracting Officer First Name", validators=[Required()])
|
||||||
lname_co = StringField("Contracting Officer Last Name", validators=[Required()])
|
lname_co = StringField("Contracting Officer Last Name", validators=[Required()])
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ from wtforms.fields.html5 import IntegerField
|
|||||||
from wtforms.fields import RadioField, StringField, TextAreaField
|
from wtforms.fields import RadioField, StringField, TextAreaField
|
||||||
from wtforms.validators import NumberRange, InputRequired
|
from wtforms.validators import NumberRange, InputRequired
|
||||||
from wtforms_tornado import Form
|
from wtforms_tornado import Form
|
||||||
from .fields import DateField, NewlineListField
|
from .fields import DateField
|
||||||
from .validators import DateRange
|
from .validators import DateRange
|
||||||
import pendulum
|
import pendulum
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ class BaseHandler(tornado.web.RequestHandler):
|
|||||||
try:
|
try:
|
||||||
session = self.application.sessions.get_session(cookie)
|
session = self.application.sessions.get_session(cookie)
|
||||||
except SessionNotFoundError:
|
except SessionNotFoundError:
|
||||||
|
self.clear_cookie("atat")
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
@ -2,7 +2,7 @@ import tornado
|
|||||||
from atst.handler import BaseHandler
|
from atst.handler import BaseHandler
|
||||||
|
|
||||||
|
|
||||||
class Login(BaseHandler):
|
class LoginRedirect(BaseHandler):
|
||||||
def initialize(self, authnid_client, sessions):
|
def initialize(self, authnid_client, sessions):
|
||||||
self.authnid_client = authnid_client
|
self.authnid_client = authnid_client
|
||||||
self.sessions = sessions
|
self.sessions = sessions
|
@ -2,7 +2,7 @@ import tornado
|
|||||||
from atst.handler import BaseHandler
|
from atst.handler import BaseHandler
|
||||||
|
|
||||||
|
|
||||||
class MainHandler(BaseHandler):
|
class Main(BaseHandler):
|
||||||
def initialize(self, page):
|
def initialize(self, page):
|
||||||
self.page = page
|
self.page = page
|
||||||
|
|
||||||
|
@ -1,43 +1,15 @@
|
|||||||
import tornado
|
import tornado
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from atst.handler import BaseHandler
|
from atst.handler import BaseHandler
|
||||||
from atst.forms.request import RequestForm
|
from atst.forms.request import RequestForm
|
||||||
from atst.forms.org import OrgForm
|
from atst.forms.org import OrgForm
|
||||||
from atst.forms.poc import POCForm
|
from atst.forms.poc import POCForm
|
||||||
from atst.forms.review import ReviewForm
|
from atst.forms.review import ReviewForm
|
||||||
from atst.forms.financial import FinancialForm
|
from atst.forms.financial import FinancialForm
|
||||||
import tornado.httputil
|
|
||||||
|
|
||||||
|
|
||||||
class RequestNew(BaseHandler):
|
class RequestNew(BaseHandler):
|
||||||
screens = [
|
|
||||||
{
|
|
||||||
"title": "Details of Use",
|
|
||||||
"section": "details_of_use",
|
|
||||||
"form": RequestForm,
|
|
||||||
"subitems": [
|
|
||||||
{"title": "Overall request details", "id": "overall-request-details"},
|
|
||||||
{"title": "Cloud Resources", "id": "cloud-resources"},
|
|
||||||
{"title": "Support Staff", "id": "support-staff"},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Information About You",
|
|
||||||
"section": "information_about_you",
|
|
||||||
"form": OrgForm,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Primary Point of Contact",
|
|
||||||
"section": "primary_poc",
|
|
||||||
"form": POCForm,
|
|
||||||
},
|
|
||||||
{"title": "Review & Submit", "section": "review_submit", "form": ReviewForm},
|
|
||||||
{
|
|
||||||
"title": "Financial Verification",
|
|
||||||
"section": "financial_verification",
|
|
||||||
"form": FinancialForm,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
def initialize(self, page, requests_client):
|
def initialize(self, page, requests_client):
|
||||||
self.page = page
|
self.page = page
|
||||||
self.requests_client = requests_client
|
self.requests_client = requests_client
|
||||||
@ -47,58 +19,62 @@ class RequestNew(BaseHandler):
|
|||||||
def post(self, screen=1, request_id=None):
|
def post(self, screen=1, request_id=None):
|
||||||
self.check_xsrf_cookie()
|
self.check_xsrf_cookie()
|
||||||
screen = int(screen)
|
screen = int(screen)
|
||||||
form_metadata = self.screens[screen - 1]
|
post_data = self.request.arguments
|
||||||
form_section = form_metadata["section"]
|
jedi_flow = JEDIRequestFlow(
|
||||||
form = form_metadata["form"](self.request.arguments)
|
self.requests_client, screen, post_data=post_data, request_id=request_id
|
||||||
|
|
||||||
if form.validate():
|
|
||||||
response = yield self.create_or_update_request(
|
|
||||||
form_section, form.data, request_id
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if jedi_flow.validate():
|
||||||
|
response = yield jedi_flow.create_or_update_request(self.get_current_user())
|
||||||
if response.ok:
|
if response.ok:
|
||||||
where = self.application.default_router.reverse_url(
|
where = self.application.default_router.reverse_url(
|
||||||
"request_form_update",
|
"request_form_update", str(screen + 1), jedi_flow.request_id
|
||||||
str(screen + 1),
|
|
||||||
request_id or response.json["id"],
|
|
||||||
)
|
)
|
||||||
self.redirect(where)
|
self.redirect(where)
|
||||||
else:
|
else:
|
||||||
self.set_status(response.code)
|
self.set_status(response.code)
|
||||||
else:
|
else:
|
||||||
self.show_form(screen, form)
|
self.render(
|
||||||
|
"requests/screen-%d.html.to" % int(screen),
|
||||||
|
f=jedi_flow.form,
|
||||||
|
data=post_data,
|
||||||
|
page=self.page,
|
||||||
|
screens=jedi_flow.screens,
|
||||||
|
current=screen,
|
||||||
|
next_screen=jedi_flow.next_screen,
|
||||||
|
request_id=jedi_flow.request_id,
|
||||||
|
)
|
||||||
|
|
||||||
@tornado.web.authenticated
|
@tornado.web.authenticated
|
||||||
@tornado.gen.coroutine
|
@tornado.gen.coroutine
|
||||||
def get(self, screen=1, request_id=None):
|
def get(self, screen=1, request_id=None):
|
||||||
form = None
|
screen = int(screen)
|
||||||
form_data = None
|
request = None
|
||||||
is_review_section = screen == 4
|
|
||||||
|
|
||||||
if request_id:
|
if request_id:
|
||||||
request = yield self.get_request(request_id)
|
response = yield self.requests_client.get(
|
||||||
if request.ok:
|
"/users/{}/requests/{}".format(
|
||||||
if is_review_section:
|
self.get_current_user()["id"], request_id
|
||||||
form_data = request.json["body"]
|
),
|
||||||
else:
|
raise_error=False,
|
||||||
form_metadata = self.screens[int(screen) - 1]
|
)
|
||||||
section = form_metadata["section"]
|
if response.ok:
|
||||||
form_data = request.json["body"].get(section, request.json["body"])
|
request = response.json
|
||||||
form = form_metadata["form"](data=form_data)
|
|
||||||
|
|
||||||
self.show_form(screen=screen, form=form, request_id=request_id, data=form_data)
|
jedi_flow = JEDIRequestFlow(
|
||||||
|
self.requests_client, screen, request, request_id=request_id
|
||||||
|
)
|
||||||
|
|
||||||
def show_form(self, screen=1, form=None, request_id=None, data=None):
|
|
||||||
if not form:
|
|
||||||
form = self.screens[int(screen) - 1]["form"](self.request.arguments)
|
|
||||||
self.render(
|
self.render(
|
||||||
"requests/screen-%d.html.to" % int(screen),
|
"requests/screen-%d.html.to" % int(screen),
|
||||||
f=form,
|
f=jedi_flow.form,
|
||||||
data=data,
|
data=jedi_flow.current_step_data,
|
||||||
page=self.page,
|
page=self.page,
|
||||||
screens=self.screens,
|
screens=jedi_flow.screens,
|
||||||
current=int(screen),
|
current=screen,
|
||||||
next_screen=int(screen) + 1,
|
next_screen=screen + 1,
|
||||||
request_id=request_id,
|
request_id=request_id,
|
||||||
|
can_submit=jedi_flow.can_submit
|
||||||
)
|
)
|
||||||
|
|
||||||
@tornado.gen.coroutine
|
@tornado.gen.coroutine
|
||||||
@ -109,16 +85,128 @@ class RequestNew(BaseHandler):
|
|||||||
)
|
)
|
||||||
return request
|
return request
|
||||||
|
|
||||||
|
|
||||||
|
class JEDIRequestFlow(object):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
requests_client,
|
||||||
|
current_step,
|
||||||
|
request=None,
|
||||||
|
post_data=None,
|
||||||
|
request_id=None,
|
||||||
|
):
|
||||||
|
self.requests_client = requests_client
|
||||||
|
|
||||||
|
self.current_step = current_step
|
||||||
|
self.request = request
|
||||||
|
|
||||||
|
self.post_data = post_data
|
||||||
|
self.is_post = self.post_data is not None
|
||||||
|
|
||||||
|
self.request_id = request_id
|
||||||
|
self.form = self._form()
|
||||||
|
|
||||||
|
def _form(self):
|
||||||
|
if self.is_post:
|
||||||
|
return self.form_class()(self.post_data)
|
||||||
|
elif self.request:
|
||||||
|
return self.form_class()(data=self.current_step_data)
|
||||||
|
else:
|
||||||
|
return self.form_class()()
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
return self.form.validate()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_screen(self):
|
||||||
|
return self.screens[self.current_step - 1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def form_section(self):
|
||||||
|
return self.current_screen["section"]
|
||||||
|
|
||||||
|
def form_class(self):
|
||||||
|
return self.current_screen["form"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_step_data(self):
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
if self.is_post:
|
||||||
|
data = self.post_data
|
||||||
|
|
||||||
|
if self.request:
|
||||||
|
if self.form_section == "review_submit":
|
||||||
|
data = self.request["body"]
|
||||||
|
else:
|
||||||
|
data = self.request["body"].get(self.form_section, {})
|
||||||
|
|
||||||
|
return defaultdict(lambda: defaultdict(lambda: 'Input required'), data)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_submit(self):
|
||||||
|
return self.request and self.request["status"] != "incomplete"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def next_screen(self):
|
||||||
|
return self.current_step + 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def screens(self):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"title": "Details of Use",
|
||||||
|
"section": "details_of_use",
|
||||||
|
"form": RequestForm,
|
||||||
|
"subitems": [
|
||||||
|
{
|
||||||
|
"title": "Overall request details",
|
||||||
|
"id": "overall-request-details",
|
||||||
|
},
|
||||||
|
{"title": "Cloud Resources", "id": "cloud-resources"},
|
||||||
|
{"title": "Support Staff", "id": "support-staff"},
|
||||||
|
],
|
||||||
|
"show": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Information About You",
|
||||||
|
"section": "information_about_you",
|
||||||
|
"form": OrgForm,
|
||||||
|
"show": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Primary Point of Contact",
|
||||||
|
"section": "primary_poc",
|
||||||
|
"form": POCForm,
|
||||||
|
"show": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Review & Submit",
|
||||||
|
"section": "review_submit",
|
||||||
|
"form": ReviewForm,
|
||||||
|
"show":True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Financial Verification",
|
||||||
|
"section": "financial_verification",
|
||||||
|
"form": FinancialForm,
|
||||||
|
"show": self.request and self.request["status"] == "approved",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
@tornado.gen.coroutine
|
@tornado.gen.coroutine
|
||||||
def create_or_update_request(self, form_section, form_data, request_id=None):
|
def create_or_update_request(self, user):
|
||||||
request_data = {
|
request_data = {
|
||||||
"creator_id": self.get_current_user()["id"],
|
"creator_id": user["id"],
|
||||||
"request": {form_section: form_data},
|
"request": {self.form_section: self.form.data},
|
||||||
}
|
}
|
||||||
if request_id:
|
if self.request_id:
|
||||||
response = yield self.requests_client.patch(
|
response = yield self.requests_client.patch(
|
||||||
"/requests/{}".format(request_id), json=request_data
|
"/requests/{}".format(self.request_id), json=request_data
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
response = yield self.requests_client.post("/requests", json=request_data)
|
response = yield self.requests_client.post("/requests", json=request_data)
|
||||||
|
self.request = response.json
|
||||||
|
self.request_id = self.request["id"]
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
17
atst/handlers/request_submit.py
Normal file
17
atst/handlers/request_submit.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import tornado
|
||||||
|
|
||||||
|
from atst.handler import BaseHandler
|
||||||
|
|
||||||
|
|
||||||
|
class RequestsSubmit(BaseHandler):
|
||||||
|
def initialize(self, requests_client):
|
||||||
|
self.requests_client = requests_client
|
||||||
|
|
||||||
|
@tornado.web.authenticated
|
||||||
|
@tornado.gen.coroutine
|
||||||
|
def post(self, request_id):
|
||||||
|
yield self.requests_client.post(
|
||||||
|
"/requests/{}/submit".format(request_id),
|
||||||
|
allow_nonstandard_methods=True
|
||||||
|
)
|
||||||
|
self.redirect("/requests")
|
@ -1,7 +1,7 @@
|
|||||||
from atst.handler import BaseHandler
|
from atst.handler import BaseHandler
|
||||||
|
|
||||||
|
|
||||||
class Home(BaseHandler):
|
class Root(BaseHandler):
|
||||||
def initialize(self, page):
|
def initialize(self, page):
|
||||||
self.page = page
|
self.page = page
|
||||||
|
|
@ -23,12 +23,12 @@ RUN set -x ; \
|
|||||||
# Set working dir
|
# Set working dir
|
||||||
WORKDIR ${APP_DIR}
|
WORKDIR ${APP_DIR}
|
||||||
|
|
||||||
# Copy over alpine setup script
|
# Copy over setup scripts
|
||||||
COPY script/alpine_setup ./script/
|
COPY script/ ./script/
|
||||||
|
|
||||||
# Add required system packages and app user
|
# Add required system packages and app user
|
||||||
RUN set -x ; \
|
RUN set -x ; \
|
||||||
script/alpine_setup "${APP_USER}" "${APP_GROUP}"
|
script/alpine_setup
|
||||||
|
|
||||||
### Items that will change almost every build
|
### Items that will change almost every build
|
||||||
#############################################
|
#############################################
|
@ -3,20 +3,11 @@
|
|||||||
# script/alpine_setup: Adds all the system packages, directors, users, etc.
|
# script/alpine_setup: Adds all the system packages, directors, users, etc.
|
||||||
# required to run the application on Alpine
|
# required to run the application on Alpine
|
||||||
|
|
||||||
# If a command fails, exit the script
|
source "$(dirname "${0}")"/../script/include/global_header.inc.sh
|
||||||
set -e
|
|
||||||
|
|
||||||
# Ensure we are in the app root directory (not the /script directory)
|
# Set app specific items
|
||||||
cd "$(dirname "${0}")/.."
|
APP_USER="atst"
|
||||||
|
APP_UID="8010"
|
||||||
|
|
||||||
APP_USER=${1}
|
# Run the shared alpine setup script
|
||||||
APP_GROUP=${2}
|
source ./script/include/run_alpine_setup
|
||||||
|
|
||||||
apk update
|
|
||||||
apk upgrade
|
|
||||||
|
|
||||||
apk add bash
|
|
||||||
apk add dumb-init
|
|
||||||
|
|
||||||
addgroup -g 8000 -S "${APP_GROUP}"
|
|
||||||
adduser -u 8010 -D -S -G "${APP_GROUP}" "${APP_USER}"
|
|
||||||
|
@ -3,33 +3,18 @@
|
|||||||
# script/bootstrap: Resolve all dependencies that the application requires to
|
# script/bootstrap: Resolve all dependencies that the application requires to
|
||||||
# run.
|
# run.
|
||||||
|
|
||||||
# If a command fails, exit the script
|
source "$(dirname "${0}")"/../script/include/global_header.inc.sh
|
||||||
set -e
|
|
||||||
|
|
||||||
# Ensure we are in the app root directory (not the /script directory)
|
# Set sass compiling command for this app
|
||||||
cd "$(dirname "${0}")/.."
|
COMPILE_SASS_CMD="webassets -m atst.assets build"
|
||||||
|
|
||||||
if [ -z "${CIBUILD+xxxx}" ]; then
|
# Enable python and node package installation
|
||||||
CMD_PREFIX='pipenv run '
|
INSTALL_PYTHON_PACKAGES="true"
|
||||||
fi
|
INSTALL_NODE_PACKAGES="true"
|
||||||
PIP_CMD="${CMD_PREFIX}pip"
|
|
||||||
WEBASSETS_CMD="${CMD_PREFIX}webassets"
|
|
||||||
|
|
||||||
PIPENV_INSTALL_FLAGS='--dev'
|
# Run the shared bootstrap script
|
||||||
if [ -n "${CIBUILD}" ]; then
|
source ./script/include/run_bootstrap
|
||||||
PIPENV_INSTALL_FLAGS+=' --system --ignore-pipfile'
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install Python dependencies
|
# Link USWDS fonts into the /static directory
|
||||||
${PIP_CMD} install --upgrade pip
|
|
||||||
pipenv install ${PIPENV_INSTALL_FLAGS}
|
|
||||||
|
|
||||||
# Install uswds node module and dependencies
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# Relink uswds fonts into the /static directory
|
|
||||||
rm -f ./static/fonts
|
rm -f ./static/fonts
|
||||||
ln -s ../node_modules/uswds/src/fonts ./static/fonts
|
ln -s ../node/modules/uswds/src/fonts ./static/fonts
|
||||||
|
|
||||||
# Precompile assets for deployment
|
|
||||||
${WEBASSETS_CMD} -m atst.assets build
|
|
||||||
|
@ -2,15 +2,7 @@
|
|||||||
|
|
||||||
# script/cibuild: Run CI related checks and tests
|
# script/cibuild: Run CI related checks and tests
|
||||||
|
|
||||||
# If a command fails, exit the script
|
source "$(dirname "${0}")"/../script/include/global_header.inc.sh
|
||||||
set -e
|
|
||||||
|
|
||||||
# Ensure we are in the app root directory (not the /script directory)
|
|
||||||
cd "$(dirname "${0}")/.."
|
|
||||||
|
|
||||||
# Run lint/style checks and unit tests
|
# Run lint/style checks and unit tests
|
||||||
script/test
|
source ./script/test
|
||||||
|
|
||||||
# Run static code analysis security checks
|
|
||||||
# (excluding the tests and node_modules subdirs)
|
|
||||||
bandit -r . -x node_modules,tests
|
|
||||||
|
32
script/dev_server
Executable file
32
script/dev_server
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# script/dev_server: Launch a local dev version of the server in the background
|
||||||
|
|
||||||
|
#
|
||||||
|
# WIP
|
||||||
|
#
|
||||||
|
|
||||||
|
source "$(dirname "${0}")"/../script/include/global_header.inc.sh
|
||||||
|
|
||||||
|
# Create a function to run after a trap is triggered
|
||||||
|
reap() {
|
||||||
|
kill -s SIGTERM -- "-$$"
|
||||||
|
sleep 0.1
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
# Register trapping of SIGTERM and SIGINT
|
||||||
|
trap reap SIGTERM SIGINT
|
||||||
|
|
||||||
|
# Display the script PID, which will also be the process group ID for all
|
||||||
|
# child processes
|
||||||
|
echo "Process Group: $$"
|
||||||
|
|
||||||
|
# Set server launch related environment variables
|
||||||
|
DEBUG=1
|
||||||
|
LAUNCH_ARGS="$*"
|
||||||
|
|
||||||
|
|
||||||
|
# Launch the app
|
||||||
|
source ./script/server &
|
||||||
|
wait
|
1
script/include
Submodule
1
script/include
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 7417942f1614d6a7ad94e94d1621dca9b422dec2
|
@ -1,26 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
reap() {
|
# script/server: Launch the server
|
||||||
kill -TERM $child
|
|
||||||
sleep 0.1
|
|
||||||
exit
|
|
||||||
}
|
|
||||||
|
|
||||||
trap reap TERM INT
|
source "$(dirname "${0}")"/../script/include/global_header.inc.sh
|
||||||
|
|
||||||
# If a command fails, exit the script
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Ensure we are in the app root directory (not the /script directory)
|
|
||||||
cd "$(dirname "${0}")/.."
|
|
||||||
|
|
||||||
if [ -z "${SKIP_PIPENV+xxxx}" ]; then
|
|
||||||
CMD_PREFIX='pipenv run '
|
|
||||||
fi
|
|
||||||
PYTHON_CMD="${CMD_PREFIX}python"
|
|
||||||
|
|
||||||
# Launch the app
|
# Launch the app
|
||||||
${PYTHON_CMD} app.py ${@} &
|
run_command "./app.py ${LAUNCH_ARGS}"
|
||||||
child=$!
|
|
||||||
|
|
||||||
wait $child
|
|
||||||
|
24
script/setup
24
script/setup
@ -3,24 +3,10 @@
|
|||||||
# script/setup: Set up application for the first time after cloning, or set it
|
# script/setup: Set up application for the first time after cloning, or set it
|
||||||
# back to the initial first unused state.
|
# back to the initial first unused state.
|
||||||
|
|
||||||
# If a command fails, exit the script
|
source "$(dirname "${0}")"/../script/include/global_header.inc.sh
|
||||||
set -e
|
|
||||||
|
|
||||||
# Ensure we are in the app root directory (not the /script directory)
|
# Turn on sass compiler installation
|
||||||
cd "$(dirname "${0}")/.."
|
INSTALL_SASS="true"
|
||||||
|
|
||||||
# Install virtualenv
|
# Run the shared setup script
|
||||||
pip install pipenv
|
source ./script/include/run_setup
|
||||||
pipenv --python 3.6
|
|
||||||
|
|
||||||
if ! type sass > /dev/null; then
|
|
||||||
if type gem > /dev/null; then
|
|
||||||
echo 'installing a sass compiler...'
|
|
||||||
gem install sass
|
|
||||||
else
|
|
||||||
echo 'Could not install a sass compiler. Please install a version of sass.'
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install application dependencies
|
|
||||||
script/bootstrap
|
|
||||||
|
23
script/test
23
script/test
@ -2,22 +2,13 @@
|
|||||||
|
|
||||||
# script/test: Run static code checks and unit tests
|
# script/test: Run static code checks and unit tests
|
||||||
|
|
||||||
# If a command fails, exit the script
|
source "$(dirname "${0}")"/../script/include/global_header.inc.sh
|
||||||
set -e
|
|
||||||
|
|
||||||
# Ensure we are in the app root directory (not the /script directory)
|
# Define all relevant python files and directories for this app
|
||||||
cd "$(dirname "${0}")/.."
|
PYTHON_FILES="./app.py ./atst ./config"
|
||||||
|
|
||||||
if [ -z "${SKIP_PIPENV+xxxx}" ]; then
|
# Enable Python testing
|
||||||
CMD_PREFIX='pipenv run '
|
RUN_PYTHON_TESTS="true"
|
||||||
fi
|
|
||||||
PYLINT_CMD="${CMD_PREFIX}pylint"
|
|
||||||
PYTHON_CMD="${CMD_PREFIX}python"
|
|
||||||
|
|
||||||
# Run lint check
|
# Run the shared test script
|
||||||
echo "Running lint..."
|
source ./script/include/run_test
|
||||||
${PYLINT_CMD} app.py atst/ tests/
|
|
||||||
|
|
||||||
# Run unit tests
|
|
||||||
echo "Running unit tests..."
|
|
||||||
${PYTHON_CMD} -m pytest -s $*
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# If a command fails, exit the script
|
# script/update: Update dependencies
|
||||||
set -e
|
|
||||||
|
|
||||||
# Ensure we are in the app root directory (not the /script directory)
|
source "$(dirname "${0}")"/../script/include/global_header.inc.sh
|
||||||
cd "$(dirname "${0}")/.."
|
|
||||||
|
|
||||||
# Update dependencies
|
# Run the bootstrap script
|
||||||
script/bootstrap
|
source ./script/bootstrap
|
||||||
|
@ -47,6 +47,9 @@
|
|||||||
|
|
||||||
@include media($medium-screen) {
|
@include media($medium-screen) {
|
||||||
@include margin(($site-margins * 2) null);
|
@include margin(($site-margins * 2) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media($large-screen) {
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +79,10 @@
|
|||||||
> ul {
|
> ul {
|
||||||
@include panel-margin;
|
@include panel-margin;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
> li {
|
> li {
|
||||||
&:last-child {
|
&:last-child {
|
||||||
> .sidenav__link {
|
> .sidenav__link {
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
{% extends '../requests_new.html.to' %}
|
{% extends '../requests_new.html.to' %}
|
||||||
|
|
||||||
|
{% block form_action %}
|
||||||
|
<form method='POST' action="{{ reverse_url('requests_submit', request_id) }}" autocomplete="off">
|
||||||
|
{% end %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
|
|
||||||
{% autoescape None %}
|
{% autoescape None %}
|
||||||
@ -8,110 +12,105 @@
|
|||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
<h2 id="review-submit">Review & Submit</h2>
|
<h2 id="review-submit">Review & Submit</h2>
|
||||||
|
|
||||||
<p class="usa-font-lead">Before you can submit your request, please take a moment to review the information entered in the form. You may make changes by clicking the edit link on each section. When all information looks right, go ahead and submit.</p>
|
<p class="usa-font-lead">Before you can submit your request, please take a moment to review the information entered in the form. You may make changes by clicking the edit link on each section. When all information looks right, go ahead and submit.</p>
|
||||||
|
|
||||||
<h3>Details of Use <a href="">Edit</a></h3>
|
<h3>Details of Use <a href="{{ reverse_url('request_form_update', 1, request_id) }}">Edit</a></h3>
|
||||||
|
|
||||||
<h4>Overall Request Details</h4>
|
<h4>Overall Request Details</h4>
|
||||||
|
|
||||||
<label>What is the total estimated dollar value of the cloud resources you are requesting using the JEDI CSP Calculator? </label>
|
<label>What is the total estimated dollar value of the cloud resources you are requesting using the JEDI CSP Calculator? </label>
|
||||||
<b>{{ data.get('details_of_use', {}).get('dollar_value') }}</b>
|
<b>{{ data['details_of_use']['dollar_value'] }}</b>
|
||||||
|
|
||||||
<label>Please estimate the number of applications that might be supported by this request</label>
|
<label>Please estimate the number of applications that might be supported by this request</label>
|
||||||
<b>{{ data.get('details_of_use', {}).get('num_applications') }}</b>
|
<b>{{ data['details_of_use']['num_applications'] }}</b>
|
||||||
|
|
||||||
<label>Start Date</label>
|
<label>Start Date</label>
|
||||||
<b>{{ data.get('details_of_use', {}).get('date_start') }}</b>
|
<b>{{ data['details_of_use']['date_start'] }}</b>
|
||||||
|
|
||||||
<label>Please briefly describe how your team is expecting to use the JEDI Cloud</label>
|
<label>Please briefly describe how your team is expecting to use the JEDI Cloud</label>
|
||||||
<b>{{ data.get('details_of_use', {}).get('app_description') }}</b>
|
<b>{{ data['details_of_use']['app_description'] }}</b>
|
||||||
|
|
||||||
<label>What organizations are supported by these applications?</label>
|
<label>What organizations are supported by these applications?</label>
|
||||||
<b>{{ data.get('details_of_use', {}).get('supported_organizations') }}</b>
|
<b>{{ data['details_of_use']['supported_organizations'] }}</b>
|
||||||
|
|
||||||
<label>Please enter the Unique Item Identifier (UII)s related to your application(s) if you already have them.</label>
|
|
||||||
<b>{{ data.get('details_of_use', {}).get('uii_ids') }}</b>
|
|
||||||
|
|
||||||
<label>Please provide the Program Element (PE) Numbers related to your request</label>
|
|
||||||
<b>{{ data.get('details_of_use', {}).get('pe_id') }}</b>
|
|
||||||
|
|
||||||
<h4>Cloud Resources</h4>
|
<h4>Cloud Resources</h4>
|
||||||
|
|
||||||
<label>Total Number of vCPU cores</label>
|
<label>Total Number of vCPU cores</label>
|
||||||
<b>{{ data.get('details_of_use', {}).get('total_cores') }}</b>
|
<b>{{ data['details_of_use']['total_cores'] }}</b>
|
||||||
|
|
||||||
<label>Total RAM</label>
|
<label>Total RAM</label>
|
||||||
<b>{{ data.get('details_of_use', {}).get('total_ram') }}</b>
|
<b>{{ data['details_of_use']['total_ram'] }}</b>
|
||||||
|
|
||||||
<label>Total object storage</label>
|
<label>Total object storage</label>
|
||||||
<b>{{ data.get('details_of_use', {}).get('total_object_storage') }}</b>
|
<b>{{ data['details_of_use']['total_object_storage'] }}</b>
|
||||||
|
|
||||||
<label>Total server storage</label>
|
<label>Total server storage</label>
|
||||||
<b>{{ data.get('details_of_use', {}).get('total_server_storage') }}</b>
|
<b>{{ data['details_of_use']['total_server_storage'] }}</b>
|
||||||
|
|
||||||
<h4>Support Staff</h4>
|
<h4>Support Staff</h4>
|
||||||
|
|
||||||
<label>Do you have a contractor to advise and assist you with using cloud services?</label>
|
<label>Do you have a contractor to advise and assist you with using cloud services?</label>
|
||||||
<b>{{ data.get('details_of_use', {}).get('has_contractor_advisor') }}</b>
|
<b>{{ data['details_of_use']['has_contractor_advisor'] }}</b>
|
||||||
|
|
||||||
<label>Are you using the JEDI Cloud to migrate existing applications?</label>
|
<label>Are you using the JEDI Cloud to migrate existing applications?</label>
|
||||||
<b>{{ data.get('details_of_use', {}).get('is_migrating_application') }}</b>
|
<b>{{ data['details_of_use']['is_migrating_application'] }}</b>
|
||||||
|
|
||||||
<label>Please describe the organizations that are supporting you, include both government and contractor resources</label>
|
<label>Please describe the organizations that are supporting you, include both government and contractor resources</label>
|
||||||
<b>{{ data.get('details_of_use', {}).get('supporting_organization') }}</b>
|
<b>{{ data['details_of_use']['supporting_organization'] }}</b>
|
||||||
|
|
||||||
<label>Do you have a migration office that you're working with to migrate to the cloud?</label>
|
<label>Do you have a migration office that you're working with to migrate to the cloud?</label>
|
||||||
<b>{{ data.get('details_of_use', {}).get('has_migration_office') }}</b>
|
<b>{{ data['details_of_use']['has_migration_office'] }}</b>
|
||||||
|
|
||||||
<label>Please describe the organizations that are supporting you, include both government and contractor resources.</label>
|
<label>Please describe the organizations that are supporting you, include both government and contractor resources.</label>
|
||||||
<b>{{ data.get('details_of_use', {}).get('supporting_organization') }}</b>
|
<b>{{ data['details_of_use']['supporting_organization'] }}</b>
|
||||||
|
|
||||||
<br><br><hr>
|
<br><br><hr>
|
||||||
|
|
||||||
<h3>Information About You <a href="">Edit</a></h3>
|
<h3>Information About You <a href="{{ reverse_url('request_form_update', 2, request_id) }}">Edit</a></h3>
|
||||||
|
|
||||||
<label>First Name</label>
|
<label>First Name</label>
|
||||||
<b>{{ data.get('information_about_you', {}).get('fname_request') }}</b>
|
<b>{{ data['information_about_you']['fname_request'] }}</b>
|
||||||
|
|
||||||
<label>Last Name</label>
|
<label>Last Name</label>
|
||||||
<b>{{ data.get('information_about_you', {}).get('lname_request') }}</b>
|
<b>{{ data['information_about_you']['lname_request'] }}</b>
|
||||||
|
|
||||||
<label>Email (associated with your CAC)</label>
|
<label>Email (associated with your CAC)</label>
|
||||||
<b>{{ data.get('information_about_you', {}).get('email_request') }}</b>
|
<b>{{ data['information_about_you']['email_request'] }}</b>
|
||||||
|
|
||||||
<label>Phone Number</label>
|
<label>Phone Number</label>
|
||||||
<b>{{ data.get('information_about_you', {}).get('phone_number') }}</b>
|
<b>{{ data['information_about_you']['phone_number'] }}</b>
|
||||||
|
|
||||||
<label>Service Branch or Agency</label>
|
<label>Service Branch or Agency</label>
|
||||||
<b>{{ data.get('information_about_you', {}).get('service_branch') }}</b>
|
<b>{{ data['information_about_you']['service_branch'] }}</b>
|
||||||
|
|
||||||
<label>Citizenship</label>
|
<label>Citizenship</label>
|
||||||
<b>{{ data.get('information_about_you', {}).get('citizenship') }}</b>
|
<b>{{ data['information_about_you']['citizenship'] }}</b>
|
||||||
|
|
||||||
<label>Designation of Person</label>
|
<label>Designation of Person</label>
|
||||||
<b>{{ data.get('information_about_you', {}).get('designation') }}</b>
|
<b>{{ data['information_about_you']['designation'] }}</b>
|
||||||
|
|
||||||
<label>Latest Information Assurance (IA) Training completion date</label>
|
<label>Latest Information Assurance (IA) Training completion date</label>
|
||||||
<b>{{ data.get('information_about_you', {}).get('date_latest_training') }}</b>
|
<b>{{ data['information_about_you']['date_latest_training'] }}</b>
|
||||||
|
|
||||||
|
|
||||||
<br><br><hr>
|
<br><br><hr>
|
||||||
|
|
||||||
<h3>Primary Government/Military Point of Contact (POC) <a href="">Edit</a></h3>
|
<h3>Primary Government/Military Point of Contact (POC) <a href="{{ reverse_url('request_form_update', 3, request_id) }}">Edit</a></h3>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<label>POC First Name</label>
|
<label>POC First Name</label>
|
||||||
<b>{{ data.get('primary_poc', {}).get('fname_poc')}}</b>
|
<b>{{ data['primary_poc']['fname_poc']}}</b>
|
||||||
|
|
||||||
<label>POC Last Name</label>
|
<label>POC Last Name</label>
|
||||||
<b>{{ data.get('primary_poc', {}).get('lname_poc')}}</b>
|
<b>{{ data['primary_poc']['lname_poc']}}</b>
|
||||||
|
|
||||||
<label>POC Email (associated with CAC)</label>
|
<label>POC Email (associated with CAC)</label>
|
||||||
<b>{{ data.get('primary_poc', {}).get('email_poc')}} </b>
|
<b>{{ data['primary_poc']['email_poc']}} </b>
|
||||||
|
|
||||||
<label>DOD ID</label>
|
<label>DOD ID</label>
|
||||||
<b>{{ data.get('primary_poc', {}).get('dodid_poc')}}</b>
|
<b>{{ data['primary_poc']['dodid_poc']}}</b>
|
||||||
|
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
@ -119,5 +118,8 @@
|
|||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
{% block next %}
|
{% block next %}
|
||||||
<input type='submit' class='usa-button usa-button-primary' value='Submit' />
|
{% if not can_submit %}
|
||||||
|
<b class="usa-input-error-message">Please complete all required fields before submitting.</b>
|
||||||
|
{% end %}
|
||||||
|
<input type='submit' class='usa-button usa-button-primary' value='Submit' {{ "disabled" if not can_submit else "" }} />
|
||||||
{% end %}
|
{% end %}
|
||||||
|
@ -35,6 +35,21 @@
|
|||||||
</div>
|
</div>
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
|
{{ f.treasury_code.label }}
|
||||||
|
{{ f.treasury_code(placeholder="Example: 1200") }}
|
||||||
|
{% for e in f.treasury_code.errors %}
|
||||||
|
<div class="usa-input-error-message">
|
||||||
|
{{ e }}
|
||||||
|
</div>
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
{{ f.ba_code.label }}
|
||||||
|
{{ f.ba_code(placeholder="Example: 02") }}
|
||||||
|
{% for e in f.ba_code.errors %}
|
||||||
|
<div class="usa-input-error-message">
|
||||||
|
{{ e }}
|
||||||
|
</div>
|
||||||
|
{% end %}
|
||||||
|
|
||||||
<!-- KO Information -->
|
<!-- KO Information -->
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{% for i,s in enumerate(screens) %}
|
{% for i,s in enumerate(screens) %}
|
||||||
|
{% if s["show"] %}
|
||||||
<li>
|
<li>
|
||||||
{% if i+1==current %}
|
{% if i+1==current %}
|
||||||
<a class="sidenav__link sidenav__link--active" href="{{ reverse_url('request_form_update', i+1, request_id) if request_id else reverse_url('request_form_new',i+1) }}">
|
<a class="sidenav__link sidenav__link--active" href="{{ reverse_url('request_form_update', i+1, request_id) if request_id else reverse_url('request_form_new',i+1) }}">
|
||||||
@ -27,5 +28,6 @@
|
|||||||
{% end %}
|
{% end %}
|
||||||
</li>
|
</li>
|
||||||
{% end %}
|
{% end %}
|
||||||
|
{% end %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
@ -17,11 +17,13 @@
|
|||||||
<h1>New Request</h1>
|
<h1>New Request</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% block form_action %}
|
||||||
{% if request_id %}
|
{% if request_id %}
|
||||||
<form method='POST' action="{{ reverse_url('request_form_update', current, request_id) }}" autocomplete="off">
|
<form method='POST' action="{{ reverse_url('request_form_update', current, request_id) }}" autocomplete="off">
|
||||||
{% else %}
|
{% else %}
|
||||||
<form method='POST' action="{{ reverse_url('request_form_new', current) }}" autocomplete="off">
|
<form method='POST' action="{{ reverse_url('request_form_new', current) }}" autocomplete="off">
|
||||||
{% end %}
|
{% end %}
|
||||||
|
{% end %}
|
||||||
|
|
||||||
{% module xsrf_form_html() %}
|
{% module xsrf_form_html() %}
|
||||||
{% block form %}
|
{% block form %}
|
||||||
@ -34,13 +36,8 @@
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
|
@ -19,3 +19,23 @@ def app():
|
|||||||
deps.update(TEST_DEPS)
|
deps.update(TEST_DEPS)
|
||||||
|
|
||||||
return make_app(config, deps)
|
return make_app(config, deps)
|
||||||
|
|
||||||
|
class DummyForm(dict):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DummyField(object):
|
||||||
|
def __init__(self, data=None, errors=(), raw_data=None):
|
||||||
|
self.data = data
|
||||||
|
self.errors = list(errors)
|
||||||
|
self.raw_data = raw_data
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def dummy_form():
|
||||||
|
return DummyForm()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def dummy_field():
|
||||||
|
return DummyField()
|
||||||
|
61
tests/forms/test_validators.py
Normal file
61
tests/forms/test_validators.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
from wtforms.validators import ValidationError
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from atst.forms.validators import Alphabet, IsNumber, PhoneNumber
|
||||||
|
|
||||||
|
|
||||||
|
class TestIsNumber:
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("valid", ["0", "12", "-12"])
|
||||||
|
def test_IsNumber_accepts_integers(self, valid, dummy_form, dummy_field):
|
||||||
|
validator = IsNumber()
|
||||||
|
dummy_field.data = valid
|
||||||
|
validator(dummy_form, dummy_field)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("invalid", ["12.1", "two", ""])
|
||||||
|
def test_IsNumber_rejects_anything_else(self, invalid, dummy_form, dummy_field):
|
||||||
|
validator = IsNumber()
|
||||||
|
dummy_field.data = invalid
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
validator(dummy_form, dummy_field)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPhoneNumber:
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("valid", [
|
||||||
|
"12345",
|
||||||
|
"1234567890",
|
||||||
|
"(123) 456-7890",
|
||||||
|
])
|
||||||
|
def test_PhoneNumber_accepts_valid_numbers(self, valid, dummy_form, dummy_field):
|
||||||
|
validator = PhoneNumber()
|
||||||
|
dummy_field.data = valid
|
||||||
|
validator(dummy_form, dummy_field)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("invalid", [
|
||||||
|
"1234",
|
||||||
|
"123456",
|
||||||
|
"1234567abc",
|
||||||
|
"(123) 456-789012",
|
||||||
|
])
|
||||||
|
def test_PhoneNumber_rejects_invalid_numbers(self, invalid, dummy_form, dummy_field):
|
||||||
|
validator = PhoneNumber()
|
||||||
|
dummy_field.data = invalid
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
validator(dummy_form, dummy_field)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAlphabet:
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("valid", ["a", "abcde"])
|
||||||
|
def test_Alphabet_accepts_letters(self, valid, dummy_form, dummy_field):
|
||||||
|
validator = Alphabet()
|
||||||
|
dummy_field.data = valid
|
||||||
|
validator(dummy_form, dummy_field)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("invalid", ["", "hi mark", "cloud9"])
|
||||||
|
def test_Alphabet_rejects_non_letters(self, invalid, dummy_form, dummy_field):
|
||||||
|
validator = Alphabet()
|
||||||
|
dummy_field.data = invalid
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
validator(dummy_form, dummy_field)
|
@ -49,6 +49,7 @@ class MockRequestsClient(MockApiClient):
|
|||||||
"id": "66b8ef71-86d3-48ef-abc2-51bfa1732b6b",
|
"id": "66b8ef71-86d3-48ef-abc2-51bfa1732b6b",
|
||||||
"creator": "49903ae7-da4a-49bf-a6dc-9dff5d004238",
|
"creator": "49903ae7-da4a-49bf-a6dc-9dff5d004238",
|
||||||
"body": {},
|
"body": {},
|
||||||
|
"status": "incomplete"
|
||||||
}
|
}
|
||||||
return self._get_response("GET", path, 200, json=json)
|
return self._get_response("GET", path, 200, json=json)
|
||||||
|
|
||||||
|
@ -21,11 +21,14 @@ def test_redirects_when_not_logged_in(http_client, base_url):
|
|||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_redirects_when_session_does_not_exist(monkeypatch, http_client, base_url):
|
def test_redirects_when_session_does_not_exist(monkeypatch, http_client, base_url):
|
||||||
monkeypatch.setattr("atst.handlers.main.MainHandler.get_secure_cookie", lambda s,c: 'stale cookie!')
|
monkeypatch.setattr("atst.handlers.main.Main.get_secure_cookie", lambda s,c: 'stale cookie!')
|
||||||
response = yield http_client.fetch(
|
response = yield http_client.fetch(
|
||||||
base_url + "/home", raise_error=False, follow_redirects=False
|
base_url + "/home", raise_error=False, follow_redirects=False
|
||||||
)
|
)
|
||||||
location = response.headers["Location"]
|
location = response.headers["Location"]
|
||||||
|
cookie = response.headers._dict.get('Set-Cookie')
|
||||||
|
# should clear session cookie
|
||||||
|
assert 'atat=""' in cookie
|
||||||
assert response.code == 302
|
assert response.code == 302
|
||||||
assert response.error
|
assert response.error
|
||||||
assert re.match("/\??", location)
|
assert re.match("/\??", location)
|
||||||
@ -33,9 +36,9 @@ def test_redirects_when_session_does_not_exist(monkeypatch, http_client, base_ur
|
|||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_login_with_valid_bearer_token(app, monkeypatch, http_client, base_url):
|
def test_login_with_valid_bearer_token(app, monkeypatch, http_client, base_url):
|
||||||
monkeypatch.setattr("atst.handlers.login.Login._fetch_user_info", _fetch_user_info)
|
monkeypatch.setattr("atst.handlers.login_redirect.LoginRedirect._fetch_user_info", _fetch_user_info)
|
||||||
response = yield http_client.fetch(
|
response = yield http_client.fetch(
|
||||||
base_url + "/login?bearer-token=abc-123",
|
base_url + "/login-redirect?bearer-token=abc-123",
|
||||||
follow_redirects=False,
|
follow_redirects=False,
|
||||||
raise_error=False,
|
raise_error=False,
|
||||||
)
|
)
|
||||||
@ -65,10 +68,10 @@ def test_login_with_invalid_bearer_token(http_client, base_url):
|
|||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_valid_login_creates_session(app, monkeypatch, http_client, base_url):
|
def test_valid_login_creates_session(app, monkeypatch, http_client, base_url):
|
||||||
monkeypatch.setattr("atst.handlers.login.Login._fetch_user_info", _fetch_user_info)
|
monkeypatch.setattr("atst.handlers.login_redirect.LoginRedirect._fetch_user_info", _fetch_user_info)
|
||||||
assert len(app.sessions.sessions) == 0
|
assert len(app.sessions.sessions) == 0
|
||||||
yield http_client.fetch(
|
yield http_client.fetch(
|
||||||
base_url + "/login?bearer-token=abc-123",
|
base_url + "/login-redirect?bearer-token=abc-123",
|
||||||
follow_redirects=False,
|
follow_redirects=False,
|
||||||
raise_error=False,
|
raise_error=False,
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user