Skip to content

Commit c445577

Browse files
wscourgeclaude
andcommitted
Add missing test coverage for recent SDK changes
Add edge case, error path, and backwards-compatibility tests: Invoice: - Verify update_status request body is sent correctly - 404 error paths for update_status and disable - Verify disable sends no request body - LineItem/Transaction errors=None and errors-absent cases SubscriptionEvent: - Flat destroy/modify with external_id+data_source_uuid - Passthrough when caller already wraps in envelope (no double-wrap) - disable/enable with external_id+data_source_uuid identification Account: - Graceful handling when id field absent from response - Single include param Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0b7a467 commit c445577

File tree

3 files changed

+394
-0
lines changed

3 files changed

+394
-0
lines changed

test/api/test_account.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,52 @@ def test_retrieve_with_include(self, mock_requests):
9393
mock_requests.last_request.qs,
9494
{"include": ["churn_recognition,churn_when_zero_mrr"]},
9595
)
96+
97+
@requests_mock.mock()
98+
def test_retrieve_without_id_field(self, mock_requests):
99+
"""Old API responses without id field should not break deserialization."""
100+
mock_requests.register_uri(
101+
"GET",
102+
"https://api.chartmogul.com/v1/account",
103+
request_headers={"Authorization": "Basic dG9rZW46"},
104+
status_code=200,
105+
json=jsonResponse,
106+
)
107+
108+
config = Config("token")
109+
account = Account.retrieve(config).get()
110+
self.assertTrue(isinstance(account, Account))
111+
self.assertFalse(hasattr(account, "id"))
112+
113+
@requests_mock.mock()
114+
def test_retrieve_with_single_include(self, mock_requests):
115+
singleIncludeResponse = {
116+
"id": "acct_a1b2c3d4",
117+
"name": "Example Test Company",
118+
"currency": "EUR",
119+
"time_zone": "Europe/Berlin",
120+
"week_start_on": "sunday",
121+
"churn_recognition": "immediate",
122+
}
123+
124+
mock_requests.register_uri(
125+
"GET",
126+
"https://api.chartmogul.com/v1/account?include=churn_recognition",
127+
request_headers={"Authorization": "Basic dG9rZW46"},
128+
headers={"Content-Type": "application/json"},
129+
status_code=200,
130+
json=singleIncludeResponse,
131+
)
132+
133+
config = Config("token")
134+
account = Account.retrieve(
135+
config,
136+
include="churn_recognition"
137+
).get()
138+
self.assertTrue(isinstance(account, Account))
139+
self.assertEqual(account.churn_recognition, "immediate")
140+
self.assertFalse(hasattr(account, "churn_when_zero_mrr"))
141+
self.assertEqual(
142+
mock_requests.last_request.qs,
143+
{"include": ["churn_recognition"]},
144+
)

