Skip to content

[Eng-834]:testcases for invoice - #3726

Open
nandkishorr wants to merge 10 commits into
developfrom
ENG-834-testcases-for-invoice
Open

[Eng-834]:testcases for invoice#3726
nandkishorr wants to merge 10 commits into
developfrom
ENG-834-testcases-for-invoice

Conversation

@nandkishorr

@nandkishorr nandkishorr commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Associated Issue

-ENG-834

Merge Checklist

  • Tests added/fixed
  • Update docs in /docs
  • Linting Complete
  • Any other necessary step

Only PR's with test cases included and passing lint and test pipelines will be reviewed

@ohcnetwork/care-backend-maintainers @ohcnetwork/care-backend-admins

Summary by CodeRabbit

  • Tests
    • Expanded invoice API coverage across creation, updates, listing, retrieval, cancellation, locking, and unlocking.
    • Added coverage for attaching and removing charge items and accounts.
    • Added validation for permissions, invalid requests, status transitions, payment reconciliation filters, cancellation windows, and facility/account restrictions.
    • Improved shared test setup and reusable scenarios for more consistent invoice workflow validation.

@nandkishorr
nandkishorr requested a review from a team as a code owner August 6, 2026 07:36
Copilot AI review requested due to automatic review settings August 6, 2026 07:36
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a430b70-8bde-480e-9f4f-98ce31d9c270

📥 Commits

Reviewing files that changed from the base of the PR and between 3b84106 and 6b6724f.

📒 Files selected for processing (2)
  • care/emr/tests/test_invoice_api.py
  • care/emr/tests/test_user_api.py
💤 Files with no reviewable changes (1)
  • care/emr/tests/test_user_api.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • care/emr/tests/test_invoice_api.py

📝 Walkthrough

Walkthrough

The PR replaces narrow invoice account-attachment tests with broad invoice API coverage. It adds shared fixtures and tests for invoice lifecycle, filtering, locking, charge-item operations, and account attachment. It also changes user test setup behavior.

Changes

Invoice API coverage

Layer / File(s) Summary
Invoice setup and creation
care/emr/tests/test_invoice_api.py
Adds shared fixtures, helpers, permissions, and creation tests for numbering, locks, authorization, and facility constraints.
Invoice updates and cancellation
care/emr/tests/test_invoice_api.py
Adds tests for status transitions, charge-item requirements, cancellation reasons, permissions, and cancellation windows.
Invoice listing, retrieval, and locking
care/emr/tests/test_invoice_api.py
Adds filter, authorization, locked-invoice, lock, and unlock coverage.
Charge-item and account operations
care/emr/tests/test_invoice_api.py, care/emr/tests/test_user_api.py
Adds tests for charge-item attachment and removal, account attachment authorization, validation, and the updated user test setup.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • ohcnetwork/care#3702: Expands tests for attach_account_to_invoice and covers related bug-fix behavior.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description includes the issue and checklist but omits the required proposed changes summary and issue solution details. Add a Proposed Changes section, provide an issue link, explain the solution, and update the checklist to reflect completed work.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the addition of invoice test cases for issue ENG-834.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ENG-834-testcases-for-invoice

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds/expands Django REST Framework API test coverage for the EMR invoice endpoints (create/update/list/retrieve/cancel/lock + permission and validation scenarios) to support ENG-834.

Changes:

  • Refactors invoice API tests into a shared helper-based test class and adds broad endpoint coverage.
  • Adds tests for invoice numbering defaults, create-lock failure handling, and multiple status-transition validations.
  • Adds tests for payment-reconciliation-related invoice filtering and locked-invoice permission behavior.
Suppressed comments (5)

care/emr/tests/test_invoice_api.py:553

  • This PaymentReconciliation.objects.create(...) omits required non-null fields (reconciliation_type, kind, issuer_type, outcome, method) and uses an invalid status value ("completed"). This will fail when saving the model.
        PaymentReconciliation.objects.create(
            facility=self.facility,
            account=self.account,
            status="completed",
            amount=invoice.total_gross,
            tendered_amount=invoice.total_gross,
            returned_amount=Decimal("0.00"),
            target_invoice=invoice,
        )

