Merge branch 'staging' into upload-timing-bug
This commit is contained in:
commit
dd6d516d2c
@ -293,6 +293,11 @@ workflows:
|
|||||||
- integration-tests:
|
- integration-tests:
|
||||||
requires:
|
requires:
|
||||||
- docker-build
|
- docker-build
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- staging
|
||||||
|
- master
|
||||||
- deploy-staging:
|
- deploy-staging:
|
||||||
requires:
|
requires:
|
||||||
- test
|
- test
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -33,6 +33,7 @@ static/buildinfo.*
|
|||||||
log/*
|
log/*
|
||||||
|
|
||||||
config/dev.ini
|
config/dev.ini
|
||||||
|
.env*
|
||||||
|
|
||||||
# CRLs
|
# CRLs
|
||||||
/crl
|
/crl
|
||||||
|
@ -161,7 +161,7 @@
|
|||||||
"hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207",
|
"hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207",
|
||||||
"is_secret": false,
|
"is_secret": false,
|
||||||
"is_verified": false,
|
"is_verified": false,
|
||||||
"line_number": 32,
|
"line_number": 31,
|
||||||
"type": "Hex High Entropy String"
|
"type": "Hex High Entropy String"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -170,7 +170,7 @@
|
|||||||
"hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207",
|
"hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207",
|
||||||
"is_secret": false,
|
"is_secret": false,
|
||||||
"is_verified": false,
|
"is_verified": false,
|
||||||
"line_number": 656,
|
"line_number": 657,
|
||||||
"type": "Hex High Entropy String"
|
"type": "Hex High Entropy String"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -0,0 +1,198 @@
|
|||||||
|
"""update schema based on business logic
|
||||||
|
|
||||||
|
Revision ID: 67a2151d6269
|
||||||
|
Revises: 687fd43489d6
|
||||||
|
Create Date: 2019-12-02 14:16:24.902108
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '67a2151d6269' # pragma: allowlist secret
|
||||||
|
down_revision = '687fd43489d6' # pragma: allowlist secret
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('application_invitations', 'application_role_id',
|
||||||
|
existing_type=postgresql.UUID(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('application_invitations', 'dod_id',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('application_invitations', 'expiration_time',
|
||||||
|
existing_type=postgresql.TIMESTAMP(timezone=True),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('application_invitations', 'first_name',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('application_invitations', 'inviter_id',
|
||||||
|
existing_type=postgresql.UUID(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('application_invitations', 'last_name',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('application_invitations', 'token',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('application_roles', 'status',
|
||||||
|
existing_type=sa.VARCHAR(length=8),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('clins', 'end_date',
|
||||||
|
existing_type=sa.DATE(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('clins', 'jedi_clin_type',
|
||||||
|
existing_type=sa.VARCHAR(length=11),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('clins', 'number',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('clins', 'obligated_amount',
|
||||||
|
existing_type=sa.NUMERIC(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('clins', 'start_date',
|
||||||
|
existing_type=sa.DATE(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('clins', 'total_amount',
|
||||||
|
existing_type=sa.NUMERIC(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('environment_roles', 'status',
|
||||||
|
existing_type=sa.VARCHAR(length=9),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('portfolio_invitations', 'dod_id',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('portfolio_invitations', 'expiration_time',
|
||||||
|
existing_type=postgresql.TIMESTAMP(timezone=True),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('portfolio_invitations', 'first_name',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('portfolio_invitations', 'inviter_id',
|
||||||
|
existing_type=postgresql.UUID(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('portfolio_invitations', 'last_name',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('portfolio_invitations', 'portfolio_role_id',
|
||||||
|
existing_type=postgresql.UUID(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('portfolio_invitations', 'token',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('portfolio_roles', 'status',
|
||||||
|
existing_type=sa.VARCHAR(length=8),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('portfolios', 'defense_component',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('portfolios', 'name',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('task_orders', 'portfolio_id',
|
||||||
|
existing_type=postgresql.UUID(),
|
||||||
|
nullable=False)
|
||||||
|
op.drop_constraint('task_orders_user_id_fkey', 'task_orders', type_='foreignkey')
|
||||||
|
op.drop_column('task_orders', 'user_id')
|
||||||
|
op.alter_column('users', 'first_name',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('users', 'last_name',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('users', 'last_name',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('users', 'first_name',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=True)
|
||||||
|
op.add_column('task_orders', sa.Column('user_id', postgresql.UUID(), autoincrement=False, nullable=True))
|
||||||
|
op.create_foreign_key('task_orders_user_id_fkey', 'task_orders', 'users', ['user_id'], ['id'])
|
||||||
|
op.alter_column('task_orders', 'portfolio_id',
|
||||||
|
existing_type=postgresql.UUID(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('portfolios', 'name',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('portfolios', 'defense_component',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('portfolio_roles', 'status',
|
||||||
|
existing_type=sa.VARCHAR(length=8),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('portfolio_invitations', 'token',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('portfolio_invitations', 'portfolio_role_id',
|
||||||
|
existing_type=postgresql.UUID(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('portfolio_invitations', 'last_name',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('portfolio_invitations', 'inviter_id',
|
||||||
|
existing_type=postgresql.UUID(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('portfolio_invitations', 'first_name',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('portfolio_invitations', 'expiration_time',
|
||||||
|
existing_type=postgresql.TIMESTAMP(timezone=True),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('portfolio_invitations', 'dod_id',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('environment_roles', 'status',
|
||||||
|
existing_type=sa.VARCHAR(length=9),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('clins', 'total_amount',
|
||||||
|
existing_type=sa.NUMERIC(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('clins', 'start_date',
|
||||||
|
existing_type=sa.DATE(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('clins', 'obligated_amount',
|
||||||
|
existing_type=sa.NUMERIC(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('clins', 'number',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('clins', 'jedi_clin_type',
|
||||||
|
existing_type=sa.VARCHAR(length=11),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('clins', 'end_date',
|
||||||
|
existing_type=sa.DATE(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('application_roles', 'status',
|
||||||
|
existing_type=sa.VARCHAR(length=8),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('application_invitations', 'token',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('application_invitations', 'last_name',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('application_invitations', 'inviter_id',
|
||||||
|
existing_type=postgresql.UUID(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('application_invitations', 'first_name',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('application_invitations', 'expiration_time',
|
||||||
|
existing_type=postgresql.TIMESTAMP(timezone=True),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('application_invitations', 'dod_id',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('application_invitations', 'application_role_id',
|
||||||
|
existing_type=postgresql.UUID(),
|
||||||
|
nullable=True)
|
||||||
|
# ### end Alembic commands ###
|
@ -11,10 +11,8 @@ class TaskOrders(BaseDomainClass):
|
|||||||
resource_name = "task_order"
|
resource_name = "task_order"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, creator, portfolio_id, number, clins, pdf):
|
def create(cls, portfolio_id, number, clins, pdf):
|
||||||
task_order = TaskOrder(
|
task_order = TaskOrder(portfolio_id=portfolio_id, number=number, pdf=pdf)
|
||||||
portfolio_id=portfolio_id, creator=creator, number=number, pdf=pdf
|
|
||||||
)
|
|
||||||
db.session.add(task_order)
|
db.session.add(task_order)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ from wtforms.fields import (
|
|||||||
HiddenField,
|
HiddenField,
|
||||||
)
|
)
|
||||||
from wtforms.fields.html5 import DateField
|
from wtforms.fields.html5 import DateField
|
||||||
from wtforms.validators import Required, Optional, Length, NumberRange, ValidationError
|
from wtforms.validators import Required, Length, NumberRange, ValidationError
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from numbers import Number
|
from numbers import Number
|
||||||
|
|
||||||
@ -61,9 +61,7 @@ class CLINForm(FlaskForm):
|
|||||||
coerce=coerce_enum,
|
coerce=coerce_enum,
|
||||||
)
|
)
|
||||||
|
|
||||||
number = StringField(
|
number = StringField(label=translate("task_orders.form.clin_number_label"))
|
||||||
label=translate("task_orders.form.clin_number_label"), validators=[Optional()]
|
|
||||||
)
|
|
||||||
start_date = DateField(
|
start_date = DateField(
|
||||||
translate("task_orders.form.pop_start"),
|
translate("task_orders.form.pop_start"),
|
||||||
description=translate("task_orders.form.pop_example"),
|
description=translate("task_orders.form.pop_example"),
|
||||||
|
@ -12,7 +12,10 @@ class ApplicationInvitation(
|
|||||||
__tablename__ = "application_invitations"
|
__tablename__ = "application_invitations"
|
||||||
|
|
||||||
application_role_id = Column(
|
application_role_id = Column(
|
||||||
UUID(as_uuid=True), ForeignKey("application_roles.id"), index=True
|
UUID(as_uuid=True),
|
||||||
|
ForeignKey("application_roles.id"),
|
||||||
|
index=True,
|
||||||
|
nullable=False,
|
||||||
)
|
)
|
||||||
role = relationship(
|
role = relationship(
|
||||||
"ApplicationRole",
|
"ApplicationRole",
|
||||||
|
@ -46,7 +46,9 @@ class ApplicationRole(
|
|||||||
UUID(as_uuid=True), ForeignKey("users.id"), index=True, nullable=True
|
UUID(as_uuid=True), ForeignKey("users.id"), index=True, nullable=True
|
||||||
)
|
)
|
||||||
|
|
||||||
status = Column(SQLAEnum(Status, native_enum=False), default=Status.PENDING)
|
status = Column(
|
||||||
|
SQLAEnum(Status, native_enum=False), default=Status.PENDING, nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
permission_sets = relationship(
|
permission_sets = relationship(
|
||||||
"PermissionSet", secondary=application_roles_permission_sets
|
"PermissionSet", secondary=application_roles_permission_sets
|
||||||
|
@ -23,12 +23,12 @@ class CLIN(Base, mixins.TimestampsMixin):
|
|||||||
task_order_id = Column(ForeignKey("task_orders.id"), nullable=False)
|
task_order_id = Column(ForeignKey("task_orders.id"), nullable=False)
|
||||||
task_order = relationship("TaskOrder")
|
task_order = relationship("TaskOrder")
|
||||||
|
|
||||||
number = Column(String, nullable=True)
|
number = Column(String, nullable=False)
|
||||||
start_date = Column(Date, nullable=True)
|
start_date = Column(Date, nullable=False)
|
||||||
end_date = Column(Date, nullable=True)
|
end_date = Column(Date, nullable=False)
|
||||||
total_amount = Column(Numeric(scale=2), nullable=True)
|
total_amount = Column(Numeric(scale=2), nullable=False)
|
||||||
obligated_amount = Column(Numeric(scale=2), nullable=True)
|
obligated_amount = Column(Numeric(scale=2), nullable=False)
|
||||||
jedi_clin_type = Column(SQLAEnum(JEDICLINType, native_enum=False), nullable=True)
|
jedi_clin_type = Column(SQLAEnum(JEDICLINType, native_enum=False), nullable=False)
|
||||||
|
|
||||||
#
|
#
|
||||||
# NOTE: For now obligated CLINS are CLIN 1 + CLIN 3
|
# NOTE: For now obligated CLINS are CLIN 1 + CLIN 3
|
||||||
|
@ -43,7 +43,9 @@ class EnvironmentRole(
|
|||||||
COMPLETED = "completed"
|
COMPLETED = "completed"
|
||||||
DISABLED = "disabled"
|
DISABLED = "disabled"
|
||||||
|
|
||||||
status = Column(SQLAEnum(Status, native_enum=False), default=Status.PENDING)
|
status = Column(
|
||||||
|
SQLAEnum(Status, native_enum=False), default=Status.PENDING, nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<EnvironmentRole(role='{}', user='{}', environment='{}', id='{}')>".format(
|
return "<EnvironmentRole(role='{}', user='{}', environment='{}', id='{}')>".format(
|
||||||
|
@ -31,23 +31,29 @@ class InvitesMixin(object):
|
|||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def inviter_id(cls):
|
def inviter_id(cls):
|
||||||
return Column(UUID(as_uuid=True), ForeignKey("users.id"), index=True)
|
return Column(
|
||||||
|
UUID(as_uuid=True), ForeignKey("users.id"), index=True, nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def inviter(cls):
|
def inviter(cls):
|
||||||
return relationship("User", foreign_keys=[cls.inviter_id])
|
return relationship("User", foreign_keys=[cls.inviter_id])
|
||||||
|
|
||||||
status = Column(SQLAEnum(Status, native_enum=False, default=Status.PENDING))
|
status = Column(
|
||||||
|
SQLAEnum(Status, native_enum=False, default=Status.PENDING, nullable=False)
|
||||||
|
)
|
||||||
|
|
||||||
expiration_time = Column(TIMESTAMP(timezone=True))
|
expiration_time = Column(TIMESTAMP(timezone=True), nullable=False)
|
||||||
|
|
||||||
token = Column(String, index=True, default=lambda: secrets.token_urlsafe())
|
token = Column(
|
||||||
|
String, index=True, default=lambda: secrets.token_urlsafe(), nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
email = Column(String, nullable=False)
|
email = Column(String, nullable=False)
|
||||||
|
|
||||||
dod_id = Column(String)
|
dod_id = Column(String, nullable=False)
|
||||||
first_name = Column(String)
|
first_name = Column(String, nullable=False)
|
||||||
last_name = Column(String)
|
last_name = Column(String, nullable=False)
|
||||||
phone_number = Column(String)
|
phone_number = Column(String)
|
||||||
phone_ext = Column(String)
|
phone_ext = Column(String)
|
||||||
|
|
||||||
|
@ -18,8 +18,10 @@ class Portfolio(
|
|||||||
__tablename__ = "portfolios"
|
__tablename__ = "portfolios"
|
||||||
|
|
||||||
id = types.Id()
|
id = types.Id()
|
||||||
name = Column(String)
|
name = Column(String, nullable=False)
|
||||||
defense_component = Column(String) # Department of Defense Component
|
defense_component = Column(
|
||||||
|
String, nullable=False
|
||||||
|
) # Department of Defense Component
|
||||||
|
|
||||||
app_migration = Column(String) # App Migration
|
app_migration = Column(String) # App Migration
|
||||||
complexity = Column(ARRAY(String)) # Application Complexity
|
complexity = Column(ARRAY(String)) # Application Complexity
|
||||||
|
@ -12,7 +12,7 @@ class PortfolioInvitation(
|
|||||||
__tablename__ = "portfolio_invitations"
|
__tablename__ = "portfolio_invitations"
|
||||||
|
|
||||||
portfolio_role_id = Column(
|
portfolio_role_id = Column(
|
||||||
UUID(as_uuid=True), ForeignKey("portfolio_roles.id"), index=True
|
UUID(as_uuid=True), ForeignKey("portfolio_roles.id"), index=True, nullable=False
|
||||||
)
|
)
|
||||||
role = relationship(
|
role = relationship(
|
||||||
"PortfolioRole",
|
"PortfolioRole",
|
||||||
|
@ -52,7 +52,9 @@ class PortfolioRole(
|
|||||||
UUID(as_uuid=True), ForeignKey("users.id"), index=True, nullable=True
|
UUID(as_uuid=True), ForeignKey("users.id"), index=True, nullable=True
|
||||||
)
|
)
|
||||||
|
|
||||||
status = Column(SQLAEnum(Status, native_enum=False), default=Status.PENDING)
|
status = Column(
|
||||||
|
SQLAEnum(Status, native_enum=False), default=Status.PENDING, nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
permission_sets = relationship(
|
permission_sets = relationship(
|
||||||
"PermissionSet", secondary=portfolio_roles_permission_sets
|
"PermissionSet", secondary=portfolio_roles_permission_sets
|
||||||
|
@ -33,12 +33,9 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
|||||||
|
|
||||||
id = types.Id()
|
id = types.Id()
|
||||||
|
|
||||||
portfolio_id = Column(ForeignKey("portfolios.id"))
|
portfolio_id = Column(ForeignKey("portfolios.id"), nullable=False)
|
||||||
portfolio = relationship("Portfolio")
|
portfolio = relationship("Portfolio")
|
||||||
|
|
||||||
user_id = Column(ForeignKey("users.id"))
|
|
||||||
creator = relationship("User", foreign_keys="TaskOrder.user_id")
|
|
||||||
|
|
||||||
pdf_attachment_id = Column(ForeignKey("attachments.id"))
|
pdf_attachment_id = Column(ForeignKey("attachments.id"))
|
||||||
_pdf = relationship("Attachment", foreign_keys=[pdf_attachment_id])
|
_pdf = relationship("Attachment", foreign_keys=[pdf_attachment_id])
|
||||||
number = Column(String) # Task Order Number
|
number = Column(String) # Task Order Number
|
||||||
|
@ -56,8 +56,8 @@ class User(
|
|||||||
|
|
||||||
email = Column(String)
|
email = Column(String)
|
||||||
dod_id = Column(String, unique=True, nullable=False)
|
dod_id = Column(String, unique=True, nullable=False)
|
||||||
first_name = Column(String)
|
first_name = Column(String, nullable=False)
|
||||||
last_name = Column(String)
|
last_name = Column(String, nullable=False)
|
||||||
phone_number = Column(String)
|
phone_number = Column(String)
|
||||||
phone_ext = Column(String)
|
phone_ext = Column(String)
|
||||||
service_branch = Column(String)
|
service_branch = Column(String)
|
||||||
|
@ -66,7 +66,7 @@ def update_task_order(
|
|||||||
task_order = TaskOrders.update(task_order_id, **form.data)
|
task_order = TaskOrders.update(task_order_id, **form.data)
|
||||||
portfolio_id = task_order.portfolio_id
|
portfolio_id = task_order.portfolio_id
|
||||||
else:
|
else:
|
||||||
task_order = TaskOrders.create(g.current_user, portfolio_id, **form.data)
|
task_order = TaskOrders.create(portfolio_id, **form.data)
|
||||||
|
|
||||||
return redirect(url_for(next_page, task_order_id=task_order.id))
|
return redirect(url_for(next_page, task_order_id=task_order.id))
|
||||||
else:
|
else:
|
||||||
@ -181,9 +181,7 @@ def cancel_edit(task_order_id=None, portfolio_id=None):
|
|||||||
if task_order_id:
|
if task_order_id:
|
||||||
task_order = TaskOrders.update(task_order_id, **form.data)
|
task_order = TaskOrders.update(task_order_id, **form.data)
|
||||||
else:
|
else:
|
||||||
task_order = TaskOrders.create(
|
task_order = TaskOrders.create(portfolio_id, **form.data)
|
||||||
g.current_user, portfolio_id, **form.data
|
|
||||||
)
|
|
||||||
elif not save and task_order_id:
|
elif not save and task_order_id:
|
||||||
TaskOrders.delete(task_order_id)
|
TaskOrders.delete(task_order_id)
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ The production configuration (azure.atat.code.mil, currently) is reflected in th
|
|||||||
- AUTH_DOMAIN: The host domain for the authentication endpoint for the environment.
|
- AUTH_DOMAIN: The host domain for the authentication endpoint for the environment.
|
||||||
- KV_MI_ID: the fully qualified id (path) of the managed identity for the key vault (instructions on retrieving this are down in section on [Setting up FlexVol](#configuring-the-identity)). Example: /subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/RESOURCE_GROUP_NAME/providers/Microsoft.ManagedIdentity/userAssignedIdentities/MANAGED_IDENTITY_NAME
|
- KV_MI_ID: the fully qualified id (path) of the managed identity for the key vault (instructions on retrieving this are down in section on [Setting up FlexVol](#configuring-the-identity)). Example: /subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/RESOURCE_GROUP_NAME/providers/Microsoft.ManagedIdentity/userAssignedIdentities/MANAGED_IDENTITY_NAME
|
||||||
- KV_MI_CLIENT_ID: The client id of the managed identity for the key vault. This is a GUID.
|
- KV_MI_CLIENT_ID: The client id of the managed identity for the key vault. This is a GUID.
|
||||||
|
- TENANT_ID: The id of the active directory tenant in which the cluster and it's associated users exist. This is a GUID.
|
||||||
|
|
||||||
We use envsubst to substitute values for these variables. There is a wrapper script (script/k8s_config) that will output the compiled configuration, using a combination of kustomize and envsubst.
|
We use envsubst to substitute values for these variables. There is a wrapper script (script/k8s_config) that will output the compiled configuration, using a combination of kustomize and envsubst.
|
||||||
|
|
||||||
@ -169,6 +170,12 @@ Then:
|
|||||||
kubectl -n atat create secret tls azure-atat-code-mil-tls --key="[path to the private key]" --cert="[path to the full chain]"
|
kubectl -n atat create secret tls azure-atat-code-mil-tls --key="[path to the private key]" --cert="[path to the full chain]"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Create the Diffie-Hellman parameters
|
||||||
|
|
||||||
|
Diffie-Hellman parameters allow per-session encryption of SSL traffic to help improve security. We currently store our parameters in KeyVault, the value can be updated using the following command. Note: Generating the new paramter can take over 10 minutes and there won't be any output while it's running.
|
||||||
|
```
|
||||||
|
az keyvault secret set --vault-name <VAULT NAME> --name <NAME OF PARAM> --value "$(openssl genpkey -genparam -algorithm DH -outform pem -pkeyopt dh_paramgen_prime_len:4096 2> /dev/null)"
|
||||||
|
```
|
||||||
---
|
---
|
||||||
|
|
||||||
# Setting Up FlexVol for Secrets
|
# Setting Up FlexVol for Secrets
|
||||||
@ -217,3 +224,45 @@ Example values:
|
|||||||
|
|
||||||
5. The file `deploy/azure/aadpodidentity.yml` is templated via Kustomize, so you'll need to include clientId (as `KV_MI_CLIENT_ID`) and id (as `KV_MI_ID`) of the managed identity as part of the call to Kustomize.
|
5. The file `deploy/azure/aadpodidentity.yml` is templated via Kustomize, so you'll need to include clientId (as `KV_MI_CLIENT_ID`) and id (as `KV_MI_ID`) of the managed identity as part of the call to Kustomize.
|
||||||
|
|
||||||
|
## Using the FlexVol
|
||||||
|
|
||||||
|
There are 3 steps to using the FlexVol to access secrets from KeyVault
|
||||||
|
|
||||||
|
1. For the resource in which you would like to mount a FlexVol, add a metadata label with the selector from `aadpodidentity.yml`
|
||||||
|
```
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: atst
|
||||||
|
role: web
|
||||||
|
aadpodidbinding: atat-kv-id-binding
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Register the FlexVol as a mount and specifiy which secrets you want to mount, along with the file name they should have. The `keyvaultobjectnames`, `keyvaultobjectaliases`, and `keyvaultobjecttypes` correspond to one another, positionally. They are passed as semicolon delimited strings, examples below.
|
||||||
|
|
||||||
|
```
|
||||||
|
- name: volume-of-secrets
|
||||||
|
flexVolume:
|
||||||
|
driver: "azure/kv"
|
||||||
|
options:
|
||||||
|
usepodidentity: "true"
|
||||||
|
keyvaultname: "<NAME OF KEY VAULT>"
|
||||||
|
keyvaultobjectnames: "mysecret;mykey;mycert"
|
||||||
|
keyvaultobjectaliases: "mysecret.pem;mykey.txt;mycert.crt"
|
||||||
|
keyvaultobjecttypes: "secret;key;cert"
|
||||||
|
tenantid: $TENANT_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Tell the resource where to mount your new volume, using the same name that you specified for the volume above.
|
||||||
|
```
|
||||||
|
- name: nginx-secret
|
||||||
|
mountPath: "/usr/secrets/"
|
||||||
|
readOnly: true
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Once applied, the directory specified in the `mountPath` argument will contain the files you specified in the flexVolume. In our case, you would be able to do this:
|
||||||
|
```
|
||||||
|
$ kubectl exec -it CONTAINER_NAME -c atst ls /usr/secrets
|
||||||
|
mycert.crt
|
||||||
|
mykey.txt
|
||||||
|
mysecret.pem
|
||||||
|
```
|
||||||
|
@ -5,8 +5,10 @@ metadata:
|
|||||||
name: atst-nginx
|
name: atst-nginx
|
||||||
namespace: atat
|
namespace: atat
|
||||||
data:
|
data:
|
||||||
nginx-config: |-
|
atst.conf: |-
|
||||||
server {
|
server {
|
||||||
|
access_log /var/log/nginx/access.log json;
|
||||||
|
|
||||||
listen ${PORT_PREFIX}342;
|
listen ${PORT_PREFIX}342;
|
||||||
server_name ${MAIN_DOMAIN};
|
server_name ${MAIN_DOMAIN};
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
@ -18,6 +20,8 @@ data:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
server {
|
server {
|
||||||
|
access_log /var/log/nginx/access.log json;
|
||||||
|
|
||||||
listen ${PORT_PREFIX}343;
|
listen ${PORT_PREFIX}343;
|
||||||
server_name ${AUTH_DOMAIN};
|
server_name ${AUTH_DOMAIN};
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
@ -29,12 +33,17 @@ data:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
server {
|
server {
|
||||||
|
access_log /var/log/nginx/access.log json;
|
||||||
|
|
||||||
server_name ${MAIN_DOMAIN};
|
server_name ${MAIN_DOMAIN};
|
||||||
# access_log /var/log/nginx/access.log json;
|
# access_log /var/log/nginx/access.log json;
|
||||||
listen ${PORT_PREFIX}442 ssl;
|
listen ${PORT_PREFIX}442 ssl;
|
||||||
listen [::]:${PORT_PREFIX}442 ssl ipv6only=on;
|
listen [::]:${PORT_PREFIX}442 ssl ipv6only=on;
|
||||||
ssl_certificate /etc/ssl/private/atat.crt;
|
ssl_certificate /etc/ssl/atat.crt;
|
||||||
ssl_certificate_key /etc/ssl/private/atat.key;
|
ssl_certificate_key /etc/ssl/atat.key;
|
||||||
|
# additional SSL/TLS settings
|
||||||
|
include /etc/nginx/snippets/ssl.conf;
|
||||||
|
|
||||||
location /login-redirect {
|
location /login-redirect {
|
||||||
return 301 https://auth-azure.atat.code.mil$request_uri;
|
return 301 https://auth-azure.atat.code.mil$request_uri;
|
||||||
}
|
}
|
||||||
@ -58,18 +67,20 @@ data:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
server {
|
server {
|
||||||
# access_log /var/log/nginx/access.log json;
|
access_log /var/log/nginx/access.log json;
|
||||||
|
|
||||||
server_name ${AUTH_DOMAIN};
|
server_name ${AUTH_DOMAIN};
|
||||||
listen ${PORT_PREFIX}443 ssl;
|
listen ${PORT_PREFIX}443 ssl;
|
||||||
listen [::]:${PORT_PREFIX}443 ssl ipv6only=on;
|
listen [::]:${PORT_PREFIX}443 ssl ipv6only=on;
|
||||||
ssl_certificate /etc/ssl/private/atat.crt;
|
ssl_certificate /etc/ssl/atat.crt;
|
||||||
ssl_certificate_key /etc/ssl/private/atat.key;
|
ssl_certificate_key /etc/ssl/atat.key;
|
||||||
# Request and validate client certificate
|
# Request and validate client certificate
|
||||||
ssl_verify_client on;
|
ssl_verify_client on;
|
||||||
ssl_verify_depth 10;
|
ssl_verify_depth 10;
|
||||||
ssl_client_certificate /etc/ssl/client-ca-bundle.pem;
|
ssl_client_certificate /etc/ssl/client-ca-bundle.pem;
|
||||||
# Guard against HTTPS -> HTTP downgrade
|
# additional SSL/TLS settings
|
||||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; always";
|
include /etc/nginx/snippets/ssl.conf;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
return 301 https://azure.atat.code.mil$request_uri;
|
return 301 https://azure.atat.code.mil$request_uri;
|
||||||
}
|
}
|
||||||
@ -88,3 +99,18 @@ data:
|
|||||||
uwsgi_param HTTP_X_REQUEST_ID $request_id;
|
uwsgi_param HTTP_X_REQUEST_ID $request_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
00json_log.conf: |-
|
||||||
|
log_format json escape=json
|
||||||
|
'{'
|
||||||
|
'"timestamp":"$time_iso8601",'
|
||||||
|
'"msec":"$msec",'
|
||||||
|
'"request_id":"$request_id",'
|
||||||
|
'"remote_addr":"$remote_addr",'
|
||||||
|
'"remote_user":"$remote_user",'
|
||||||
|
'"request":"$request",'
|
||||||
|
'"status":$status,'
|
||||||
|
'"body_bytes_sent":$body_bytes_sent,'
|
||||||
|
'"referer":"$http_referer",'
|
||||||
|
'"user_agent":"$http_user_agent",'
|
||||||
|
'"http_x_forwarded_for":"$http_x_forwarded_for"'
|
||||||
|
'}';
|
||||||
|
@ -23,6 +23,7 @@ spec:
|
|||||||
labels:
|
labels:
|
||||||
app: atst
|
app: atst
|
||||||
role: web
|
role: web
|
||||||
|
aadpodidbinding: atat-kv-id-binding
|
||||||
spec:
|
spec:
|
||||||
securityContext:
|
securityContext:
|
||||||
fsGroup: 101
|
fsGroup: 101
|
||||||
@ -62,19 +63,21 @@ spec:
|
|||||||
name: auth
|
name: auth
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: nginx-config
|
- name: nginx-config
|
||||||
mountPath: "/etc/nginx/conf.d/atst.conf"
|
mountPath: "/etc/nginx/conf.d/"
|
||||||
subPath: atst.conf
|
|
||||||
- name: uwsgi-socket-dir
|
- name: uwsgi-socket-dir
|
||||||
mountPath: "/var/run/uwsgi"
|
mountPath: "/var/run/uwsgi"
|
||||||
- name: nginx-htpasswd
|
- name: nginx-htpasswd
|
||||||
mountPath: "/etc/nginx/.htpasswd"
|
mountPath: "/etc/nginx/.htpasswd"
|
||||||
subPath: .htpasswd
|
subPath: .htpasswd
|
||||||
- name: tls
|
|
||||||
mountPath: "/etc/ssl/private"
|
|
||||||
- name: nginx-client-ca-bundle
|
- name: nginx-client-ca-bundle
|
||||||
mountPath: "/etc/ssl/"
|
mountPath: "/etc/ssl/client-ca-bundle.pem"
|
||||||
|
subPath: "client-ca-bundle.pem"
|
||||||
- name: acme
|
- name: acme
|
||||||
mountPath: "/usr/share/nginx/html/.well-known/acme-challenge/"
|
mountPath: "/usr/share/nginx/html/.well-known/acme-challenge/"
|
||||||
|
- name: snippets
|
||||||
|
mountPath: "/etc/nginx/snippets/"
|
||||||
|
- name: nginx-secret
|
||||||
|
mountPath: "/etc/ssl/"
|
||||||
volumes:
|
volumes:
|
||||||
- name: atst-config
|
- name: atst-config
|
||||||
secret:
|
secret:
|
||||||
@ -86,13 +89,13 @@ spec:
|
|||||||
- name: nginx-client-ca-bundle
|
- name: nginx-client-ca-bundle
|
||||||
configMap:
|
configMap:
|
||||||
name: nginx-client-ca-bundle
|
name: nginx-client-ca-bundle
|
||||||
defaultMode: 0666
|
defaultMode: 0444
|
||||||
|
items:
|
||||||
|
- key: "client-ca-bundle.pem"
|
||||||
|
path: "client-ca-bundle.pem"
|
||||||
- name: nginx-config
|
- name: nginx-config
|
||||||
configMap:
|
configMap:
|
||||||
name: atst-nginx
|
name: atst-nginx
|
||||||
items:
|
|
||||||
- key: nginx-config
|
|
||||||
path: atst.conf
|
|
||||||
- name: uwsgi-socket-dir
|
- name: uwsgi-socket-dir
|
||||||
emptyDir:
|
emptyDir:
|
||||||
medium: Memory
|
medium: Memory
|
||||||
@ -103,16 +106,6 @@ spec:
|
|||||||
- key: htpasswd
|
- key: htpasswd
|
||||||
path: .htpasswd
|
path: .htpasswd
|
||||||
mode: 0640
|
mode: 0640
|
||||||
- name: tls
|
|
||||||
secret:
|
|
||||||
secretName: azure-atat-code-mil-tls
|
|
||||||
items:
|
|
||||||
- key: tls.crt
|
|
||||||
path: atat.crt
|
|
||||||
mode: 0644
|
|
||||||
- key: tls.key
|
|
||||||
path: atat.key
|
|
||||||
mode: 0640
|
|
||||||
- name: crls-vol
|
- name: crls-vol
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: crls-vol-claim
|
claimName: crls-vol-claim
|
||||||
@ -135,6 +128,19 @@ spec:
|
|||||||
- key: uwsgi.ini
|
- key: uwsgi.ini
|
||||||
path: uwsgi.ini
|
path: uwsgi.ini
|
||||||
mode: 0644
|
mode: 0644
|
||||||
|
- name: snippets
|
||||||
|
configMap:
|
||||||
|
name: nginx-snippets
|
||||||
|
- name: nginx-secret
|
||||||
|
flexVolume:
|
||||||
|
driver: "azure/kv"
|
||||||
|
options:
|
||||||
|
usepodidentity: "true"
|
||||||
|
keyvaultname: "atat-vault-test"
|
||||||
|
keyvaultobjectnames: "dhparam4096;master-cert;master-cert"
|
||||||
|
keyvaultobjectaliases: "dhparam.pem;atat.key;atat.crt"
|
||||||
|
keyvaultobjecttypes: "secret;secret;secret"
|
||||||
|
tenantid: $TENANT_ID
|
||||||
---
|
---
|
||||||
apiVersion: extensions/v1beta1
|
apiVersion: extensions/v1beta1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@ -161,13 +167,14 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- name: atst-worker
|
- name: atst-worker
|
||||||
image: $CONTAINER_IMAGE
|
image: $CONTAINER_IMAGE
|
||||||
args: [
|
args:
|
||||||
|
[
|
||||||
"/opt/atat/atst/.venv/bin/python",
|
"/opt/atat/atst/.venv/bin/python",
|
||||||
"/opt/atat/atst/.venv/bin/celery",
|
"/opt/atat/atst/.venv/bin/celery",
|
||||||
"-A",
|
"-A",
|
||||||
"celery_worker.celery",
|
"celery_worker.celery",
|
||||||
"worker",
|
"worker",
|
||||||
"--loglevel=info"
|
"--loglevel=info",
|
||||||
]
|
]
|
||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
@ -222,13 +229,14 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- name: atst-beat
|
- name: atst-beat
|
||||||
image: $CONTAINER_IMAGE
|
image: $CONTAINER_IMAGE
|
||||||
args: [
|
args:
|
||||||
|
[
|
||||||
"/opt/atat/atst/.venv/bin/python",
|
"/opt/atat/atst/.venv/bin/python",
|
||||||
"/opt/atat/atst/.venv/bin/celery",
|
"/opt/atat/atst/.venv/bin/celery",
|
||||||
"-A",
|
"-A",
|
||||||
"celery_worker.celery",
|
"celery_worker.celery",
|
||||||
"beat",
|
"beat",
|
||||||
"--loglevel=info"
|
"--loglevel=info",
|
||||||
]
|
]
|
||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
|
@ -11,3 +11,4 @@ resources:
|
|||||||
- nginx-client-ca-bundle.yml
|
- nginx-client-ca-bundle.yml
|
||||||
- acme-challenges.yml
|
- acme-challenges.yml
|
||||||
- aadpodidentity.yml
|
- aadpodidentity.yml
|
||||||
|
- nginx-snippets.yml
|
||||||
|
24
deploy/azure/nginx-snippets.yml
Normal file
24
deploy/azure/nginx-snippets.yml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: nginx-snippets
|
||||||
|
namespace: atat
|
||||||
|
data:
|
||||||
|
ssl.conf: |-
|
||||||
|
# Guard against HTTPS -> HTTP downgrade
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; always";
|
||||||
|
# Set SSL protocols, ciphers, and related options
|
||||||
|
ssl_protocols TLSv1.3 TLSv1.2;
|
||||||
|
ssl_ciphers TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_ecdh_curve X25519:prime256v1:secp384r1;
|
||||||
|
ssl_dhparam /etc/ssl/dhparam.pem;
|
||||||
|
# SSL session options
|
||||||
|
ssl_session_timeout 4h;
|
||||||
|
ssl_session_cache shared:SSL:10m; # 1mb = ~4000 sessions
|
||||||
|
ssl_session_tickets off;
|
||||||
|
# OCSP Stapling
|
||||||
|
ssl_stapling on;
|
||||||
|
ssl_stapling_verify on;
|
||||||
|
resolver 8.8.8.8 8.8.4.4;
|
13
deploy/overlays/staging/flex_vol.yml
Normal file
13
deploy/overlays/staging/flex_vol.yml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: atst
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
volumes:
|
||||||
|
- name: nginx-secret
|
||||||
|
flexVolume:
|
||||||
|
options:
|
||||||
|
keyvaultname: "atat-vault-test"
|
||||||
|
keyvaultobjectnames: "dhparam4096;staging-cert;staging-cert"
|
@ -7,6 +7,7 @@ patchesStrategicMerge:
|
|||||||
- replica_count.yml
|
- replica_count.yml
|
||||||
- ports.yml
|
- ports.yml
|
||||||
- envvars.yml
|
- envvars.yml
|
||||||
|
- flex_vol.yml
|
||||||
patchesJson6902:
|
patchesJson6902:
|
||||||
- target:
|
- target:
|
||||||
group: extensions
|
group: extensions
|
||||||
|
@ -13,6 +13,7 @@ SETTINGS=(
|
|||||||
AUTH_DOMAIN
|
AUTH_DOMAIN
|
||||||
KV_MI_ID
|
KV_MI_ID
|
||||||
KV_MI_CLIENT_ID
|
KV_MI_CLIENT_ID
|
||||||
|
TENANT_ID
|
||||||
)
|
)
|
||||||
|
|
||||||
# Loop all expected settings. Track ones that are missing and build
|
# Loop all expected settings. Track ones that are missing and build
|
||||||
|
@ -30,6 +30,8 @@ from atst.domain.users import Users
|
|||||||
|
|
||||||
from atst.routes.dev import _DEV_USERS as DEV_USERS
|
from atst.routes.dev import _DEV_USERS as DEV_USERS
|
||||||
|
|
||||||
|
from atst.utils import pick
|
||||||
|
|
||||||
from tests.factories import (
|
from tests.factories import (
|
||||||
random_service_branch,
|
random_service_branch,
|
||||||
TaskOrderFactory,
|
TaskOrderFactory,
|
||||||
@ -238,6 +240,7 @@ def add_applications_to_portfolio(portfolio):
|
|||||||
None,
|
None,
|
||||||
first_name=user_data["first_name"],
|
first_name=user_data["first_name"],
|
||||||
last_name=user_data["last_name"],
|
last_name=user_data["last_name"],
|
||||||
|
email=user_data["email"],
|
||||||
)
|
)
|
||||||
|
|
||||||
app_role = ApplicationRoles.create(
|
app_role = ApplicationRoles.create(
|
||||||
@ -263,7 +266,23 @@ def add_applications_to_portfolio(portfolio):
|
|||||||
|
|
||||||
def create_demo_portfolio(name, data):
|
def create_demo_portfolio(name, data):
|
||||||
try:
|
try:
|
||||||
portfolio_owner = Users.get_or_create_by_dod_id("2345678901") # Amanda
|
portfolio_owner = Users.get_or_create_by_dod_id(
|
||||||
|
"2345678901",
|
||||||
|
**pick(
|
||||||
|
[
|
||||||
|
"permission_sets",
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"email",
|
||||||
|
"service_branch",
|
||||||
|
"phone_number",
|
||||||
|
"citizenship",
|
||||||
|
"designation",
|
||||||
|
"date_latest_training",
|
||||||
|
],
|
||||||
|
DEV_USERS["amanda"],
|
||||||
|
),
|
||||||
|
) # Amanda
|
||||||
# auditor = Users.get_by_dod_id("3453453453") # Sally
|
# auditor = Users.get_by_dod_id("3453453453") # Sally
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
print(
|
print(
|
||||||
|
@ -9,6 +9,7 @@ from atst.domain.portfolios import (
|
|||||||
)
|
)
|
||||||
from atst.domain.portfolio_roles import PortfolioRoles
|
from atst.domain.portfolio_roles import PortfolioRoles
|
||||||
from atst.domain.applications import Applications
|
from atst.domain.applications import Applications
|
||||||
|
from atst.domain.application_roles import ApplicationRoles
|
||||||
from atst.domain.environments import Environments
|
from atst.domain.environments import Environments
|
||||||
from atst.domain.permission_sets import PermissionSets, PORTFOLIO_PERMISSION_SETS
|
from atst.domain.permission_sets import PermissionSets, PORTFOLIO_PERMISSION_SETS
|
||||||
from atst.models.application_role import Status as ApplicationRoleStatus
|
from atst.models.application_role import Status as ApplicationRoleStatus
|
||||||
|
@ -96,7 +96,6 @@ def test_create_adds_clins():
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
task_order = TaskOrders.create(
|
task_order = TaskOrders.create(
|
||||||
creator=portfolio.owner,
|
|
||||||
portfolio_id=portfolio.id,
|
portfolio_id=portfolio.id,
|
||||||
number="0123456789",
|
number="0123456789",
|
||||||
clins=clins,
|
clins=clins,
|
||||||
@ -127,7 +126,6 @@ def test_update_adds_clins():
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
task_order = TaskOrders.create(
|
task_order = TaskOrders.create(
|
||||||
creator=task_order.creator,
|
|
||||||
portfolio_id=task_order.portfolio_id,
|
portfolio_id=task_order.portfolio_id,
|
||||||
number="0000000000",
|
number="0000000000",
|
||||||
clins=clins,
|
clins=clins,
|
||||||
|
@ -4,36 +4,41 @@ from uuid import uuid4
|
|||||||
|
|
||||||
from atst.domain.users import Users
|
from atst.domain.users import Users
|
||||||
from atst.domain.exceptions import NotFoundError, AlreadyExistsError, UnauthorizedError
|
from atst.domain.exceptions import NotFoundError, AlreadyExistsError, UnauthorizedError
|
||||||
|
from atst.utils import pick
|
||||||
|
|
||||||
from tests.factories import UserFactory
|
from tests.factories import UserFactory
|
||||||
|
|
||||||
DOD_ID = "my_dod_id"
|
DOD_ID = "my_dod_id"
|
||||||
|
REQUIRED_KWARGS = {"first_name": "Luke", "last_name": "Skywalker"}
|
||||||
|
|
||||||
|
|
||||||
def test_create_user():
|
def test_create_user():
|
||||||
user = Users.create(DOD_ID)
|
user = Users.create(DOD_ID, **REQUIRED_KWARGS)
|
||||||
assert user.dod_id == DOD_ID
|
assert user.dod_id == DOD_ID
|
||||||
|
|
||||||
|
|
||||||
def test_create_user_with_existing_email():
|
def test_create_user_with_existing_email():
|
||||||
Users.create(DOD_ID, email="thisusersemail@usersRus.com")
|
Users.create(DOD_ID, email="thisusersemail@usersRus.com", **REQUIRED_KWARGS)
|
||||||
with pytest.raises(AlreadyExistsError):
|
with pytest.raises(AlreadyExistsError):
|
||||||
Users.create(DOD_ID, email="thisusersemail@usersRus.com")
|
Users.create(DOD_ID, email="thisusersemail@usersRus.com")
|
||||||
|
|
||||||
|
|
||||||
def test_create_user_with_nonexistent_permission_set():
|
def test_create_user_with_nonexistent_permission_set():
|
||||||
with pytest.raises(NotFoundError):
|
with pytest.raises(NotFoundError):
|
||||||
Users.create(DOD_ID, permission_sets=["nonexistent"])
|
Users.create(DOD_ID, permission_sets=["nonexistent"], **REQUIRED_KWARGS)
|
||||||
|
|
||||||
|
|
||||||
def test_get_or_create_nonexistent_user():
|
def test_get_or_create_nonexistent_user():
|
||||||
user = Users.get_or_create_by_dod_id(DOD_ID)
|
user = Users.get_or_create_by_dod_id(DOD_ID, **REQUIRED_KWARGS)
|
||||||
assert user.dod_id == DOD_ID
|
assert user.dod_id == DOD_ID
|
||||||
|
|
||||||
|
|
||||||
def test_get_or_create_existing_user():
|
def test_get_or_create_existing_user():
|
||||||
fact_user = UserFactory.create()
|
fact_user = UserFactory.create()
|
||||||
user = Users.get_or_create_by_dod_id(fact_user.dod_id)
|
user = Users.get_or_create_by_dod_id(
|
||||||
|
fact_user.dod_id,
|
||||||
|
**pick(["first_name", "last_name"], fact_user.to_dictionary()),
|
||||||
|
)
|
||||||
assert user == fact_user
|
assert user == fact_user
|
||||||
|
|
||||||
|
|
||||||
|
@ -236,9 +236,17 @@ class ApplicationRoleFactory(Base):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _create(cls, model_class, *args, **kwargs):
|
def _create(cls, model_class, *args, **kwargs):
|
||||||
with_invite = kwargs.pop("invite", True)
|
with_invite = kwargs.pop("invite", True)
|
||||||
app_role = super()._create(model_class, *args, **kwargs)
|
app_role = model_class(*args, **kwargs)
|
||||||
|
|
||||||
if with_invite:
|
if with_invite and app_role.user:
|
||||||
|
ApplicationInvitationFactory.create(
|
||||||
|
role=app_role,
|
||||||
|
dod_id=app_role.user.dod_id,
|
||||||
|
first_name=app_role.user.first_name,
|
||||||
|
last_name=app_role.user.last_name,
|
||||||
|
email=app_role.user.email,
|
||||||
|
)
|
||||||
|
elif with_invite:
|
||||||
ApplicationInvitationFactory.create(role=app_role)
|
ApplicationInvitationFactory.create(role=app_role)
|
||||||
|
|
||||||
return app_role
|
return app_role
|
||||||
@ -260,6 +268,14 @@ class PortfolioInvitationFactory(Base):
|
|||||||
email = factory.Faker("email")
|
email = factory.Faker("email")
|
||||||
status = InvitationStatus.PENDING
|
status = InvitationStatus.PENDING
|
||||||
expiration_time = PortfolioInvitations.current_expiration_time()
|
expiration_time = PortfolioInvitations.current_expiration_time()
|
||||||
|
dod_id = factory.LazyFunction(random_dod_id)
|
||||||
|
first_name = factory.Faker("first_name")
|
||||||
|
last_name = factory.Faker("last_name")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _create(cls, model_class, *args, **kwargs):
|
||||||
|
inviter_id = kwargs.pop("inviter_id", UserFactory.create().id)
|
||||||
|
return super()._create(model_class, inviter_id=inviter_id, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ApplicationInvitationFactory(Base):
|
class ApplicationInvitationFactory(Base):
|
||||||
@ -270,6 +286,14 @@ class ApplicationInvitationFactory(Base):
|
|||||||
status = InvitationStatus.PENDING
|
status = InvitationStatus.PENDING
|
||||||
expiration_time = PortfolioInvitations.current_expiration_time()
|
expiration_time = PortfolioInvitations.current_expiration_time()
|
||||||
role = factory.SubFactory(ApplicationRoleFactory, invite=False)
|
role = factory.SubFactory(ApplicationRoleFactory, invite=False)
|
||||||
|
dod_id = factory.LazyFunction(random_dod_id)
|
||||||
|
first_name = factory.Faker("first_name")
|
||||||
|
last_name = factory.Faker("last_name")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _create(cls, model_class, *args, **kwargs):
|
||||||
|
inviter_id = kwargs.pop("inviter_id", UserFactory.create().id)
|
||||||
|
return super()._create(model_class, inviter_id=inviter_id, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class AttachmentFactory(Base):
|
class AttachmentFactory(Base):
|
||||||
@ -284,11 +308,8 @@ class TaskOrderFactory(Base):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = TaskOrder
|
model = TaskOrder
|
||||||
|
|
||||||
portfolio = factory.SubFactory(
|
portfolio = factory.SubFactory(PortfolioFactory)
|
||||||
PortfolioFactory, owner=factory.SelfAttribute("..creator")
|
|
||||||
)
|
|
||||||
number = factory.LazyFunction(random_task_order_number)
|
number = factory.LazyFunction(random_task_order_number)
|
||||||
creator = factory.SubFactory(UserFactory)
|
|
||||||
signed_at = None
|
signed_at = None
|
||||||
_pdf = factory.SubFactory(AttachmentFactory)
|
_pdf = factory.SubFactory(AttachmentFactory)
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ class TestTaskOrderStatus:
|
|||||||
@patch("atst.models.TaskOrder.is_signed", new_callable=PropertyMock)
|
@patch("atst.models.TaskOrder.is_signed", new_callable=PropertyMock)
|
||||||
def test_draft_status(self, is_signed, is_completed):
|
def test_draft_status(self, is_signed, is_completed):
|
||||||
# Given that I have a TO that is neither completed nor signed
|
# Given that I have a TO that is neither completed nor signed
|
||||||
to = TaskOrder()
|
to = TaskOrderFactory.create()
|
||||||
is_signed.return_value = False
|
is_signed.return_value = False
|
||||||
is_completed.return_value = False
|
is_completed.return_value = False
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ class TestTaskOrderStatus:
|
|||||||
def test_active_status(self, is_signed, is_completed, start_date, end_date):
|
def test_active_status(self, is_signed, is_completed, start_date, end_date):
|
||||||
# Given that I have a signed TO and today is within its start_date and end_date
|
# Given that I have a signed TO and today is within its start_date and end_date
|
||||||
today = pendulum.today().date()
|
today = pendulum.today().date()
|
||||||
to = TaskOrder()
|
to = TaskOrderFactory.create()
|
||||||
|
|
||||||
start_date.return_value = today.subtract(days=1)
|
start_date.return_value = today.subtract(days=1)
|
||||||
end_date.return_value = today.add(days=1)
|
end_date.return_value = today.add(days=1)
|
||||||
@ -105,7 +105,7 @@ class TestTaskOrderStatus:
|
|||||||
@patch("atst.models.TaskOrder.is_signed", new_callable=PropertyMock)
|
@patch("atst.models.TaskOrder.is_signed", new_callable=PropertyMock)
|
||||||
def test_upcoming_status(self, is_signed, is_completed, start_date, end_date):
|
def test_upcoming_status(self, is_signed, is_completed, start_date, end_date):
|
||||||
# Given that I have a signed TO and today is before its start_date
|
# Given that I have a signed TO and today is before its start_date
|
||||||
to = TaskOrder()
|
to = TaskOrderFactory.create()
|
||||||
start_date.return_value = pendulum.today().add(days=1).date()
|
start_date.return_value = pendulum.today().add(days=1).date()
|
||||||
end_date.return_value = pendulum.today().add(days=2).date()
|
end_date.return_value = pendulum.today().add(days=2).date()
|
||||||
is_signed.return_value = True
|
is_signed.return_value = True
|
||||||
@ -120,7 +120,7 @@ class TestTaskOrderStatus:
|
|||||||
@patch("atst.models.TaskOrder.is_signed", new_callable=PropertyMock)
|
@patch("atst.models.TaskOrder.is_signed", new_callable=PropertyMock)
|
||||||
def test_expired_status(self, is_signed, is_completed, end_date, start_date):
|
def test_expired_status(self, is_signed, is_completed, end_date, start_date):
|
||||||
# Given that I have a signed TO and today is after its expiration date
|
# Given that I have a signed TO and today is after its expiration date
|
||||||
to = TaskOrder()
|
to = TaskOrderFactory.create()
|
||||||
end_date.return_value = pendulum.today().subtract(days=1).date()
|
end_date.return_value = pendulum.today().subtract(days=1).date()
|
||||||
start_date.return_value = pendulum.today().subtract(days=2).date()
|
start_date.return_value = pendulum.today().subtract(days=2).date()
|
||||||
is_signed.return_value = True
|
is_signed.return_value = True
|
||||||
@ -143,7 +143,7 @@ class TestTaskOrderStatus:
|
|||||||
|
|
||||||
class TestBudget:
|
class TestBudget:
|
||||||
def test_total_contract_amount(self):
|
def test_total_contract_amount(self):
|
||||||
to = TaskOrder()
|
to = TaskOrderFactory.create()
|
||||||
assert to.total_contract_amount == 0
|
assert to.total_contract_amount == 0
|
||||||
|
|
||||||
clin1 = CLINFactory(task_order=to, jedi_clin_type=JEDICLINType.JEDI_CLIN_1)
|
clin1 = CLINFactory(task_order=to, jedi_clin_type=JEDICLINType.JEDI_CLIN_1)
|
||||||
@ -156,7 +156,7 @@ class TestBudget:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_total_obligated_funds(self):
|
def test_total_obligated_funds(self):
|
||||||
to = TaskOrder()
|
to = TaskOrderFactory.create()
|
||||||
assert to.total_obligated_funds == 0
|
assert to.total_obligated_funds == 0
|
||||||
|
|
||||||
clin1 = CLINFactory(task_order=to, jedi_clin_type=JEDICLINType.JEDI_CLIN_1)
|
clin1 = CLINFactory(task_order=to, jedi_clin_type=JEDICLINType.JEDI_CLIN_1)
|
||||||
|
@ -30,7 +30,7 @@ def task_order():
|
|||||||
portfolio = PortfolioFactory.create(owner=user)
|
portfolio = PortfolioFactory.create(owner=user)
|
||||||
attachment = Attachment(filename="sample_attachment", object_name="sample")
|
attachment = Attachment(filename="sample_attachment", object_name="sample")
|
||||||
|
|
||||||
return TaskOrderFactory.create(creator=user, portfolio=portfolio)
|
return TaskOrderFactory.create(portfolio=portfolio)
|
||||||
|
|
||||||
|
|
||||||
def test_review_task_order_not_draft(client, user_session, task_order):
|
def test_review_task_order_not_draft(client, user_session, task_order):
|
||||||
|
@ -20,14 +20,13 @@ def task_order():
|
|||||||
user = UserFactory.create()
|
user = UserFactory.create()
|
||||||
portfolio = PortfolioFactory.create(owner=user)
|
portfolio = PortfolioFactory.create(owner=user)
|
||||||
|
|
||||||
return TaskOrderFactory.create(creator=user, portfolio=portfolio)
|
return TaskOrderFactory.create(portfolio=portfolio)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def completed_task_order():
|
def completed_task_order():
|
||||||
portfolio = PortfolioFactory.create()
|
portfolio = PortfolioFactory.create()
|
||||||
task_order = TaskOrderFactory.create(
|
task_order = TaskOrderFactory.create(
|
||||||
creator=portfolio.owner,
|
|
||||||
portfolio=portfolio,
|
portfolio=portfolio,
|
||||||
create_clins=[{"number": "1234567890123456789012345678901234567890123"}],
|
create_clins=[{"number": "1234567890123456789012345678901234567890123"}],
|
||||||
)
|
)
|
||||||
@ -68,7 +67,7 @@ def test_task_orders_submit_form_step_one_add_pdf(client, user_session, portfoli
|
|||||||
def test_task_orders_form_step_one_add_pdf_existing_to(
|
def test_task_orders_form_step_one_add_pdf_existing_to(
|
||||||
client, user_session, task_order
|
client, user_session, task_order
|
||||||
):
|
):
|
||||||
user_session(task_order.creator)
|
user_session(task_order.portfolio.owner)
|
||||||
response = client.get(
|
response = client.get(
|
||||||
url_for("task_orders.form_step_one_add_pdf", task_order_id=task_order.id)
|
url_for("task_orders.form_step_one_add_pdf", task_order_id=task_order.id)
|
||||||
)
|
)
|
||||||
@ -77,7 +76,7 @@ def test_task_orders_form_step_one_add_pdf_existing_to(
|
|||||||
|
|
||||||
def test_task_orders_submit_form_step_one_add_pdf_existing_to(client, user_session):
|
def test_task_orders_submit_form_step_one_add_pdf_existing_to(client, user_session):
|
||||||
task_order = TaskOrderFactory.create()
|
task_order = TaskOrderFactory.create()
|
||||||
user_session(task_order.creator)
|
user_session(task_order.portfolio.owner)
|
||||||
response = client.post(
|
response = client.post(
|
||||||
url_for(
|
url_for(
|
||||||
"task_orders.submit_form_step_one_add_pdf", task_order_id=task_order.id
|
"task_orders.submit_form_step_one_add_pdf", task_order_id=task_order.id
|
||||||
@ -140,7 +139,7 @@ def test_task_orders_submit_form_step_one_validates_object_name(
|
|||||||
|
|
||||||
|
|
||||||
def test_task_orders_form_step_two_add_number(client, user_session, task_order):
|
def test_task_orders_form_step_two_add_number(client, user_session, task_order):
|
||||||
user_session(task_order.creator)
|
user_session(task_order.portfolio.owner)
|
||||||
response = client.get(
|
response = client.get(
|
||||||
url_for("task_orders.form_step_two_add_number", task_order_id=task_order.id)
|
url_for("task_orders.form_step_two_add_number", task_order_id=task_order.id)
|
||||||
)
|
)
|
||||||
@ -148,7 +147,7 @@ def test_task_orders_form_step_two_add_number(client, user_session, task_order):
|
|||||||
|
|
||||||
|
|
||||||
def test_task_orders_submit_form_step_two_add_number(client, user_session, task_order):
|
def test_task_orders_submit_form_step_two_add_number(client, user_session, task_order):
|
||||||
user_session(task_order.creator)
|
user_session(task_order.portfolio.owner)
|
||||||
form_data = {"number": "1234567890"}
|
form_data = {"number": "1234567890"}
|
||||||
response = client.post(
|
response = client.post(
|
||||||
url_for(
|
url_for(
|
||||||
@ -164,7 +163,7 @@ def test_task_orders_submit_form_step_two_add_number(client, user_session, task_
|
|||||||
def test_task_orders_submit_form_step_two_add_number_existing_to(
|
def test_task_orders_submit_form_step_two_add_number_existing_to(
|
||||||
client, user_session, task_order
|
client, user_session, task_order
|
||||||
):
|
):
|
||||||
user_session(task_order.creator)
|
user_session(task_order.portfolio.owner)
|
||||||
form_data = {"number": "0000000000"}
|
form_data = {"number": "0000000000"}
|
||||||
original_number = task_order.number
|
original_number = task_order.number
|
||||||
response = client.post(
|
response = client.post(
|
||||||
@ -179,7 +178,7 @@ def test_task_orders_submit_form_step_two_add_number_existing_to(
|
|||||||
|
|
||||||
|
|
||||||
def test_task_orders_form_step_three_add_clins(client, user_session, task_order):
|
def test_task_orders_form_step_three_add_clins(client, user_session, task_order):
|
||||||
user_session(task_order.creator)
|
user_session(task_order.portfolio.owner)
|
||||||
response = client.get(
|
response = client.get(
|
||||||
url_for("task_orders.form_step_three_add_clins", task_order_id=task_order.id)
|
url_for("task_orders.form_step_three_add_clins", task_order_id=task_order.id)
|
||||||
)
|
)
|
||||||
@ -187,7 +186,7 @@ def test_task_orders_form_step_three_add_clins(client, user_session, task_order)
|
|||||||
|
|
||||||
|
|
||||||
def test_task_orders_submit_form_step_three_add_clins(client, user_session, task_order):
|
def test_task_orders_submit_form_step_three_add_clins(client, user_session, task_order):
|
||||||
user_session(task_order.creator)
|
user_session(task_order.portfolio.owner)
|
||||||
form_data = {
|
form_data = {
|
||||||
"clins-0-jedi_clin_type": "JEDI_CLIN_1",
|
"clins-0-jedi_clin_type": "JEDI_CLIN_1",
|
||||||
"clins-0-clin_number": "12312",
|
"clins-0-clin_number": "12312",
|
||||||
@ -237,7 +236,7 @@ def test_task_orders_submit_form_step_three_add_clins_existing_to(
|
|||||||
TaskOrders.create_clins(task_order.id, clin_list)
|
TaskOrders.create_clins(task_order.id, clin_list)
|
||||||
assert len(task_order.clins) == 2
|
assert len(task_order.clins) == 2
|
||||||
|
|
||||||
user_session(task_order.creator)
|
user_session(task_order.portfolio.owner)
|
||||||
form_data = {
|
form_data = {
|
||||||
"clins-0-jedi_clin_type": "JEDI_CLIN_1",
|
"clins-0-jedi_clin_type": "JEDI_CLIN_1",
|
||||||
"clins-0-clin_number": "12312",
|
"clins-0-clin_number": "12312",
|
||||||
@ -258,7 +257,7 @@ def test_task_orders_submit_form_step_three_add_clins_existing_to(
|
|||||||
|
|
||||||
|
|
||||||
def test_task_orders_form_step_four_review(client, user_session, completed_task_order):
|
def test_task_orders_form_step_four_review(client, user_session, completed_task_order):
|
||||||
user_session(completed_task_order.creator)
|
user_session(completed_task_order.portfolio.owner)
|
||||||
response = client.get(
|
response = client.get(
|
||||||
url_for(
|
url_for(
|
||||||
"task_orders.form_step_four_review", task_order_id=completed_task_order.id
|
"task_orders.form_step_four_review", task_order_id=completed_task_order.id
|
||||||
@ -270,7 +269,7 @@ def test_task_orders_form_step_four_review(client, user_session, completed_task_
|
|||||||
def test_task_orders_form_step_four_review_incomplete_to(
|
def test_task_orders_form_step_four_review_incomplete_to(
|
||||||
client, user_session, task_order
|
client, user_session, task_order
|
||||||
):
|
):
|
||||||
user_session(task_order.creator)
|
user_session(task_order.portfolio.owner)
|
||||||
response = client.get(
|
response = client.get(
|
||||||
url_for("task_orders.form_step_four_review", task_order_id=task_order.id)
|
url_for("task_orders.form_step_four_review", task_order_id=task_order.id)
|
||||||
)
|
)
|
||||||
@ -280,7 +279,7 @@ def test_task_orders_form_step_four_review_incomplete_to(
|
|||||||
def test_task_orders_form_step_five_confirm_signature(
|
def test_task_orders_form_step_five_confirm_signature(
|
||||||
client, user_session, completed_task_order
|
client, user_session, completed_task_order
|
||||||
):
|
):
|
||||||
user_session(completed_task_order.creator)
|
user_session(completed_task_order.portfolio.owner)
|
||||||
response = client.get(
|
response = client.get(
|
||||||
url_for(
|
url_for(
|
||||||
"task_orders.form_step_five_confirm_signature",
|
"task_orders.form_step_five_confirm_signature",
|
||||||
@ -293,7 +292,7 @@ def test_task_orders_form_step_five_confirm_signature(
|
|||||||
def test_task_orders_form_step_five_confirm_signature_incomplete_to(
|
def test_task_orders_form_step_five_confirm_signature_incomplete_to(
|
||||||
client, user_session, task_order
|
client, user_session, task_order
|
||||||
):
|
):
|
||||||
user_session(task_order.creator)
|
user_session(task_order.portfolio.owner)
|
||||||
response = client.get(
|
response = client.get(
|
||||||
url_for(
|
url_for(
|
||||||
"task_orders.form_step_five_confirm_signature", task_order_id=task_order.id
|
"task_orders.form_step_five_confirm_signature", task_order_id=task_order.id
|
||||||
@ -340,9 +339,7 @@ def test_task_orders_submit_task_order(client, user_session, task_order):
|
|||||||
def test_task_orders_edit_redirects_to_latest_incomplete_step(
|
def test_task_orders_edit_redirects_to_latest_incomplete_step(
|
||||||
client, user_session, portfolio, to_factory_args, expected_step
|
client, user_session, portfolio, to_factory_args, expected_step
|
||||||
):
|
):
|
||||||
task_order = TaskOrderFactory.create(
|
task_order = TaskOrderFactory.create(portfolio=portfolio, **to_factory_args)
|
||||||
portfolio=portfolio, creator=portfolio.owner, **to_factory_args
|
|
||||||
)
|
|
||||||
user_session(portfolio.owner)
|
user_session(portfolio.owner)
|
||||||
|
|
||||||
response = client.get(url_for("task_orders.edit", task_order_id=task_order.id))
|
response = client.get(url_for("task_orders.edit", task_order_id=task_order.id))
|
||||||
@ -414,8 +411,7 @@ def test_task_orders_update_invalid_data(client, user_session, portfolio):
|
|||||||
|
|
||||||
@pytest.mark.skip(reason="Update after implementing errors on TO form")
|
@pytest.mark.skip(reason="Update after implementing errors on TO form")
|
||||||
def test_task_order_form_shows_errors(client, user_session, task_order):
|
def test_task_order_form_shows_errors(client, user_session, task_order):
|
||||||
creator = task_order.creator
|
user_session(task_order.portfolio.owner)
|
||||||
user_session(creator)
|
|
||||||
|
|
||||||
task_order_data = TaskOrderFactory.dictionary()
|
task_order_data = TaskOrderFactory.dictionary()
|
||||||
funding_data = slice_data_for_section(task_order_data, "funding")
|
funding_data = slice_data_for_section(task_order_data, "funding")
|
||||||
|
@ -487,7 +487,9 @@ def test_portfolios_resend_invitation_access(post_url_assert_status):
|
|||||||
|
|
||||||
portfolio = PortfolioFactory.create(owner=owner)
|
portfolio = PortfolioFactory.create(owner=owner)
|
||||||
prr = PortfolioRoleFactory.create(user=invitee, portfolio=portfolio)
|
prr = PortfolioRoleFactory.create(user=invitee, portfolio=portfolio)
|
||||||
invite = PortfolioInvitationFactory.create(user=UserFactory.create(), role=prr)
|
invite = PortfolioInvitationFactory.create(
|
||||||
|
user=UserFactory.create(), role=prr, inviter_id=owner.id
|
||||||
|
)
|
||||||
|
|
||||||
url = url_for(
|
url = url_for(
|
||||||
"portfolios.resend_invitation",
|
"portfolios.resend_invitation",
|
||||||
@ -651,7 +653,6 @@ def test_task_orders_new_get_routes(get_url_assert_status):
|
|||||||
|
|
||||||
portfolio = PortfolioFactory.create(owner=owner)
|
portfolio = PortfolioFactory.create(owner=owner)
|
||||||
task_order = TaskOrderFactory.create(
|
task_order = TaskOrderFactory.create(
|
||||||
creator=owner,
|
|
||||||
portfolio=portfolio,
|
portfolio=portfolio,
|
||||||
create_clins=[{"number": "1234567890123456789012345678901234567890123"}],
|
create_clins=[{"number": "1234567890123456789012345678901234567890123"}],
|
||||||
)
|
)
|
||||||
@ -689,7 +690,7 @@ def test_task_orders_new_post_routes(post_url_assert_status):
|
|||||||
rando = user_with()
|
rando = user_with()
|
||||||
|
|
||||||
portfolio = PortfolioFactory.create(owner=owner)
|
portfolio = PortfolioFactory.create(owner=owner)
|
||||||
task_order = TaskOrderFactory.create(portfolio=portfolio, creator=owner)
|
task_order = TaskOrderFactory.create(portfolio=portfolio)
|
||||||
|
|
||||||
for route, data in post_routes:
|
for route, data in post_routes:
|
||||||
url = url_for(route, task_order_id=task_order.id)
|
url = url_for(route, task_order_id=task_order.id)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user