Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IRONN-210 allow for change of withdrawal dates #4343

Merged
merged 1 commit into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 33 additions & 8 deletions portal/views/user.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""User API view functions"""
from datetime import datetime
from datetime import datetime, timedelta

from dateutil.relativedelta import relativedelta
from flask import (
Expand Down Expand Up @@ -50,7 +50,7 @@
permanently_delete_user,
validate_email,
)
from ..models.user_consent import UserConsent
from ..models.user_consent import UserConsent, consent_withdrawal_dates
from ..models.user_document import UserDocument
from ..type_tools import check_int
from .auth import logout
Expand Down Expand Up @@ -866,13 +866,38 @@ def withdraw_consent(
uc = UserConsent.query.filter_by(
user_id=user.id, organization_id=org_id, status='consented',
research_study_id=research_study_id).first()

if not uc:
abort(
404,
"no UserConsent found for user ID {}, org ID {}, research study "
"ID {}".format(user.id, org_id, research_study_id))
try:
if not uc:
# Possible replacement of existing withdrawal
wc = UserConsent.query.filter_by(
user_id=user.id, organization_id=org_id, status='suspended',
research_study_id=research_study_id).first()

if not wc:
abort(
404,
"no UserConsent found for user ID {}, org ID {}, research study "
"ID {}".format(user.id, org_id, research_study_id))

# replace with requested time, provided it's in a valid window
prior_consent, prior_withdrawal = consent_withdrawal_dates(user, research_study_id)
if acceptance_date == prior_withdrawal:
# valid nop, leave.
return jsonify(wc.as_json())
if acceptance_date < prior_consent:
raise ValueError(
f"Can't suspend with acceptance date {acceptance_date} "
f"prior to last valid consent {prior_consent}")
if acceptance_date > datetime.utcnow() + timedelta(days=1):
raise ValueError(
"Can't suspend with acceptance date in the future")
wc.acceptance_date = acceptance_date
db.session.commit()
# As withdrawal time just changed, force recalculation
invalidate_users_QBT(
user_id=user.id, research_study_id=research_study_id)
return jsonify(wc.as_json())

if not acceptance_date:
acceptance_date = datetime.utcnow()
if acceptance_date <= uc.acceptance_date:
Expand Down
68 changes: 68 additions & 0 deletions tests/test_consent.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,3 +519,71 @@ def test_withdraw_too_early(self):
assert resp.status_code == 400
assert 1 == UserConsent.query.filter_by(
user_id=TEST_USER_ID, organization_id=org_id).count()

def test_double_withdrawal(self):
"""second withdrawal should replace the first"""
self.shallow_org_tree()
org = Organization.query.filter(Organization.id > 0).first()
org_id = org.id

acceptance_date = FHIR_datetime.parse("2018-06-30 12:12:12")
suspend_date = FHIR_datetime.parse("2018-06-30 12:12:15")
audit = Audit(user_id=TEST_USER_ID, subject_id=TEST_USER_ID)
uc = UserConsent(
organization_id=org_id, user_id=TEST_USER_ID,
agreement_url=self.url, audit=audit,
acceptance_date=acceptance_date,
research_study_id=0)
with SessionScope(db):
db.session.add(uc)
db.session.commit()
self.test_user = db.session.merge(self.test_user)
assert len(self.test_user.valid_consents) == 1

self.login()

data = {'organization_id': org_id, 'acceptance_date': suspend_date}
resp = self.client.post(
'/api/user/{}/consent/withdraw'.format(TEST_USER_ID),
json=data,
)
assert resp.status_code == 200

# withdraw a second time with a few invalid values
too_early = acceptance_date - timedelta(days=1)
too_late = datetime.now() + timedelta(hours=25)
just_right = suspend_date + timedelta(days=30)

data = {'organization_id': org_id, 'acceptance_date': too_early}
resp = self.client.post(
'/api/user/{}/consent/withdraw'.format(TEST_USER_ID),
json=data,
)
assert resp.status_code == 400

data = {'organization_id': org_id, 'acceptance_date': too_late}
resp = self.client.post(
'/api/user/{}/consent/withdraw'.format(TEST_USER_ID),
json=data,
)
assert resp.status_code == 400

data = {'organization_id': org_id, 'acceptance_date': just_right}
resp = self.client.post(
'/api/user/{}/consent/withdraw'.format(TEST_USER_ID),
json=data,
)
assert resp.status_code == 200

# check that old consent is marked as deleted
old_consent = UserConsent.query.filter_by(
user_id=TEST_USER_ID, organization_id=org_id,
status='deleted').first()
assert old_consent.deleted_id

# check withdrawn consent was replaced
query = UserConsent.query.filter_by(
user_id=TEST_USER_ID, organization_id=org_id,
status='suspended')
assert query.count() == 1
assert query.first().acceptance_date == just_right
Loading