care/emr/tests/test_invoice_api.py:651

  • This PaymentReconciliation.objects.create(...) omits required non-null fields (reconciliation_type, kind, issuer_type, outcome, method) and uses an invalid status value ("completed"). This will fail when saving the model.
        PaymentReconciliation.objects.create(
            facility=self.facility,
            account=self.account,
            status="completed",
            amount=invoice.total_gross,
            tendered_amount=invoice.total_gross,
            returned_amount=Decimal("0.00"),
            target_invoice=invoice,
        )

care/emr/tests/test_invoice_api.py:678

  • This PaymentReconciliation.objects.create(...) omits required non-null fields (reconciliation_type, kind, issuer_type, outcome, method) and uses an invalid status value ("completed"). This will fail when saving the model.
        PaymentReconciliation.objects.create(
            facility=self.facility,
            account=self.account,
            status="completed",
            amount=invoice.total_gross,
            tendered_amount=invoice.total_gross,
            returned_amount=Decimal("0.00"),
            target_invoice=invoice,
        )

care/emr/tests/test_invoice_api.py:719

  • Typo in test name: retriveretrieve (helps readability and consistency when searching for tests).
    def test_retrive_locked_invoice_with_user_without_permission(self):

care/emr/tests/test_invoice_api.py:738

  • Typo in test name: retriveretrieve (helps readability and consistency when searching for tests).
    def test_retrive_locked_invoice_with_user_with_permission(self):

Comment thread care/emr/tests/test_invoice_api.py
Comment thread care/emr/tests/test_invoice_api.py
Comment thread care/emr/tests/test_invoice_api.py
Comment thread care/emr/tests/test_invoice_api.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (6)
care/emr/tests/test_invoice_api.py (6)

270-281: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use a local role variable instead of reassigning self.role.

This test overwrites the self.role created in setUp. Other tests in this file (for example lines 1156 and 1219) use a local role for the same purpose. Both styles work because setUp runs per test, but the mix makes the fixture contract harder to follow. Pick the local variable form throughout.

♻️ Proposed change
-        self.client.force_authenticate(user=self.user)
         permissions = [
             InvoicePermissions.can_read_invoice.name,
         ]
-        self.role = self.create_role_with_permissions(permissions)
-        self.attach_role_facility_organization_user(
-            self.organization, self.user, self.role
-        )
+        role = self.create_role_with_permissions(permissions)
+        self.attach_role_facility_organization_user(self.organization, self.user, role)
         self.client.force_authenticate(user=self.user)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@care/emr/tests/test_invoice_api.py` around lines 270 - 281, Update
test_update_invoice_with_user_without_write_permission to store the result of
create_role_with_permissions in a local role variable instead of overwriting
self.role, and pass that local role to attach_role_facility_organization_user.

912-912: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix the section comment.

The comment reads "# def testcases for attach and detach charge items to invoice". The def is left over. The endpoints and helpers use "remove", not "detach", so align the wording.

♻️ Proposed fix
-    # def testcases for attach and detach charge items to invoice
+    # testcases for attach and remove charge items to invoice
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@care/emr/tests/test_invoice_api.py` at line 912, Update the section comment
above the invoice charge-item tests to remove the stray “def” wording and
replace “detach” with “remove,” matching the endpoint and helper terminology.

1118-1128: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert lock_history as well.

The lock action appends an entry with user, timestamp, and action to invoice.lock_history. No test checks it. That is an audit trail on a financial record, so a silent regression there would be unfortunate. Refresh the invoice and assert one entry with action == "lock".

💚 Proposed addition
         self.assertEqual(response.status_code, 200)
         response_data = response.data
         self.assertTrue(response_data["locked"])
