Skip to content
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
19 changes: 1 addition & 18 deletions chartmogul/api/data_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,7 @@ class DataSource(Resource):
'with_auto_churn_subscription_setting',
'with_invoice_handling_setting'
]
_many = namedtuple(
"DataSources",
[_root_key] + _bool_query_params,
defaults=[None, None, None]
)

@classmethod
def _preProcessParams(cls, params):
params = super()._preProcessParams(params)

for query_param in cls._bool_query_params:
if query_param in params and isinstance(params[query_param], bool):
if params[query_param] is True:
params[query_param] = 'true'
else:
del params[query_param]

return params
_many = namedtuple("DataSources", [_root_key])

class _Schema(Schema):
uuid = fields.String()
Expand Down
10 changes: 10 additions & 0 deletions chartmogul/api/invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class Invoice(Resource):

_path = "/import/customers{/uuid}/invoices"
_root_key = "invoices"
_bool_query_params = [
'include_edit_histories',
'with_disabled'
]
_many = namedtuple(
"Invoices",
[_root_key, "cursor", "has_more", "customer_uuid"],
Expand All @@ -58,6 +62,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)

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
9 changes: 9 additions & 0 deletions chartmogul/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ def _preProcessParams(cls, params):
if key in params:
params[replacement] = params[key]
del params[key]

if hasattr(cls, '_bool_query_params'):
for query_param in cls._bool_query_params:
if query_param in params and isinstance(params[query_param], bool):
if params[query_param] is True:
params[query_param] = 'true'
elif params[query_param] is False:
params[query_param] = 'false'

return params

@classmethod
Expand Down
176 changes: 175 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,142 @@ 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")
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"]},
)
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")
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"])
self.assertTrue(isinstance(result, Invoice))
self.assertEqual(result.uuid, "inv_22910fc6-c931-48e7-ac12-90d2cb5f0059")
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")
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"]},
)

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")
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"])
self.assertTrue(isinstance(result, Invoice._many))
self.assertEqual(len(result.invoices), 1)