test/api/test_invoice.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,3 +722,188 @@ def test_disable(self, mock_requests):
722722
self.assertEqual(mock_requests.call_count, 1, "expected call")
723723
self.assertTrue(isinstance(result, Invoice))
724724
self.assertTrue(result.disabled)
725+
726+
@requests_mock.mock()
727+
def test_update_status_verifies_request_body(self, mock_requests):
728+
updatedInvoice = dict(retrieveInvoiceExample)
729+
updatedInvoice["disabled"] = False
730+
731+
mock_requests.register_uri(
732+
"PATCH",
733+
"https://api.chartmogul.com/v1/invoices/inv_22910fc6-c931-48e7-ac12-90d2cb5f0059",
734+
request_headers={"Authorization": "Basic dG9rZW46"},
735+
headers={"Content-Type": "application/json"},
736+
status_code=200,
737+
json=updatedInvoice,
738+
)
739+
740+
config = Config("token")
741+
Invoice.update_status(
742+
config,
743+
uuid="inv_22910fc6-c931-48e7-ac12-90d2cb5f0059",
744+
data={"disabled": False}
745+
).get()
746+
747+
self.assertEqual(
748+
mock_requests.last_request.json(),
749+
{"disabled": False},
750+
)
751+
752+
@requests_mock.mock()
753+
def test_update_status_not_found(self, mock_requests):
754+
mock_requests.register_uri(
755+
"PATCH",
756+
"https://api.chartmogul.com/v1/invoices/inv_nonexistent",
757+
request_headers={"Authorization": "Basic dG9rZW46"},
758+
headers={"Content-Type": "application/json"},
759+
status_code=404,
760+
json={"error": "Invoice not found"},
761+
)
762+
763+
config = Config("token")
764+
with self.assertRaises(APIError):
765+
Invoice.update_status(
766+
config,
767+
uuid="inv_nonexistent",
768+
data={"disabled": False}
769+
).get()
770+
771+
self.assertEqual(mock_requests.call_count, 1, "expected call")
772+
773+
@requests_mock.mock()
774+
def test_disable_no_request_body(self, mock_requests):
775+
disabledInvoice = dict(retrieveInvoiceExample)
776+
disabledInvoice["disabled"] = True
777+
778+
mock_requests.register_uri(
779+
"PATCH",
780+
"https://api.chartmogul.com/v1/invoices/inv_22910fc6-c931-48e7-ac12-90d2cb5f0059/disable",
781+
request_headers={"Authorization": "Basic dG9rZW46"},
782+
headers={"Content-Type": "application/json"},
783+
status_code=200,
784+
json=disabledInvoice,
785+
)
786+
787+
config = Config("token")
788+
Invoice.disable(
789+
config,
790+
uuid="inv_22910fc6-c931-48e7-ac12-90d2cb5f0059",
791+
).get()
792+
793+
self.assertIsNone(mock_requests.last_request.body)
794+
795+
@requests_mock.mock()
796+
def test_disable_not_found(self, mock_requests):
797+
mock_requests.register_uri(
798+
"PATCH",
799+
"https://api.chartmogul.com/v1/invoices/inv_nonexistent/disable",
800+
request_headers={"Authorization": "Basic dG9rZW46"},
801+
headers={"Content-Type": "application/json"},
802+
status_code=404,
803+
json={"error": "Invoice not found"},
804+
)
805+
806+
config = Config("token")
807+
with self.assertRaises(APIError):
808+
Invoice.disable(config, uuid="inv_nonexistent").get()
809+
810+
self.assertEqual(mock_requests.call_count, 1, "expected call")
811+
812+
@requests_mock.mock()
813+
def test_line_item_errors_none(self, mock_requests):
814+
responseWithNoneErrors = {
815+
"uuid": "inv_test",
816+
"external_id": "INV0001",
817+
"date": "2015-11-01T00:00:00.000Z",
818+
"due_date": "2015-11-15T00:00:00.000Z",
819+
"currency": "USD",
820+
"line_items": [
821+
{
822+
"uuid": "li_test",
823+
"external_id": None,
824+
"type": "subscription",
825+
"prorated": False,
826+
"amount_in_cents": 5000,
827+
"quantity": 1,
828+
"discount_amount_in_cents": 0,
829+
"tax_amount_in_cents": 0,
830+
"transaction_fees_in_cents": 0,
831+
"errors": None,
832+
},
833+
],
834+
"transactions": [
835+
{
836+
"uuid": "tr_test",
837+
"external_id": None,
838+
"type": "payment",
839+
"date": "2015-11-05T00:04:03.000Z",
840+
"result": "successful",
841+
"errors": None,
842+
},
843+
],
844+
}
845+
846+
mock_requests.register_uri(
847+
"GET",
848+
"https://api.chartmogul.com/v1/invoices/inv_test",
849+
request_headers={"Authorization": "Basic dG9rZW46"},
850+
headers={"Content-Type": "application/json"},
851+
status_code=200,
852+
json=responseWithNoneErrors,
853+
)
854+
855+
config = Config("token")
856+
result = Invoice.retrieve(config, uuid="inv_test").get()
857+
858+
self.assertTrue(isinstance(result, Invoice))
859+
self.assertIsNone(result.line_items[0].errors)
860+
self.assertIsNone(result.transactions[0].errors)
861+
862+
@requests_mock.mock()
863+
def test_line_item_errors_absent(self, mock_requests):
864+
responseNoErrors = {
865+
"uuid": "inv_test",
866+
"external_id": "INV0001",
867+
"date": "2015-11-01T00:00:00.000Z",
868+
"due_date": "2015-11-15T00:00:00.000Z",
869+
"currency": "USD",
870+
"line_items": [
871+
{
872+
"uuid": "li_test",
873+
"external_id": None,
874+
"type": "subscription",
875+
"prorated": False,
876+
"amount_in_cents": 5000,
877+
"quantity": 1,
878+
"discount_amount_in_cents": 0,
879+
"tax_amount_in_cents": 0,
880+
"transaction_fees_in_cents": 0,
881+
},
882+
],
883+
"transactions": [
884+
{
885+
"uuid": "tr_test",
886+
"external_id": None,
887+
"type": "payment",
888+
"date": "2015-11-05T00:04:03.000Z",
889+
"result": "successful",
890+
},
891+
],
892+
}
893+
894+
mock_requests.register_uri(
895+
"GET",
896+
"https://api.chartmogul.com/v1/invoices/inv_test_no_errors",
897+
request_headers={"Authorization": "Basic dG9rZW46"},
898+
headers={"Content-Type": "application/json"},
899+
status_code=200,
900+
json=responseNoErrors,
901+
)
902+
903+
config = Config("token")
904+
result = Invoice.retrieve(config, uuid="inv_test_no_errors").get()
905+
906+
self.assertTrue(isinstance(result, Invoice))
907+
# When errors field is absent from response, the attribute should not be set
908+
self.assertFalse(hasattr(result.line_items[0], "errors"))
909+
self.assertFalse(hasattr(result.transactions[0], "errors"))

0 commit comments

Comments
 (0)