Skip to content

Commit

Permalink
Merge pull request #414 from dimagi/pkv/tests-program-management
Browse files Browse the repository at this point in the history
Program Management Tests
  • Loading branch information
pxwxnvermx authored Oct 24, 2024
2 parents 751f53d + 26867e1 commit 9d994e0
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 22 deletions.
4 changes: 3 additions & 1 deletion commcare_connect/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ def user(db) -> User:
@pytest.fixture()
def opportunity(request):
verification_flags = getattr(request, "param", {}).get("verification_flags", {})
factory = OpportunityFactory(is_test=False)
opp_options = {"is_test": False}
opp_options.update(getattr(request, "param", {}).get("opp_options", {}))
factory = OpportunityFactory(**opp_options)
OpportunityVerificationFlagsFactory(opportunity=factory, **verification_flags)
return factory

Expand Down
54 changes: 37 additions & 17 deletions commcare_connect/form_receiver/tests/test_receiver_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
OpportunityClaimLimit,
OpportunityVerificationFlags,
UserVisit,
VisitReviewStatus,
VisitValidationStatus,
)
from commcare_connect.opportunity.tasks import bulk_approve_completed_work
Expand Down Expand Up @@ -172,24 +173,14 @@ def test_receiver_deliver_form_daily_visits_reached(
def test_receiver_deliver_form_max_visits_reached(
mobile_user_with_connect_link: User, api_client: APIClient, opportunity: Opportunity
):
def form_json(payment_unit):
deliver_unit = DeliverUnitFactory(app=opportunity.deliver_app, payment_unit=payment_unit)
stub = DeliverUnitStubFactory(id=deliver_unit.slug)
form_json = get_form_json(
form_block=stub.json,
domain=deliver_unit.app.cc_domain,
app_id=deliver_unit.app.cc_app_id,
)
return form_json

def submit_form_for_random_entity(form_json):
duplicate_json = deepcopy(form_json)
duplicate_json["form"]["deliver"]["entity_id"] = str(uuid4())
make_request(api_client, duplicate_json, mobile_user_with_connect_link)

payment_units = opportunity.paymentunit_set.all()
form_json1 = form_json(payment_units[0])
form_json2 = form_json(payment_units[1])
form_json1 = get_form_json_for_payment_unit(payment_units[0])
form_json2 = get_form_json_for_payment_unit(payment_units[1])
for _ in range(2):
submit_form_for_random_entity(form_json1)
submit_form_for_random_entity(form_json2)
Expand Down Expand Up @@ -503,7 +494,7 @@ def test_reciever_verification_flags_form_submission(
assert ["form_submission_period", expected_message] in visit.flag_reason.get("flags", [])


def test_reciever_verification_flags_duration(
def test_receiver_verification_flags_duration(
user_with_connectid_link: User, api_client: APIClient, opportunity: Opportunity
):
form_json = _create_opp_and_form_json(opportunity, user=user_with_connectid_link)
Expand All @@ -516,7 +507,7 @@ def test_reciever_verification_flags_duration(
assert ["duration", "The form was completed too quickly."] in visit.flag_reason.get("flags", [])


def test_reciever_verification_flags_check_attachments(
def test_receiver_verification_flags_check_attachments(
user_with_connectid_link: User, api_client: APIClient, opportunity: Opportunity
):
form_json = _create_opp_and_form_json(opportunity, user=user_with_connectid_link)
Expand All @@ -529,7 +520,7 @@ def test_reciever_verification_flags_check_attachments(
assert ["attachment_missing", "Form was submitted without attachements."] in visit.flag_reason.get("flags", [])


def test_reciever_verification_flags_form_json_rule(
def test_receiver_verification_flags_form_json_rule(
user_with_connectid_link: User, api_client: APIClient, opportunity: Opportunity
):
form_json = _create_opp_and_form_json(opportunity, user=user_with_connectid_link)
Expand All @@ -547,7 +538,7 @@ def test_reciever_verification_flags_form_json_rule(
assert not visit.flagged


def test_reciever_verification_flags_form_json_rule_flagged(
def test_receiver_verification_flags_form_json_rule_flagged(
user_with_connectid_link: User, api_client: APIClient, opportunity: Opportunity
):
form_json = _create_opp_and_form_json(opportunity, user=user_with_connectid_link)
Expand All @@ -569,7 +560,7 @@ def test_reciever_verification_flags_form_json_rule_flagged(
] in visit.flag_reason.get("flags", [])


def test_reciever_verification_flags_catchment_areas(
def test_receiver_verification_flags_catchment_areas(
user_with_connectid_link: User, api_client: APIClient, opportunity: Opportunity
):
verification_flags = OpportunityVerificationFlags.objects.get(opportunity=opportunity)
Expand All @@ -588,6 +579,35 @@ def test_reciever_verification_flags_catchment_areas(
assert ["catchment", "Visit outside worker catchment areas"] in visit.flag_reason.get("flags", [])


@pytest.mark.parametrize("opportunity", [{"opp_options": {"managed": True}}], indirect=True)
@pytest.mark.parametrize(
"visit_status, review_status",
[
(VisitValidationStatus.approved, VisitReviewStatus.agree),
(VisitValidationStatus.pending, VisitReviewStatus.pending),
],
)
def test_receiver_visit_review_status(
mobile_user_with_connect_link: User, api_client: APIClient, opportunity: Opportunity, visit_status, review_status
):
assert opportunity.managed
form_json = get_form_json_for_payment_unit(opportunity.paymentunit_set.first())
if visit_status != VisitValidationStatus.approved:
form_json["metadata"]["location"] = None
make_request(api_client, form_json, mobile_user_with_connect_link)


def get_form_json_for_payment_unit(payment_unit):
deliver_unit = DeliverUnitFactory(app=payment_unit.opportunity.deliver_app, payment_unit=payment_unit)
stub = DeliverUnitStubFactory(id=deliver_unit.slug)
form_json = get_form_json(
form_block=stub.json,
domain=deliver_unit.app.cc_domain,
app_id=deliver_unit.app.cc_app_id,
)
return form_json


def _get_form_json(learn_app, module_id, form_block=None):
form_json = get_form_json(
form_block=form_block or LearnModuleJsonFactory(id=module_id).json,
Expand Down
3 changes: 2 additions & 1 deletion commcare_connect/opportunity/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class UserVisitFactory(DjangoModelFactory):
visit_date = Faker("date_time", tzinfo=timezone.utc)
form_json = Faker("pydict", value_types=[str, int, float, bool])
xform_id = Faker("uuid4")
completed_work = SubFactory(CompletedWorkFactory)

class Meta:
model = "opportunity.UserVisit"
Expand Down Expand Up @@ -233,7 +234,7 @@ class Meta:
class PaymentFactory(DjangoModelFactory):
opportunity_access = SubFactory(OpportunityAccessFactory)
amount = Faker("pyint", min_value=1, max_value=10000)
date_paid = Faker("past_date")
date_paid = Faker("date_time", tzinfo=timezone.utc)

class Meta:
model = "opportunity.Payment"
75 changes: 74 additions & 1 deletion commcare_connect/opportunity/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import pytest
from django.test import Client
from django.urls import reverse
from django.utils.timezone import now

from commcare_connect.opportunity.models import Opportunity, OpportunityAccess, OpportunityClaimLimit
from commcare_connect.opportunity.models import (
Opportunity,
OpportunityAccess,
OpportunityClaimLimit,
UserVisit,
VisitReviewStatus,
VisitValidationStatus,
)
from commcare_connect.opportunity.tests.factories import (
OpportunityAccessFactory,
OpportunityClaimFactory,
OpportunityClaimLimitFactory,
PaymentUnitFactory,
UserVisitFactory,
)
from commcare_connect.organization.models import Organization
from commcare_connect.program.tests.factories import ManagedOpportunityFactory, ProgramFactory
from commcare_connect.users.models import User


Expand Down Expand Up @@ -38,3 +49,65 @@ def test_add_budget_existing_users(
assert opportunity.total_budget == 205
assert opportunity.claimed_budget == 15
assert OpportunityClaimLimit.objects.get(pk=ocl.pk).max_visits == 15


class TestUserVisitReviewView:
@pytest.fixture(autouse=True)
def setup(
self,
client: Client,
program_manager_org: Organization,
program_manager_org_user_admin: User,
organization: Organization,
org_user_admin: User,
):
self.client = client
self.pm_org = program_manager_org
self.pm_user = program_manager_org_user_admin
self.nm_org = organization
self.nm_user = org_user_admin
self.program = ProgramFactory(organization=self.pm_org)
self.opportunity = ManagedOpportunityFactory(program=self.program, organization=self.nm_org)
access = OpportunityAccessFactory(opportunity=self.opportunity, accepted=True)
self.visits = UserVisitFactory.create_batch(
10,
opportunity=self.opportunity,
status=VisitValidationStatus.approved,
review_created_on=now(),
review_status=VisitReviewStatus.pending,
opportunity_access=access,
)

def test_user_visit_review_program_manager_table(self):
self.url = reverse("opportunity:user_visit_review", args=(self.pm_org.slug, self.opportunity.id))
self.client.force_login(self.pm_user)
response = self.client.get(self.url)
assert response.status_code == 200
table = response.context["table"]
assert len(table.rows) == 10
assert "pk" in table.columns.names()

@pytest.mark.parametrize("review_status", [(VisitReviewStatus.agree), (VisitReviewStatus.disagree)])
def test_user_visit_review_program_manager_approval(self, review_status):
self.url = reverse("opportunity:user_visit_review", args=(self.pm_org.slug, self.opportunity.id))
self.client.force_login(self.pm_user)
response = self.client.post(self.url, {"pk": [], "review_status": review_status.value})
assert response.status_code == 200
visits = UserVisit.objects.filter(id__in=[visit.id for visit in self.visits])
for visit in visits:
assert visit.review_status == VisitReviewStatus.pending

visit_ids = [visit.id for visit in self.visits][:5]
response = self.client.post(self.url, {"pk": visit_ids, "review_status": review_status.value})
assert response.status_code == 200
visits = UserVisit.objects.filter(id__in=visit_ids)
for visit in visits:
assert visit.review_status == review_status

def test_user_visit_review_network_manager_table(self):
self.url = reverse("opportunity:user_visit_review", args=(self.nm_org.slug, self.opportunity.id))
self.client.force_login(self.nm_user)
response = self.client.get(self.url)
table = response.context["table"]
assert len(table.rows) == 10
assert "pk" not in table.columns.names()
63 changes: 63 additions & 0 deletions commcare_connect/opportunity/tests/test_visit_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
Payment,
PaymentUnit,
UserVisit,
VisitReviewStatus,
VisitValidationStatus,
)
from commcare_connect.opportunity.tests.factories import (
Expand Down Expand Up @@ -539,3 +540,65 @@ def get_assignable_completed_work_count(access: OpportunityAccess) -> int:
total_assigned_count += 1

return total_assigned_count


@pytest.mark.parametrize("opportunity", [{"opp_options": {"managed": True}}], indirect=True)
@pytest.mark.parametrize("visit_status", [VisitValidationStatus.approved, VisitValidationStatus.rejected])
def test_network_manager_flagged_visit_review_status(mobile_user: User, opportunity: Opportunity, visit_status):
assert opportunity.managed
access = OpportunityAccess.objects.get(user=mobile_user, opportunity=opportunity)
visits = UserVisitFactory.create_batch(
5, opportunity=opportunity, status=VisitValidationStatus.pending, user=mobile_user, opportunity_access=access
)
dataset = Dataset(headers=["visit id", "status", "rejected reason", "justification"])
dataset.extend([[visit.xform_id, visit_status.value, "", "justification"] for visit in visits])
before_update = now()
import_status = _bulk_update_visit_status(opportunity, dataset)
after_update = now()
assert not import_status.missing_visits
updated_visits = UserVisit.objects.filter(opportunity=opportunity)
for visit in updated_visits:
assert visit.status == visit_status
assert visit.status_modified_date is not None
assert before_update <= visit.status_modified_date <= after_update
if visit.status == VisitValidationStatus.approved:
assert before_update <= visit.review_created_on <= after_update
assert visit.review_status == VisitReviewStatus.pending
assert visit.justification == "justification"


@pytest.mark.parametrize("opportunity", [{"opp_options": {"managed": True}}], indirect=True)
@pytest.mark.parametrize(
"review_status, cw_status",
[
(VisitReviewStatus.pending, CompletedWorkStatus.pending),
(VisitReviewStatus.agree, CompletedWorkStatus.approved),
(VisitReviewStatus.disagree, CompletedWorkStatus.pending),
],
)
def test_review_completed_work_status(
mobile_user_with_connect_link: User, opportunity: Opportunity, review_status, cw_status
):
deliver_unit = DeliverUnitFactory(app=opportunity.deliver_app, payment_unit=opportunity.paymentunit_set.first())
access = OpportunityAccess.objects.get(user=mobile_user_with_connect_link, opportunity=opportunity)
UserVisitFactory.create_batch(
2,
opportunity_access=access,
status=VisitValidationStatus.approved,
review_status=review_status,
review_created_on=now(),
completed_work__status=CompletedWorkStatus.pending,
completed_work__opportunity_access=access,
completed_work__payment_unit=opportunity.paymentunit_set.first(),
deliver_unit=deliver_unit,
)
assert access.payment_accrued == 0
update_payment_accrued(opportunity, {mobile_user_with_connect_link.id})
completed_works = CompletedWork.objects.filter(opportunity_access=access)
payment_accrued = 0
for cw in completed_works:
assert cw.status == cw_status
if cw.status == CompletedWorkStatus.approved:
payment_accrued += cw.payment_accrued
access.refresh_from_db()
assert access.payment_accrued == payment_accrued
3 changes: 2 additions & 1 deletion commcare_connect/opportunity/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
PaymentInvoice,
PaymentUnit,
UserVisit,
VisitReviewStatus,
VisitValidationStatus,
)
from commcare_connect.opportunity.tables import (
Expand Down Expand Up @@ -1119,7 +1120,7 @@ def user_visit_review(request, org_slug, opp_id):
review_status = request.POST.get("review_status").lower()
updated_reviews = request.POST.getlist("pk")
user_visits = UserVisit.objects.filter(pk__in=updated_reviews)
if review_status in ["agree", "disagree"]:
if review_status in [VisitReviewStatus.agree.value, VisitReviewStatus.disagree.value]:
user_visits.update(review_status=review_status)
update_payment_accrued(opportunity=opportunity, users=[visit.user for visit in user_visits])

Expand Down
2 changes: 1 addition & 1 deletion commcare_connect/reports/tests/test_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def test_delivery_stats(opportunity: Opportunity):
status=VisitValidationStatus.approved.value,
opportunity_access=access,
completed_work=completed_work,
visit_date=Faker("date_this_month"),
visit_date=Faker("date_time_this_month", tzinfo=datetime.UTC),
)

quarter = math.ceil(datetime.datetime.utcnow().month / 12 * 4)
Expand Down

0 comments on commit 9d994e0

Please sign in to comment.