diff --git a/atst/forms/financial.py b/atst/forms/financial.py
index ea81ea1b..afc6f3d0 100644
--- a/atst/forms/financial.py
+++ b/atst/forms/financial.py
@@ -2,7 +2,7 @@ import re
import pendulum
from wtforms.fields.html5 import DateField, EmailField
from wtforms.fields import StringField, FileField
-from wtforms.validators import InputRequired, Required, Email, Regexp
+from wtforms.validators import InputRequired, Email, Regexp
from flask_wtf.file import FileAllowed
from atst.domain.exceptions import NotFoundError
@@ -99,47 +99,47 @@ class BaseFinancialForm(ValidatedForm):
task_order_number = StringField(
"Task Order Number associated with this request",
description="Include the original Task Order number (including the 000X at the end). Do not include any modification numbers. Note that there may be a lag between approving a task order and when it becomes available in our system.",
- validators=[Required()],
+ validators=[InputRequired()],
)
uii_ids = NewlineListField(
"Unique Item Identifier (UII)s related to your application(s) if you already have them.",
description="If you have more than one UII, place each one on a new line.",
- validators=[Required()],
+ validators=[InputRequired()],
)
pe_id = StringField(
"Program Element Number",
description="PE numbers help the Department of Defense identify which offices' budgets are contributing towards this resource use.
It should be 7 digits followed by 1-3 letters, and should have a zero as the first and third digits.",
- validators=[Required()],
+ validators=[InputRequired()],
)
treasury_code = StringField(
"Program Treasury Code",
description="Program Treasury Code (or Appropriations Code) identifies resource types.
It should be a four digit or six digit number, optionally prefixed by one or more zeros.",
- validators=[Required(), Regexp(TREASURY_CODE_REGEX)],
+ validators=[InputRequired(), Regexp(TREASURY_CODE_REGEX)],
)
ba_code = StringField(
"Program Budget Activity (BA) Code",
description="BA Code is used to identify the purposes, projects, or types of activities financed by the appropriation fund.
It should be two digits, followed by an optional letter.",
- validators=[Required(), Regexp(BA_CODE_REGEX)],
+ validators=[InputRequired(), Regexp(BA_CODE_REGEX)],
)
- fname_co = StringField("KO First Name", validators=[Required()])
- lname_co = StringField("KO Last Name", validators=[Required()])
+ fname_co = StringField("KO First Name", validators=[InputRequired()])
+ lname_co = StringField("KO Last Name", validators=[InputRequired()])
- email_co = EmailField("KO Email", validators=[Required(), Email()])
+ email_co = EmailField("KO Email", validators=[InputRequired(), Email()])
- office_co = StringField("KO Office", validators=[Required()])
+ office_co = StringField("KO Office", validators=[InputRequired()])
- fname_cor = StringField("COR First Name", validators=[Required()])
+ fname_cor = StringField("COR First Name", validators=[InputRequired()])
- lname_cor = StringField("COR Last Name", validators=[Required()])
+ lname_cor = StringField("COR Last Name", validators=[InputRequired()])
- email_cor = EmailField("COR Email", validators=[Required(), Email()])
+ email_cor = EmailField("COR Email", validators=[InputRequired(), Email()])
- office_cor = StringField("COR Office", validators=[Required()])
+ office_cor = StringField("COR Office", validators=[InputRequired()])
class FinancialForm(BaseFinancialForm):
@@ -156,13 +156,13 @@ class FinancialForm(BaseFinancialForm):
class ExtendedFinancialForm(BaseFinancialForm):
def validate(self, *args, **kwargs):
if self.funding_type.data == "OTHER":
- self.funding_type_other.validators.append(Required())
+ self.funding_type_other.validators.append(InputRequired())
return super().validate(*args, **kwargs)
funding_type = SelectField(
description="What is the source of funding?",
choices=FUNDING_TYPES,
- validators=[Required()],
+ validators=[InputRequired()],
render_kw={"required": False},
)
@@ -172,7 +172,7 @@ class ExtendedFinancialForm(BaseFinancialForm):
"Task Order Expiration Date",
description="Please enter the expiration date for the Task Order",
validators=[
- Required(),
+ InputRequired(),
DateRange(
lower_bound=pendulum.duration(days=0),
upper_bound=pendulum.duration(years=100),
@@ -228,6 +228,6 @@ class ExtendedFinancialForm(BaseFinancialForm):
"Upload a copy of your Task Order",
validators=[
FileAllowed(["pdf"], "Only PDF documents can be uploaded."),
- Required(),
+ InputRequired(),
],
)
diff --git a/atst/forms/new_member.py b/atst/forms/new_member.py
index b7358d00..5b53c839 100644
--- a/atst/forms/new_member.py
+++ b/atst/forms/new_member.py
@@ -1,4 +1,4 @@
-from flask_wtf import Form
+from flask_wtf import FlaskForm
from wtforms.fields import StringField
from wtforms.fields.html5 import EmailField
from wtforms.validators import Required, Email, Length
@@ -9,7 +9,7 @@ from atst.forms.fields import SelectField
from .data import WORKSPACE_ROLES
-class NewMemberForm(Form):
+class NewMemberForm(FlaskForm):
first_name = StringField(label="First Name", validators=[Required()])
last_name = StringField(label="Last Name", validators=[Required()])
diff --git a/atst/forms/new_project.py b/atst/forms/new_project.py
index b6007364..00b01325 100644
--- a/atst/forms/new_project.py
+++ b/atst/forms/new_project.py
@@ -1,10 +1,10 @@
-from flask_wtf import Form
+from flask_wtf import FlaskForm
from wtforms.fields import StringField, TextAreaField, FieldList
from wtforms.validators import Required
from atst.forms.validators import ListItemRequired, ListItemsUnique
-class NewProjectForm(Form):
+class NewProjectForm(FlaskForm):
EMPTY_ENVIRONMENT_NAMES = ["", None]
@@ -20,7 +20,7 @@ class NewProjectForm(Form):
@property
def data(self):
- _data = super(Form, self).data
+ _data = super(FlaskForm, self).data
_data["environment_names"] = [
n
for n in _data["environment_names"]
diff --git a/atst/forms/new_request.py b/atst/forms/new_request.py
index 74f35f51..08d2fee4 100644
--- a/atst/forms/new_request.py
+++ b/atst/forms/new_request.py
@@ -1,7 +1,7 @@
import pendulum
from wtforms.fields.html5 import DateField, EmailField, IntegerField, TelField
from wtforms.fields import BooleanField, RadioField, StringField, TextAreaField
-from wtforms.validators import Email, Length, Optional, Required, DataRequired
+from wtforms.validators import Email, Length, Optional, InputRequired, DataRequired
from .fields import SelectField
from .forms import ValidatedForm
@@ -35,8 +35,8 @@ class DetailsOfUseForm(ValidatedForm):
annual_spend = 0
if annual_spend > Requests.AUTO_APPROVE_THRESHOLD:
- self.number_user_sessions.validators.append(Required())
- self.average_daily_traffic.validators.append(Required())
+ self.number_user_sessions.validators.append(InputRequired())
+ self.average_daily_traffic.validators.append(InputRequired())
return super(DetailsOfUseForm, self).validate(*args, **kwargs)
@@ -45,13 +45,13 @@ class DetailsOfUseForm(ValidatedForm):
"DoD Component",
description="Identify the DoD component that is requesting access to the JEDI Cloud",
choices=SERVICE_BRANCHES,
- validators=[Required()],
+ validators=[InputRequired()],
)
jedi_usage = TextAreaField(
"JEDI Usage",
description="Your answer will help us provide tangible examples to DoD leadership how and why commercial cloud resources are accelerating the Department's missions",
- validators=[Required()],
+ validators=[InputRequired()],
)
# Details of Use: Cloud Readiness
@@ -94,13 +94,13 @@ class DetailsOfUseForm(ValidatedForm):
data_transfers = SelectField(
description="How much data is being transferred to the cloud?",
choices=DATA_TRANSFER_AMOUNTS,
- validators=[Required()],
+ validators=[DataRequired()],
)
expected_completion_date = SelectField(
description="When do you expect to complete your migration to the JEDI Cloud?",
choices=COMPLETION_DATE_RANGES,
- validators=[Required()],
+ validators=[DataRequired()],
)
cloud_native = RadioField(
@@ -137,7 +137,7 @@ class DetailsOfUseForm(ValidatedForm):
start_date = DateField(
description="When do you expect to start using the JEDI Cloud (not for billing purposes)?",
validators=[
- DataRequired(),
+ InputRequired(),
DateRange(
lower_bound=pendulum.duration(days=1),
upper_bound=None,
@@ -151,7 +151,7 @@ class DetailsOfUseForm(ValidatedForm):
"Name Your Request",
description="This name serves as a reference for your initial request and the associated workspace that will be created once this request is approved. You may edit this name later.",
validators=[
- Required(),
+ InputRequired(),
Length(
min=4,
max=100,
@@ -162,16 +162,16 @@ class DetailsOfUseForm(ValidatedForm):
class InformationAboutYouForm(ValidatedForm):
- fname_request = StringField("First Name", validators=[Required(), Alphabet()])
+ fname_request = StringField("First Name", validators=[InputRequired(), Alphabet()])
- lname_request = StringField("Last Name", validators=[Required(), Alphabet()])
+ lname_request = StringField("Last Name", validators=[InputRequired(), Alphabet()])
- email_request = EmailField("E-mail Address", validators=[Required(), Email()])
+ email_request = EmailField("E-mail Address", validators=[InputRequired(), Email()])
phone_number = TelField(
"Phone Number",
description="Enter a 10-digit phone number",
- validators=[Required(), PhoneNumber()],
+ validators=[InputRequired(), PhoneNumber()],
)
service_branch = SelectField(
@@ -187,7 +187,7 @@ class InformationAboutYouForm(ValidatedForm):
("Foreign National", "Foreign National"),
("Other", "Other"),
],
- validators=[Required()],
+ validators=[InputRequired()],
)
designation = RadioField(
@@ -198,14 +198,14 @@ class InformationAboutYouForm(ValidatedForm):
("civilian", "Civilian"),
("contractor", "Contractor"),
],
- validators=[Required()],
+ validators=[InputRequired()],
)
date_latest_training = DateField(
"Latest Information Assurance (IA) Training Completion Date",
description='To complete the training, you can find it in Information Assurance Cyber Awareness Challange website.',
validators=[
- Required(),
+ InputRequired(),
DateRange(
lower_bound=pendulum.duration(years=1),
upper_bound=pendulum.duration(days=0),
@@ -234,14 +234,14 @@ class WorkspaceOwnerForm(ValidatedForm):
false_values=(False, "false", "False", "no", ""),
)
- fname_poc = StringField("First Name", validators=[Required()])
+ fname_poc = StringField("First Name", validators=[InputRequired()])
- lname_poc = StringField("Last Name", validators=[Required()])
+ lname_poc = StringField("Last Name", validators=[InputRequired()])
- email_poc = EmailField("Email Address", validators=[Required(), Email()])
+ email_poc = EmailField("Email Address", validators=[InputRequired(), Email()])
dodid_poc = StringField(
- "DoD ID", validators=[Required(), Length(min=10), IsNumber()]
+ "DoD ID", validators=[InputRequired(), Length(min=10), IsNumber()]
)
diff --git a/templates/workspaces/projects/edit.html b/templates/workspaces/projects/edit.html
index 11bc064e..1fa3bc20 100644
--- a/templates/workspaces/projects/edit.html
+++ b/templates/workspaces/projects/edit.html
@@ -5,6 +5,10 @@
{% block workspace_content %}
+{% if g.dev %}
+ {{ Alert("In Progress", message="This page is a work in progress. You won't be able to edit environments on this project just yet.", level="warning") }}
+{% endif %}
+
{% set modalName = "updateProjectConfirmation" %}
{% include "fragments/edit_project_form.html" %}
diff --git a/tests/forms/test_financial.py b/tests/forms/test_financial.py
index 970e4b6b..5a4c80dd 100644
--- a/tests/forms/test_financial.py
+++ b/tests/forms/test_financial.py
@@ -47,8 +47,8 @@ def test_funding_type_other_required_if_funding_type_is_other():
],
)
def test_treasury_code_validation(input_, expected):
- form_data = {"treasury_code": input_}
- form = FinancialForm(data=form_data)
+ form_data = ImmutableMultiDict([("treasury_code", input_)])
+ form = FinancialForm(form_data)
form.validate()
is_valid = "treasury_code" not in form.errors
@@ -74,8 +74,8 @@ def test_treasury_code_validation(input_, expected):
],
)
def test_ba_code_validation(input_, expected):
- form_data = {"ba_code": input_}
- form = FinancialForm(data=form_data)
+ form_data = ImmutableMultiDict([("ba_code", input_)])
+ form = FinancialForm(form_data)
form.validate()
is_valid = "ba_code" not in form.errors
diff --git a/tests/forms/test_new_request.py b/tests/forms/test_new_request.py
index 16fa3623..39f91b92 100644
--- a/tests/forms/test_new_request.py
+++ b/tests/forms/test_new_request.py
@@ -1,4 +1,5 @@
import pytest
+from werkzeug.datastructures import ImmutableMultiDict
from atst.forms.new_request import DetailsOfUseForm
@@ -26,9 +27,13 @@ class TestDetailsOfUseForm:
"expected_completion_date": "Less than 1 month",
}
+ def _make_form(self, data):
+ form_data = ImmutableMultiDict(data.items())
+ return DetailsOfUseForm(form_data)
+
def test_require_cloud_native_when_not_migrating(self):
extra_data = {"jedi_migration": "no"}
- request_form = DetailsOfUseForm(data={**self.form_data, **extra_data})
+ request_form = self._make_form({**self.form_data, **extra_data})
assert not request_form.validate()
assert request_form.errors == {"cloud_native": ["Not a valid choice"]}
@@ -38,7 +43,7 @@ class TestDetailsOfUseForm:
"data_transfers": "",
"expected_completion_date": "",
}
- request_form = DetailsOfUseForm(data={**self.form_data, **extra_data})
+ request_form = self._make_form({**self.form_data, **extra_data})
assert not request_form.validate()
assert request_form.errors == {
"rationalization_software_systems": ["Not a valid choice"],
@@ -53,7 +58,7 @@ class TestDetailsOfUseForm:
data = {**self.form_data, **self.migration_data}
del data["organization_providing_assistance"]
- request_form = DetailsOfUseForm(data=data)
+ request_form = self._make_form(data)
assert not request_form.validate()
assert request_form.errors == {
"organization_providing_assistance": ["Not a valid choice"]
@@ -64,7 +69,7 @@ class TestDetailsOfUseForm:
data["technical_support_team"] = "no"
del data["organization_providing_assistance"]
- request_form = DetailsOfUseForm(data=data)
+ request_form = self._make_form(data)
assert request_form.validate()
def test_sessions_required_for_large_projects(self):
@@ -73,26 +78,26 @@ class TestDetailsOfUseForm:
del data["number_user_sessions"]
del data["average_daily_traffic"]
- request_form = DetailsOfUseForm(data=data)
+ request_form = self._make_form(data)
assert not request_form.validate()
assert request_form.errors == {
"number_user_sessions": ["This field is required."],
"average_daily_traffic": ["This field is required."],
}
- def test_sessions_not_required_invalid_monthly_spend(self):
+ def test_sessions_not_required_low_monthly_spend(self):
data = {**self.form_data, **self.migration_data}
- data["estimated_monthly_spend"] = "foo"
+ data["estimated_monthly_spend"] = "10"
del data["number_user_sessions"]
del data["average_daily_traffic"]
- request_form = DetailsOfUseForm(data=data)
+ request_form = self._make_form(data)
assert request_form.validate()
def test_start_date_must_be_in_the_future(self):
data = {**self.form_data, **self.migration_data}
data["start_date"] = "01/01/2018"
- request_form = DetailsOfUseForm(data=data)
+ request_form = self._make_form(data)
assert not request_form.validate()
assert "Must be a date in the future." in request_form.errors["start_date"]
diff --git a/tests/routes/test_workspaces.py b/tests/routes/test_workspaces.py
index 3523d207..66086e98 100644
--- a/tests/routes/test_workspaces.py
+++ b/tests/routes/test_workspaces.py
@@ -73,6 +73,59 @@ def test_update_workspace_name(client, user_session):
assert workspace.name == "a cool new name"
+def test_view_edit_project(client, user_session):
+ owner = UserFactory.create()
+ workspace = WorkspaceFactory.create()
+ Workspaces._create_workspace_role(owner, workspace, "admin")
+ project = Projects.create(
+ owner,
+ workspace,
+ "Snazzy Project",
+ "A new project for me and my friends",
+ {"env1", "env2"},
+ )
+ user_session(owner)
+ response = client.get(
+ "/workspaces/{}/projects/{}/edit".format(workspace.id, project.id)
+ )
+ assert response.status_code == 200
+
+
+def test_create_member(client, user_session):
+ owner = UserFactory.create()
+ user = UserFactory.create()
+ workspace = WorkspaceFactory.create()
+ Workspaces._create_workspace_role(owner, workspace, "admin")
+ user_session(owner)
+ response = client.post(
+ url_for("workspaces.create_member", workspace_id=workspace.id),
+ data={
+ "dod_id": user.dod_id,
+ "first_name": "Wilbur",
+ "last_name": "Zuckerman",
+ "email": "some_pig@zuckermans.com",
+ "workspace_role": "developer",
+ },
+ follow_redirects=True,
+ )
+
+ assert response.status_code == 200
+ assert user.has_workspaces
+
+
+def test_permissions_for_view_member(client, user_session):
+ user = UserFactory.create()
+ workspace = WorkspaceFactory.create()
+ Workspaces._create_workspace_role(user, workspace, "developer")
+ member = WorkspaceUsers.add(user, workspace.id, "developer")
+ user_session(user)
+ response = client.post(
+ url_for("workspaces.view_member", workspace_id=workspace.id, member_id=user.id),
+ follow_redirects=True,
+ )
+ assert response.status_code == 404
+
+
def test_update_member_workspace_role(client, user_session):
owner = UserFactory.create()
workspace = WorkspaceFactory.create()
@@ -91,6 +144,24 @@ def test_update_member_workspace_role(client, user_session):
assert member.role == "security_auditor"
+def test_update_member_workspace_role_with_no_data(client, user_session):
+ owner = UserFactory.create()
+ workspace = WorkspaceFactory.create()
+ Workspaces._create_workspace_role(owner, workspace, "admin")
+ user = UserFactory.create()
+ member = WorkspaceUsers.add(user, workspace.id, "developer")
+ user_session(owner)
+ response = client.post(
+ url_for(
+ "workspaces.update_member", workspace_id=workspace.id, member_id=user.id
+ ),
+ data={},
+ follow_redirects=True,
+ )
+ assert response.status_code == 200
+ assert member.role == "developer"
+
+
def test_update_member_environment_role(client, user_session):
owner = UserFactory.create()
workspace = WorkspaceFactory.create()
@@ -124,3 +195,32 @@ def test_update_member_environment_role(client, user_session):
assert response.status_code == 200
assert EnvironmentRoles.get(user.id, env1_id).role == "security_auditor"
assert EnvironmentRoles.get(user.id, env2_id).role == "devops"
+
+
+def test_update_member_environment_role_with_no_data(client, user_session):
+ owner = UserFactory.create()
+ workspace = WorkspaceFactory.create()
+ Workspaces._create_workspace_role(owner, workspace, "admin")
+
+ user = UserFactory.create()
+ member = WorkspaceUsers.add(user, workspace.id, "developer")
+ project = Projects.create(
+ owner,
+ workspace,
+ "Snazzy Project",
+ "A new project for me and my friends",
+ {"env1"},
+ )
+ env1_id = project.environments[0].id
+ for env in project.environments:
+ Environments.add_member(env, user, "developer")
+ user_session(owner)
+ response = client.post(
+ url_for(
+ "workspaces.update_member", workspace_id=workspace.id, member_id=user.id
+ ),
+ data={"env_" + str(env1_id): None, "env_" + str(env1_id): ""},
+ follow_redirects=True,
+ )
+ assert response.status_code == 200
+ assert EnvironmentRoles.get(user.id, env1_id).role == "developer"