+        invoice.refresh_from_db()
+        self.assertEqual(len(invoice.lock_history), 1)
+        self.assertEqual(invoice.lock_history[0]["action"], "lock")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@care/emr/tests/test_invoice_api.py` around lines 1118 - 1128, Extend
test_lock_invoice_with_superuser to refresh the invoice after the lock request,
then assert invoice.lock_history contains exactly one entry whose action is
"lock". Also validate the recorded user and timestamp fields are present,
preserving the existing response assertions.

89-104: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider deterministic invoice numbers.

random.randint(1000, 9999) can produce the same number twice inside one test. If Invoice.number carries a unique constraint per facility, that creates a rare flaky failure. A counter or self.fake.unique removes the randomness. Not urgent, just one of those things that fails at 2 AM.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@care/emr/tests/test_invoice_api.py` around lines 89 - 104, Update the
create_invoice helper to generate deterministic, unique invoice numbers within a
test, replacing random.randint with an existing counter or self.fake.unique
mechanism while preserving the default INV- prefix and allowing an explicitly
supplied number via kwargs.

77-87: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify generate_invoice_data.

kwargs.get("status", ...) and kwargs.get("charge_items", ...) are redundant. Line 86 already applies data.update(**kwargs), which overrides both keys. Keep the defaults only.

