Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
6 changes: 6 additions & 0 deletions chartmogul/api/invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ class _Schema(Schema):
date = fields.DateTime()
due_date = fields.DateTime(allow_none=True)

disabled = fields.Boolean(allow_none=True)
disabled_at = fields.DateTime(allow_none=True)
disabled_by = fields.String(allow_none=True)
edit_history_summary = fields.Dict(allow_none=True)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[major] When trying to retrieve this field from few random invoices in production, I get:

AttributeError: 'Invoice' object has no attribute 'edit_history_summary'

Could you confirm that it works for you in production using any invoice?

errors = fields.Dict(allow_none=True)

line_items = fields.Nested(LineItem._Schema, many=True, unknown=EXCLUDE)
transactions = fields.Nested(Transaction._Schema, many=True, unknown=EXCLUDE)

Expand Down
187 changes: 186 additions & 1 deletion test/api/test_invoice.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# pylama:ignore=W0212
import unittest
from datetime import datetime
from datetime import datetime, timezone

import requests_mock

Expand Down Expand Up @@ -175,6 +175,20 @@
"date": "2015-11-01T00:00:00.000Z",
"due_date": "2015-11-15T00:00:00.000Z",
"currency": "USD",
"disabled": False,
"disabled_at": None,
"disabled_by": None,
"edit_history_summary": {
"values_changed": {
"amount_in_cents": {
"original_value": 4500,
"edited_value": 5000
}
},
"latest_edit_author": "admin@example.com",
"latest_edit_performed_at": "2024-01-10T12:00:00.000Z"
},
"errors": None,
"line_items": [
{
"uuid": "li_d72e6843-5793-41d0-bfdf-0269514c9c56",
Expand Down Expand Up @@ -231,6 +245,27 @@
"date": "2015-11-01T00:00:00.000Z",
"due_date": "2015-11-15T00:00:00.000Z",
"currency": "USD",
"disabled": True,
"disabled_at": "2024-01-15T10:30:00.000Z",
"disabled_by": "user@example.com",
"edit_history_summary": {
"values_changed": {
"currency": {
"original_value": "EUR",
"edited_value": "USD"
},
"date": {
"original_value": "2024-01-01T00:00:00.000Z",
"edited_value": "2024-01-02T00:00:00.000Z"
}
},
"latest_edit_author": "editor@example.com",
"latest_edit_performed_at": "2024-01-20T15:45:00.000Z"
},
"errors": {
"currency": ["Currency is invalid", "Currency must be supported"],
"date": ["Date is in the future"]
},
"line_items": [
{
"uuid": "li_d72e6843-5793-41d0-bfdf-0269514c9c56",
Expand Down Expand Up @@ -447,3 +482,153 @@ def test_retrieve_invoice(self, mock_requests):
self.assertTrue(isinstance(result, Invoice))

self.assertEqual(result.uuid, "inv_22910fc6-c931-48e7-ac12-90d2cb5f0059")

@requests_mock.mock()
def test_retrieve_invoice_with_validation_type(self, mock_requests):
mock_requests.register_uri(
"GET",
("https://api.chartmogul.com/v1/invoices/inv_22910fc6-c931-48e7-ac12-90d2cb5f0059"
"?validation_type=all"),
request_headers={"Authorization": "Basic dG9rZW46"},
headers={"Content-Type": "application/json"},
status_code=200,
json=retrieveInvoiceExample,
)

config = Config("token") # is actually checked in mock
result = Invoice.retrieve(
config,
uuid="inv_22910fc6-c931-48e7-ac12-90d2cb5f0059",
validation_type="all"
).get()

self.assertEqual(mock_requests.call_count, 1, "expected call")
self.assertEqual(
mock_requests.last_request.qs,
{"validation_type": ["all"]},
)

# Struct too complex to do 1:1 comparison
self.assertTrue(isinstance(result, Invoice))

self.assertEqual(result.uuid, "inv_22910fc6-c931-48e7-ac12-90d2cb5f0059")

@requests_mock.mock()
def test_retrieve_invoice_with_all_params(self, mock_requests):
mock_requests.register_uri(
"GET",
("https://api.chartmogul.com/v1/invoices/inv_22910fc6-c931-48e7-ac12-90d2cb5f0059"
"?validation_type=invalid&include_edit_histories=true&with_disabled=false"),
request_headers={"Authorization": "Basic dG9rZW46"},
headers={"Content-Type": "application/json"},
status_code=200,
json=retrieveInvoiceExample,
)

config = Config("token") # is actually checked in mock
result = Invoice.retrieve(
config,
uuid="inv_22910fc6-c931-48e7-ac12-90d2cb5f0059",
validation_type="invalid",
include_edit_histories=True,
with_disabled=False
).get()

self.assertEqual(mock_requests.call_count, 1, "expected call")
qs = mock_requests.last_request.qs
self.assertEqual(qs["validation_type"], ["invalid"])
self.assertEqual(qs["include_edit_histories"], ["true"])
self.assertEqual(qs["with_disabled"], ["false"])

# Struct too complex to do 1:1 comparison
self.assertTrue(isinstance(result, Invoice))

self.assertEqual(result.uuid, "inv_22910fc6-c931-48e7-ac12-90d2cb5f0059")

# Verify new fields are present
self.assertTrue(result.disabled)
self.assertEqual(result.disabled_at, datetime(2024, 1, 15, 10, 30, tzinfo=timezone.utc))
self.assertEqual(result.disabled_by, "user@example.com")
self.assertIsNotNone(result.edit_history_summary)
self.assertIn("values_changed", result.edit_history_summary)
self.assertIn("currency", result.edit_history_summary["values_changed"])
self.assertEqual(
result.edit_history_summary["values_changed"]["currency"]["original_value"],
"EUR"
)
self.assertEqual(
result.edit_history_summary["values_changed"]["currency"]["edited_value"],
"USD"
)
self.assertEqual(
result.edit_history_summary["latest_edit_author"],
"editor@example.com"
)
self.assertEqual(
result.edit_history_summary["latest_edit_performed_at"],
"2024-01-20T15:45:00.000Z"
)
self.assertIsNotNone(result.errors)
self.assertIn("currency", result.errors)
self.assertIsInstance(result.errors["currency"], list)
self.assertEqual(len(result.errors["currency"]), 2)
self.assertEqual(result.errors["currency"][0], "Currency is invalid")
self.assertEqual(result.errors["currency"][1], "Currency must be supported")
self.assertIn("date", result.errors)
self.assertIsInstance(result.errors["date"], list)
self.assertEqual(len(result.errors["date"]), 1)
self.assertEqual(result.errors["date"][0], "Date is in the future")

@requests_mock.mock()
def test_all_invoices_with_validation_type(self, mock_requests):
mock_requests.register_uri(
"GET",
"https://api.chartmogul.com/v1/invoices?validation_type=all",
request_headers={"Authorization": "Basic dG9rZW46"},
headers={"Content-Type": "application/json"},
status_code=200,
json=invoiceListExample,
)

config = Config("token") # is actually checked in mock
result = Invoice.all(config, validation_type="all").get()

self.assertEqual(mock_requests.call_count, 1, "expected call")
self.assertEqual(
mock_requests.last_request.qs,
{"validation_type": ["all"]},
)

# Struct too complex to do 1:1 comparison
self.assertTrue(isinstance(result, Invoice._many))
self.assertEqual(len(result.invoices), 1)

@requests_mock.mock()
def test_all_invoices_with_all_params(self, mock_requests):
mock_requests.register_uri(
"GET",
("https://api.chartmogul.com/v1/invoices"
"?validation_type=valid&include_edit_histories=true&with_disabled=true"),
request_headers={"Authorization": "Basic dG9rZW46"},
headers={"Content-Type": "application/json"},
status_code=200,
json=invoiceListExample,
)

config = Config("token") # is actually checked in mock
result = Invoice.all(
config,
validation_type="valid",
include_edit_histories=True,
with_disabled=True
).get()

self.assertEqual(mock_requests.call_count, 1, "expected call")
qs = mock_requests.last_request.qs
self.assertEqual(qs["validation_type"], ["valid"])
self.assertEqual(qs["include_edit_histories"], ["true"])
self.assertEqual(qs["with_disabled"], ["true"])

# Struct too complex to do 1:1 comparison
self.assertTrue(isinstance(result, Invoice._many))
self.assertEqual(len(result.invoices), 1)