Merge branch 'master' into show-sort-indicators
This commit is contained in:
commit
924b234d28
50
alembic/versions/02d11579a581_add_email_to_invite.py
Normal file
50
alembic/versions/02d11579a581_add_email_to_invite.py
Normal file
@ -0,0 +1,50 @@
|
||||
"""Add email to invite
|
||||
|
||||
Revision ID: 02d11579a581
|
||||
Revises: 4f46aecb337f
|
||||
Create Date: 2018-11-19 14:51:33.178358
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.sql import text
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '02d11579a581'
|
||||
down_revision = '4f46aecb337f'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('invitations', sa.Column('email', sa.String()))
|
||||
conn = op.get_bind()
|
||||
# Add non-null value to email column
|
||||
conn.execute(
|
||||
text(
|
||||
"""
|
||||
insert into invitations (email)
|
||||
select u.email
|
||||
from invitations i
|
||||
inner join workspace_roles wr on i.workspace_role_id = wr.id
|
||||
inner join users u on wr.user_id = u.id
|
||||
where i.email is null;
|
||||
"""
|
||||
)
|
||||
)
|
||||
conn.execute(
|
||||
text(
|
||||
"""
|
||||
update invitations
|
||||
set email = 'example@example.com'
|
||||
where email is null;
|
||||
"""
|
||||
)
|
||||
)
|
||||
op.alter_column('invitations', 'email', nullable=False)
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('invitations', 'email')
|
||||
# ### end Alembic commands ###
|
@ -54,13 +54,14 @@ class Invitations(object):
|
||||
return invite
|
||||
|
||||
@classmethod
|
||||
def create(cls, inviter, workspace_role):
|
||||
def create(cls, inviter, workspace_role, email):
|
||||
invite = Invitation(
|
||||
workspace_role=workspace_role,
|
||||
inviter=inviter,
|
||||
user=workspace_role.user,
|
||||
status=InvitationStatus.PENDING,
|
||||
expiration_time=Invitations.current_expiration_time(),
|
||||
email=email,
|
||||
)
|
||||
db.session.add(invite)
|
||||
db.session.commit()
|
||||
@ -120,4 +121,6 @@ class Invitations(object):
|
||||
previous_invitation = Invitations._get(token)
|
||||
Invitations._update_status(previous_invitation, InvitationStatus.REVOKED)
|
||||
|
||||
return Invitations.create(user, previous_invitation.workspace_role)
|
||||
return Invitations.create(
|
||||
user, previous_invitation.workspace_role, previous_invitation.email
|
||||
)
|
||||
|
@ -10,7 +10,7 @@ class CSPRole(Enum):
|
||||
NONSENSE_ROLE = "nonsense_role"
|
||||
|
||||
|
||||
class EnvironmentRole(Base, mixins.TimestampsMixin):
|
||||
class EnvironmentRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
|
||||
__tablename__ = "environment_roles"
|
||||
|
||||
id = types.Id()
|
||||
@ -29,6 +29,23 @@ class EnvironmentRole(Base, mixins.TimestampsMixin):
|
||||
self.role, self.user.full_name, self.environment.name, self.id
|
||||
)
|
||||
|
||||
@property
|
||||
def history(self):
|
||||
return self.get_changes()
|
||||
|
||||
@property
|
||||
def event_details(self):
|
||||
return {
|
||||
"updated_user_name": self.user.displayname,
|
||||
"updated_user_id": str(self.user_id),
|
||||
"environment": self.environment.displayname,
|
||||
"environment_id": str(self.environment_id),
|
||||
"project": self.environment.project.name,
|
||||
"project_id": str(self.environment.project_id),
|
||||
"workspace": self.environment.project.workspace.name,
|
||||
"workspace_id": str(self.environment.project.workspace.id),
|
||||
}
|
||||
|
||||
|
||||
Index(
|
||||
"environments_role_user_environment",
|
||||
|
@ -42,11 +42,13 @@ class Invitation(Base, TimestampsMixin, AuditableMixin):
|
||||
|
||||
expiration_time = Column(TIMESTAMP(timezone=True))
|
||||
|
||||
token = Column(String(), index=True, default=lambda: secrets.token_urlsafe())
|
||||
token = Column(String, index=True, default=lambda: secrets.token_urlsafe())
|
||||
|
||||
email = Column(String, nullable=False)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Invitation(user='{}', workspace_role='{}', id='{}')>".format(
|
||||
self.user_id, self.workspace_role_id, self.id
|
||||
return "<Invitation(user='{}', workspace_role='{}', id='{}', email='{}')>".format(
|
||||
self.user_id, self.workspace_role_id, self.id, self.email
|
||||
)
|
||||
|
||||
@property
|
||||
@ -82,10 +84,6 @@ class Invitation(Base, TimestampsMixin, AuditableMixin):
|
||||
if self.workspace_role:
|
||||
return self.workspace_role.workspace
|
||||
|
||||
@property
|
||||
def user_email(self):
|
||||
return self.workspace_role.user.email
|
||||
|
||||
@property
|
||||
def user_name(self):
|
||||
return self.workspace_role.user.full_name
|
||||
|
@ -17,9 +17,12 @@ class AuditableMixin(object):
|
||||
request_id = resource.auditable_request_id()
|
||||
resource_type = resource.auditable_resource_type()
|
||||
display_name = resource.auditable_displayname()
|
||||
changed_state = resource.auditable_changed_state()
|
||||
event_details = resource.auditable_event_details()
|
||||
|
||||
changed_state = (
|
||||
resource.auditable_changed_state() if action == ACTION_UPDATE else None
|
||||
)
|
||||
|
||||
audit_event = AuditEvent(
|
||||
user_id=user_id,
|
||||
workspace_id=workspace_id,
|
||||
@ -52,14 +55,12 @@ class AuditableMixin(object):
|
||||
|
||||
@staticmethod
|
||||
def audit_update(mapper, connection, target):
|
||||
target.create_audit_event(connection, target, ACTION_UPDATE)
|
||||
if AuditableMixin.get_changes(target):
|
||||
target.create_audit_event(connection, target, ACTION_UPDATE)
|
||||
|
||||
def get_changes(self):
|
||||
"""
|
||||
This function borrows largely from a gist:
|
||||
https://gist.github.com/ngse/c20058116b8044c65d3fbceda3fdf423#file-audit_mixin-py-L106-L120
|
||||
|
||||
It returns a dictionary of the form {item: [from_value, to_value]},
|
||||
This function returns a dictionary of the form {item: [from_value, to_value]},
|
||||
where 'item' is the attribute on the target that has been updated,
|
||||
'from_value' is the value of the attribute before it was updated,
|
||||
and 'to_value' is the current value of the attribute.
|
||||
@ -71,7 +72,9 @@ class AuditableMixin(object):
|
||||
for attr in attrs:
|
||||
history = getattr(inspect(self).attrs, attr.key).history
|
||||
if history.has_changes():
|
||||
previous_state[attr.key] = [history.deleted.pop(), history.added.pop()]
|
||||
deleted = history.deleted.pop() if history.deleted else None
|
||||
added = history.added.pop() if history.added else None
|
||||
previous_state[attr.key] = [deleted, added]
|
||||
return previous_state
|
||||
|
||||
def auditable_changed_state(self):
|
||||
|
@ -262,10 +262,8 @@ def create_member(workspace_id):
|
||||
if form.validate():
|
||||
try:
|
||||
new_member = Workspaces.create_member(user, workspace, form.data)
|
||||
invite = Invitations.create(user, new_member)
|
||||
send_invite_email(
|
||||
g.current_user.full_name, invite.token, new_member.user.email
|
||||
)
|
||||
invite = Invitations.create(user, new_member, form.data["email"])
|
||||
send_invite_email(g.current_user.full_name, invite.token, invite.email)
|
||||
|
||||
return redirect(
|
||||
url_for(
|
||||
@ -381,7 +379,7 @@ def revoke_invitation(workspace_id, token):
|
||||
@bp.route("/workspaces/<workspace_id>/invitations/<token>/resend", methods=["POST"])
|
||||
def resend_invitation(workspace_id, token):
|
||||
invite = Invitations.resend(g.current_user, workspace_id, token)
|
||||
send_invite_email(g.current_user.full_name, invite.token, invite.user_email)
|
||||
send_invite_email(g.current_user.full_name, invite.token, invite.email)
|
||||
return redirect(
|
||||
url_for(
|
||||
"workspaces.workspace_members",
|
||||
|
@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ConfirmationPopover matches snapshot 1`] = `<v-popover-stub placement="top-start" delay="0" offset="0" trigger="click" container="body" popperoptions="[object Object]" popoverclass="vue-popover-theme" popoverbaseclass="tooltip popover" popoverinnerclass="tooltip-inner popover-inner" popoverwrapperclass="wrapper" popoverarrowclass="tooltip-arrow popover-arrow" autohide="true" handleresize="true"><template></template> <button type="button" class="tooltip-target">Do something dangerous</button></v-popover-stub>`;
|
32
js/components/__tests__/confirmation_popover.test.js
Normal file
32
js/components/__tests__/confirmation_popover.test.js
Normal file
@ -0,0 +1,32 @@
|
||||
import { createLocalVue, shallowMount } from '@vue/test-utils'
|
||||
import VTooltip from 'v-tooltip'
|
||||
|
||||
import ConfirmationPopover from '../confirmation_popover'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
localVue.use(VTooltip)
|
||||
|
||||
describe('ConfirmationPopover', () => {
|
||||
const wrapper = shallowMount(ConfirmationPopover, {
|
||||
localVue,
|
||||
propsData: {
|
||||
action: '/some-url',
|
||||
btn_text: 'Do something dangerous',
|
||||
cancel_btn_text: 'Cancel',
|
||||
confirm_btn_text: 'Confirm',
|
||||
confirm_msg: 'Are you sure you want to do that?',
|
||||
csrf_token: '42'
|
||||
}
|
||||
})
|
||||
|
||||
it('matches snapshot', () => {
|
||||
expect(wrapper).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders form with hidden csrf input', () => {
|
||||
const input = wrapper.find('input[type=hidden]')
|
||||
expect(input.exists()).toBe(true)
|
||||
expect(input.attributes('value')).toBe('42')
|
||||
})
|
||||
})
|
||||
|
32
js/components/confirmation_popover.js
Normal file
32
js/components/confirmation_popover.js
Normal file
@ -0,0 +1,32 @@
|
||||
export default {
|
||||
name: 'confirmation-popover',
|
||||
|
||||
props: {
|
||||
action: String,
|
||||
btn_text: String,
|
||||
cancel_btn_text: String,
|
||||
confirm_btn_text: String,
|
||||
confirm_msg: String,
|
||||
csrf_token: String
|
||||
},
|
||||
|
||||
template: `
|
||||
<v-popover placement='top-start'>
|
||||
<template slot="popover">
|
||||
<p>{{ confirm_msg }}</p>
|
||||
<div class='action-group'>
|
||||
<form method="POST" v-bind:action="action">
|
||||
<input id="csrf_token" name="csrf_token" type="hidden" v-bind:value="csrf_token">
|
||||
<button class='usa-button usa-button-primary' type='submit'>
|
||||
{{ confirm_btn_text }}
|
||||
</button>
|
||||
</form>
|
||||
<button class='usa-button usa-button-secondary' v-close-popover>
|
||||
{{ cancel_btn_text }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<button class="tooltip-target" type="button">{{ btn_text }}</button>
|
||||
</v-popover>
|
||||
`
|
||||
}
|
@ -23,6 +23,7 @@ import CcpoApproval from './components/forms/ccpo_approval'
|
||||
import MembersList from './components/members_list'
|
||||
import LocalDatetime from './components/local_datetime'
|
||||
import RequestsList from './components/requests_list'
|
||||
import ConfirmationPopover from './components/confirmation_popover'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -50,6 +51,7 @@ const app = new Vue({
|
||||
EditEnvironmentRole,
|
||||
EditProjectRoles,
|
||||
RequestsList,
|
||||
ConfirmationPopover,
|
||||
},
|
||||
|
||||
mounted: function() {
|
||||
|
@ -30,12 +30,16 @@
|
||||
|
||||
{% if event.event_details %}
|
||||
for User <code>{{ event.event_details.updated_user_id }}</code> ({{ event.event_details.updated_user_name }})
|
||||
<br>
|
||||
{% endif %}
|
||||
|
||||
{% if event.changed_state %}
|
||||
from {{ event.changed_state.role[0] }} to {{ event.changed_state.role[1] }}
|
||||
<br>
|
||||
{% if event.event_details["environment"] %}
|
||||
<br>
|
||||
in Environment <code>{{ event.event_details["environment_id"] }}</code> ({{ event.event_details["environment"] }})
|
||||
<br>
|
||||
in Project <code>{{ event.event_details["project_id"] }}</code> ({{ event.event_details["project"] }})
|
||||
<br>
|
||||
in Workspace <code>{{ event.event_details["workspace_id"] }}</code> ({{ event.event_details["workspace"] }})
|
||||
{% endif %}
|
||||
<br>
|
||||
{% endif %}
|
||||
|
||||
{% if event.workspace %}
|
||||
@ -43,6 +47,11 @@
|
||||
{% elif event.request %}
|
||||
on Request <code>{{ event.request_id }}</code> ({{ event.request.displayname }})
|
||||
{% endif %}
|
||||
|
||||
{% if event.changed_state.role %}
|
||||
from {{ event.changed_state.role[0] }} to {{ event.changed_state.role[1] }}
|
||||
<br>
|
||||
{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
|
@ -1,19 +1,10 @@
|
||||
{% macro ConfirmationButton(btn_text, action, csrf_token, confirm_msg="Are you sure?", confirm_btn="Confirm", cancel_btn="Cancel") -%}
|
||||
<v-popover placement='top-start'>
|
||||
<template slot="popover">
|
||||
<p>{{ confirm_msg }}</p>
|
||||
<div class='action-group'>
|
||||
<form method="POST" action="{{ action }}">
|
||||
{{ csrf_token }}
|
||||
<button class='usa-button usa-button-primary' type='submit'>
|
||||
{{ confirm_btn }}
|
||||
</button>
|
||||
</form>
|
||||
<button class='usa-button usa-button-secondary' v-close-popover>
|
||||
{{ cancel_btn }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<button class="tooltip-target" type="button">{{ btn_text }}</button>
|
||||
</v-popover>
|
||||
{% macro ConfirmationButton(btn_text, action, confirm_msg="Are you sure?", confirm_btn="Confirm", cancel_btn="Cancel") -%}
|
||||
<confirmation-popover
|
||||
btn_text='{{ btn_text }}'
|
||||
action='{{ action }}'
|
||||
csrf_token='{{ csrf_token() }}'
|
||||
confirm_msg='{{ confirm_msg }}'
|
||||
confirm_btn_text='{{ confirm_btn }}'
|
||||
cancel_btn_text='{{ cancel_btn }}'>
|
||||
</confirmation-popover>
|
||||
{%- endmacro %}
|
||||
|
@ -2,17 +2,6 @@
|
||||
|
||||
<div class="global-navigation sidenav {% if workspace %}global-navigation__context--workspace{% endif %}">
|
||||
<ul>
|
||||
{% if g.dev %}
|
||||
{{ SidenavItem("Styleguide",
|
||||
href="/styleguide",
|
||||
icon="visible",
|
||||
active=g.matchesPath('/styleguide'),
|
||||
subnav=[
|
||||
{"label":"Subnav 1", "href":"/styleguide?subnav1", "icon": "plus", "active": g.matchesPath('/styleguide?subnav1')},
|
||||
{"label":"Subnav 2", "href":"/styleguide?subnav2", "active": g.matchesPath('/styleguide?subnav2')},
|
||||
]) }}
|
||||
{% endif %}
|
||||
|
||||
{{ SidenavItem("Requests",
|
||||
href="/requests",
|
||||
icon="document",
|
||||
|
@ -44,14 +44,12 @@
|
||||
{{ ConfirmationButton(
|
||||
"Revoke Invitation",
|
||||
url_for("workspaces.revoke_invitation", workspace_id=workspace.id, token=member.latest_invitation.token),
|
||||
form.csrf_token
|
||||
) }}
|
||||
{% endif %}
|
||||
{% if member.can_resend_invitation %}
|
||||
{{ ConfirmationButton (
|
||||
"Resend Invitation",
|
||||
url_for("workspaces.resend_invitation", workspace_id=workspace.id, token=member.latest_invitation.token),
|
||||
form.csrf_token,
|
||||
confirm_msg="Are you sure? This will send an email to invite the user to join this workspace."
|
||||
)}}
|
||||
{% endif %}
|
||||
|
@ -22,7 +22,7 @@ def test_create_invitation():
|
||||
workspace = WorkspaceFactory.create()
|
||||
user = UserFactory.create()
|
||||
ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace)
|
||||
invite = Invitations.create(workspace.owner, ws_role)
|
||||
invite = Invitations.create(workspace.owner, ws_role, user.email)
|
||||
assert invite.user == user
|
||||
assert invite.workspace_role == ws_role
|
||||
assert invite.inviter == workspace.owner
|
||||
@ -34,7 +34,7 @@ def test_accept_invitation():
|
||||
workspace = WorkspaceFactory.create()
|
||||
user = UserFactory.create()
|
||||
ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace)
|
||||
invite = Invitations.create(workspace.owner, ws_role)
|
||||
invite = Invitations.create(workspace.owner, ws_role, user.email)
|
||||
assert invite.is_pending
|
||||
accepted_invite = Invitations.accept(user, invite.token)
|
||||
assert accepted_invite.is_accepted
|
||||
@ -89,7 +89,7 @@ def test_accept_invitation_twice():
|
||||
workspace = WorkspaceFactory.create()
|
||||
user = UserFactory.create()
|
||||
ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace)
|
||||
invite = Invitations.create(workspace.owner, ws_role)
|
||||
invite = Invitations.create(workspace.owner, ws_role, user.email)
|
||||
Invitations.accept(user, invite.token)
|
||||
with pytest.raises(InvitationError):
|
||||
Invitations.accept(user, invite.token)
|
||||
@ -99,7 +99,7 @@ def test_revoke_invitation():
|
||||
workspace = WorkspaceFactory.create()
|
||||
user = UserFactory.create()
|
||||
ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace)
|
||||
invite = Invitations.create(workspace.owner, ws_role)
|
||||
invite = Invitations.create(workspace.owner, ws_role, user.email)
|
||||
assert invite.is_pending
|
||||
Invitations.revoke(invite.token)
|
||||
assert invite.is_revoked
|
||||
@ -109,7 +109,7 @@ def test_resend_invitation():
|
||||
workspace = WorkspaceFactory.create()
|
||||
user = UserFactory.create()
|
||||
ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace)
|
||||
invite = Invitations.create(workspace.owner, ws_role)
|
||||
invite = Invitations.create(workspace.owner, ws_role, user.email)
|
||||
Invitations.resend(workspace.owner, workspace.id, invite.token)
|
||||
assert ws_role.invitations[0].is_revoked
|
||||
assert ws_role.invitations[1].is_pending
|
||||
|
@ -134,7 +134,8 @@ def test_update_workspace_role_role(workspace, workspace_owner):
|
||||
"workspace_role": "developer",
|
||||
"dod_id": "1234567890",
|
||||
}
|
||||
member = Workspaces.create_member(workspace_owner, workspace, user_data)
|
||||
WorkspaceRoleFactory._meta.sqlalchemy_session_persistence = "flush"
|
||||
member = WorkspaceRoleFactory.create(workspace=workspace)
|
||||
role_name = "admin"
|
||||
|
||||
updated_member = Workspaces.update_member(
|
||||
|
@ -342,5 +342,6 @@ class InvitationFactory(Base):
|
||||
class Meta:
|
||||
model = Invitation
|
||||
|
||||
email = factory.Faker("email")
|
||||
status = InvitationStatus.PENDING
|
||||
expiration_time = Invitations.current_expiration_time()
|
||||
|
@ -13,10 +13,13 @@ from tests.factories import (
|
||||
UserFactory,
|
||||
InvitationFactory,
|
||||
WorkspaceRoleFactory,
|
||||
EnvironmentFactory,
|
||||
EnvironmentRoleFactory,
|
||||
ProjectFactory,
|
||||
)
|
||||
|
||||
|
||||
def test_has_no_history(session):
|
||||
def test_has_no_ws_role_history(session):
|
||||
owner = UserFactory.create()
|
||||
user = UserFactory.create()
|
||||
|
||||
@ -33,7 +36,7 @@ def test_has_no_history(session):
|
||||
assert not create_event.changed_state
|
||||
|
||||
|
||||
def test_has_role_history(session):
|
||||
def test_has_ws_role_history(session):
|
||||
owner = UserFactory.create()
|
||||
user = UserFactory.create()
|
||||
|
||||
@ -58,7 +61,7 @@ def test_has_role_history(session):
|
||||
assert changed_events[0].changed_state["role"][1] == "admin"
|
||||
|
||||
|
||||
def test_has_status_history(session):
|
||||
def test_has_ws_status_history(session):
|
||||
owner = UserFactory.create()
|
||||
user = UserFactory.create()
|
||||
|
||||
@ -81,6 +84,49 @@ def test_has_status_history(session):
|
||||
assert changed_events[0].changed_state["status"][1] == "active"
|
||||
|
||||
|
||||
def test_has_no_env_role_history(session):
|
||||
owner = UserFactory.create()
|
||||
user = UserFactory.create()
|
||||
workspace = Workspaces.create(RequestFactory.create(creator=owner))
|
||||
project = ProjectFactory.create(workspace=workspace)
|
||||
environment = EnvironmentFactory.create(project=project, name="new environment!")
|
||||
|
||||
env_role = EnvironmentRoleFactory.create(
|
||||
user=user, environment=environment, role="developer"
|
||||
)
|
||||
create_event = (
|
||||
session.query(AuditEvent)
|
||||
.filter(AuditEvent.resource_id == env_role.id, AuditEvent.action == "create")
|
||||
.one()
|
||||
)
|
||||
|
||||
assert not create_event.changed_state
|
||||
|
||||
|
||||
def test_has_env_role_history(session):
|
||||
owner = UserFactory.create()
|
||||
user = UserFactory.create()
|
||||
workspace = Workspaces.create(RequestFactory.create(creator=owner))
|
||||
workspace_role = WorkspaceRoleFactory.create(workspace=workspace, user=user)
|
||||
project = ProjectFactory.create(workspace=workspace)
|
||||
environment = EnvironmentFactory.create(project=project, name="new environment!")
|
||||
|
||||
env_role = EnvironmentRoleFactory.create(
|
||||
user=user, environment=environment, role="developer"
|
||||
)
|
||||
Environments.update_environment_roles(
|
||||
owner, workspace, workspace_role, [{"role": "admin", "id": environment.id}]
|
||||
)
|
||||
changed_events = (
|
||||
session.query(AuditEvent)
|
||||
.filter(AuditEvent.resource_id == env_role.id, AuditEvent.action == "update")
|
||||
.all()
|
||||
)
|
||||
# changed_state["role"] returns a list [previous role, current role]
|
||||
assert changed_events[0].changed_state["role"][0] == "developer"
|
||||
assert changed_events[0].changed_state["role"][1] == "admin"
|
||||
|
||||
|
||||
def test_event_details():
|
||||
owner = UserFactory.create()
|
||||
user = UserFactory.create()
|
||||
|
@ -1,5 +1,6 @@
|
||||
import datetime
|
||||
from flask import url_for
|
||||
import pytest
|
||||
|
||||
from tests.factories import (
|
||||
UserFactory,
|
||||
@ -339,6 +340,29 @@ def test_existing_member_accepts_valid_invite(client, user_session):
|
||||
assert len(Workspaces.for_user(user)) == 1
|
||||
|
||||
|
||||
def test_existing_member_invite_sent_to_email_submitted_in_form(
|
||||
client, user_session, queue
|
||||
):
|
||||
workspace = WorkspaceFactory.create()
|
||||
user = UserFactory.create()
|
||||
member_form_data = {
|
||||
"dod_id": user.dod_id,
|
||||
"first_name": user.first_name,
|
||||
"last_name": user.last_name,
|
||||
"workspace_role": "developer",
|
||||
"email": "example@example.com",
|
||||
}
|
||||
user_session(workspace.owner)
|
||||
client.post(
|
||||
url_for("workspaces.create_member", workspace_id=workspace.id),
|
||||
data={**member_form_data},
|
||||
)
|
||||
|
||||
assert user.email != "example@example.com"
|
||||
assert len(queue.get_queue().jobs[0].args[0]) == 1
|
||||
assert queue.get_queue().jobs[0].args[0][0] == "example@example.com"
|
||||
|
||||
|
||||
def test_new_member_accepts_valid_invite(monkeypatch, client, user_session):
|
||||
workspace = WorkspaceFactory.create()
|
||||
user_info = UserFactory.dictionary()
|
||||
@ -478,3 +502,32 @@ def test_resend_invitation_sends_email(client, user_session, queue):
|
||||
)
|
||||
|
||||
assert len(queue.get_queue()) == 1
|
||||
|
||||
|
||||
def test_existing_member_invite_resent_to_email_submitted_in_form(
|
||||
client, user_session, queue
|
||||
):
|
||||
workspace = WorkspaceFactory.create()
|
||||
user = UserFactory.create()
|
||||
ws_role = WorkspaceRoleFactory.create(
|
||||
user=user, workspace=workspace, status=WorkspaceRoleStatus.PENDING
|
||||
)
|
||||
invite = InvitationFactory.create(
|
||||
user_id=user.id,
|
||||
workspace_role_id=ws_role.id,
|
||||
status=InvitationStatus.PENDING,
|
||||
email="example@example.com",
|
||||
)
|
||||
user_session(workspace.owner)
|
||||
client.post(
|
||||
url_for(
|
||||
"workspaces.resend_invitation",
|
||||
workspace_id=workspace.id,
|
||||
token=invite.token,
|
||||
)
|
||||
)
|
||||
|
||||
send_mail_job = queue.get_queue().jobs[0]
|
||||
assert user.email != "example@example.com"
|
||||
assert send_mail_job.func.__func__.__name__ == "_send_mail"
|
||||
assert send_mail_job.args[0] == ["example@example.com"]
|
||||
|
Loading…
x
Reference in New Issue
Block a user