♻️ Proposed simplification
     def generate_invoice_data(self, **kwargs):
         data = {
             "account": self.account.external_id,
-            "status": kwargs.get("status", InvoiceStatusOptions.draft.value),
-            "charge_items": kwargs.get("charge_items", [self.charge_item.external_id]),
+            "status": InvoiceStatusOptions.draft.value,
+            "charge_items": [self.charge_item.external_id],
             "title": "Test Invoice",
             "number": f"INV-{random.randint(1000, 9999)}",  # noqa: S311
             "issue_date": datetime.now(UTC).isoformat(),
         }
         data.update(**kwargs)
         return data
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@care/emr/tests/test_invoice_api.py` around lines 77 - 87, Update
generate_invoice_data so the initial data dictionary assigns the default
InvoiceStatusOptions.draft value and default charge-item list directly, relying
on the existing data.update(**kwargs) call to override them when provided.

932-948: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a cross-account attach test.

The suite covers permissions and non-draft status for attach, but no test attaches a charge item that belongs to a different account or facility. That is a tenant-isolation boundary, and it is the kind of thing that only gets noticed after it ships. A single negative test would cover it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@care/emr/tests/test_invoice_api.py` around lines 932 - 948, Add a negative
cross-account or cross-facility test alongside
test_attach_charge_items_to_invoice_with_superuser, creating the invoice and
charge item under different tenants and posting the charge item to the attach
endpoint. Assert the request is rejected and the invoice’s charge items remain
unchanged, using the existing test helpers and URL construction.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@care/emr/tests/test_invoice_api.py`:
- Around line 1255-1266: Update test_attach_account_to_invoice_as_superuser and
test_attach_account_to_invoice_as_user_with_permissions to create an additional
billable charge item on the account after create_invoice, ensuring it is not
initially attached to the invoice. Assert that this new item appears in
response.data["charge_items"] after the request, rather than asserting the
pre-attached self.charge_item.
- Around line 563-579: The invoice list tests, including
test_list_invoices_with_account_filter and
test_list_invoices_with_payment_reconciliation_present_filter_and_account, do
not prove account filtering because only self.account has an invoice. Extend
create_invoice to accept an account override, create a second account and
invoice for it in both tests, then assert the requested account’s result
contains only the expected invoice and excludes the second account’s invoice.
- Around line 346-364: Update
test_update_invoice_with_no_charge_items_and_issued_status so the generated
request data explicitly contains an empty charge_items list, while preserving
the issued status. Ensure the PUT payload matches the invoice created with
charge_items=[] and continues asserting the existing 400 response and validation
message.
- Around line 1284-1287: Correct the docstring in
test_attach_account_to_invoice_as_user_without_permissions to describe a user
without write permission, matching the test name and covered scenario.
- Around line 706-757: Rename all three test methods beginning with
test_retrive_locked_invoice—test_retrive_locked_invoice_with_superuser,
test_retrive_locked_invoice_with_user_without_permission, and
test_retrive_locked_invoice_with_user_with_permission—to use
test_retrieve_locked_invoice, preserving their behavior and test coverage.
- Around line 168-182: Strengthen
test_create_invoice_without_number_auto_generates by asserting
response.data["number"] equals the value produced by the configured expression,
including the expected INV- prefix, invoice count, and two-digit current year,
rather than only checking that it is truthy.
- Around line 894-910: Update
test_cancel_invoice_with_user_without_permission_outside_period to apply
`@override_settings`(INVOICE_FREE_CANCEL_PERIOD_MINUTES=5), ensuring the invoice
created 10 minutes earlier remains outside the free-cancel period and the 403
assertion exercises the destroy-permission path.

---

Nitpick comments:
In `@care/emr/tests/test_invoice_api.py`:
- Around line 270-281: Update
test_update_invoice_with_user_without_write_permission to store the result of
create_role_with_permissions in a local role variable instead of overwriting
self.role, and pass that local role to attach_role_facility_organization_user.
- Line 912: Update the section comment above the invoice charge-item tests to
remove the stray “def” wording and replace “detach” with “remove,” matching the
endpoint and helper terminology.
- Around line 1118-1128: Extend test_lock_invoice_with_superuser to refresh the
invoice after the lock request, then assert invoice.lock_history contains
exactly one entry whose action is "lock". Also validate the recorded user and
timestamp fields are present, preserving the existing response assertions.
- Around line 89-104: Update the create_invoice helper to generate
deterministic, unique invoice numbers within a test, replacing random.randint
with an existing counter or self.fake.unique mechanism while preserving the
default INV- prefix and allowing an explicitly supplied number via kwargs.
- Around line 77-87: Update generate_invoice_data so the initial data dictionary
assigns the default InvoiceStatusOptions.draft value and default charge-item
list directly, relying on the existing data.update(**kwargs) call to override
them when provided.
- Around line 932-948: Add a negative cross-account or cross-facility test
alongside test_attach_charge_items_to_invoice_with_superuser, creating the
invoice and charge item under different tenants and posting the charge item to
the attach endpoint. Assert the request is rejected and the invoice’s charge
items remain unchanged, using the existing test helpers and URL construction.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cedd7106-ca6a-4b73-abe5-066af166e829

📥 Commits

Reviewing files that changed from the base of the PR and between fcd51e8 and 3b84106.

📒 Files selected for processing (1)
  • care/emr/tests/test_invoice_api.py

Comment on lines +168 to +182
def test_create_invoice_without_number_auto_generates(self):
"""
Test that omitting number triggers auto-generation via the configured expression.
"""
config = FacilityMonetoryConfig.get_monetory_config(self.facility.id)
config.invoice_number_expression = (
"f'INV-{invoice_count + 1}-{current_year_yy}'"
)
config.save()
self.client.force_authenticate(user=self.superuser)
data = self.generate_invoice_data()
data.pop("number")
response = self.client.post(self.url, data, format="json")
self.assertEqual(response.status_code, 200)
self.assertTrue(response.data["number"])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the generated number matches the configured expression.

The docstring promises auto-generation "via the configured expression", but line 182 only checks that number is truthy. Any fallback value passes. Assert the actual shape so the expression is really exercised.

💚 Proposed stronger assertion
         response = self.client.post(self.url, data, format="json")
         self.assertEqual(response.status_code, 200)
-        self.assertTrue(response.data["number"])
+        expected_year = datetime.now(UTC).strftime("%y")
+        self.assertEqual(response.data["number"], f"INV-1-{expected_year}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_create_invoice_without_number_auto_generates(self):
"""
Test that omitting number triggers auto-generation via the configured expression.
"""
config = FacilityMonetoryConfig.get_monetory_config(self.facility.id)
config.invoice_number_expression = (
"f'INV-{invoice_count + 1}-{current_year_yy}'"
)
config.save()
self.client.force_authenticate(user=self.superuser)
data = self.generate_invoice_data()
data.pop("number")
response = self.client.post(self.url, data, format="json")
self.assertEqual(response.status_code, 200)
self.assertTrue(response.data["number"])
def test_create_invoice_without_number_auto_generates(self):
"""
Test that omitting number triggers auto-generation via the configured expression.
"""
config = FacilityMonetoryConfig.get_monetory_config(self.facility.id)
config.invoice_number_expression = (
"f'INV-{invoice_count + 1}-{current_year_yy}'"
)
config.save()
self.client.force_authenticate(user=self.superuser)
data = self.generate_invoice_data()
data.pop("number")
response = self.client.post(self.url, data, format="json")
self.assertEqual(response.status_code, 200)
expected_year = datetime.now(UTC).strftime("%y")
self.assertEqual(response.data["number"], f"INV-1-{expected_year}")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@care/emr/tests/test_invoice_api.py` around lines 168 - 182, Strengthen
test_create_invoice_without_number_auto_generates by asserting
response.data["number"] equals the value produced by the configured expression,
including the expected INV- prefix, invoice count, and two-digit current year,
rather than only checking that it is truthy.

Comment thread care/emr/tests/test_invoice_api.py
Comment on lines +563 to +579
def test_list_invoices_with_account_filter(self):
"""
Test listing invoices with an account filter.
"""
self.attach_role_facility_organization_user(
self.organization, self.user, self.role
)
self.client.force_authenticate(user=self.user)
invoice = self.create_invoice()
response = self.client.get(
f"{self.url}?payment_reconciliation_present=false&account={self.account.external_id}",
format="json",
)
self.assertEqual(response.status_code, 200)
response_data = response.data["results"]
self.assertEqual(len(response_data), 1)
self.assertEqual(response_data[0]["id"], str(invoice.external_id))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The account filter is not actually proven.

Only one invoice exists in the database, so len(response_data) == 1 passes even if the server ignores the account parameter entirely. Add a second account with its own invoice, then assert the filtered result excludes it. The same gap applies to test_list_invoices_with_payment_reconciliation_present_filter_and_account at lines 535-561.

💚 Proposed addition
         invoice = self.create_invoice()
+        other_account = Account.objects.create(
+            facility=self.facility,
+            patient=self.patient,
+            name="Other Account",
+            status=AccountStatusOptions.active.value,
+            billing_status=AccountBillingStatusOptions.open.value,
+        )
+        self.create_invoice(account=other_account)
         response = self.client.get(
             f"{self.url}?payment_reconciliation_present=false&account={self.account.external_id}",
             format="json",
         )

Note that create_invoice currently hardcodes account=self.account; it needs an account override for this to work.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@care/emr/tests/test_invoice_api.py` around lines 563 - 579, The invoice list
tests, including test_list_invoices_with_account_filter and
test_list_invoices_with_payment_reconciliation_present_filter_and_account, do
not prove account filtering because only self.account has an invoice. Extend
create_invoice to accept an account override, create a second account and
invoice for it in both tests, then assert the requested account’s result
contains only the expected invoice and excludes the second account’s invoice.

Comment thread care/emr/tests/test_invoice_api.py Outdated
Comment thread care/emr/tests/test_invoice_api.py
Comment thread care/emr/tests/test_invoice_api.py
Comment thread care/emr/tests/test_invoice_api.py
@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.08%. Comparing base (6eb0df0) to head (3b84106).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #3726      +/-   ##
===========================================
+ Coverage    79.45%   80.08%   +0.62%     
===========================================
  Files          480      480              
  Lines        23215    23214       -1     
  Branches      2420     2420              
===========================================
+ Hits         18446    18590     +144     
+ Misses        4165     4025     -140     
+ Partials       604      599       -5     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@vigneshhari

Copy link
Copy Markdown
Member

Tests broken

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants