diff --git a/README.md b/README.md index 9bdafbb..097ddcc 100644 --- a/README.md +++ b/README.md @@ -178,8 +178,12 @@ chartmogul.Customer.disconnectSubscriptions(config, uuid='cus_5915ee5a-babd-406b }) chartmogul.Customer.contacts(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', cursor='aabbcc', per_page=20) chartmogul.Customer.createContact(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={}) +# DEPRECATED: use chartmogul.Customer.entityNotes() instead chartmogul.Customer.notes(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', cursor='aabbcc', per_page=20) +# DEPRECATED: use chartmogul.Customer.createEntityNote() instead chartmogul.Customer.createNote(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={}) +chartmogul.Customer.entityNotes(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', cursor='aabbcc', per_page=20) +chartmogul.Customer.createEntityNote(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={}) chartmogul.Customer.opportunities(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', cursor='aabbcc', per_page=20) chartmogul.Customer.createOpportunity(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={}) chartmogul.Customer.tasks(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', cursor='aabbcc', per_page=20) @@ -190,17 +194,55 @@ chartmogul.Customer.createTask(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133 ```python chartmogul.Contact.create(config, data={}) -chartmogul.Contact.all(config, cursor='aabbcc', per_page=20) +chartmogul.Contact.all(config, cursor='aabbcc', per_page=20, customer_uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data_source_uuid='ds_fef05d54-47b4-431b-aed2-eb6b9e545430', email='test@example.com', customer_external_id='cus_0001', external_id='con_0001') chartmogul.Contact.retrieve(config, uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb') chartmogul.Contact.merge(config, into_uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb', from_uuid='con_2123290f-09c8-4628-a205-db5596bd58f7') chartmogul.Contact.modify(config, uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={ - "email": "test@example.com" + "email": "test@example.com", + "last_seen": "2025-04-01T12:00:00Z" }) chartmogul.Contact.destroy(config, uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb') +chartmogul.Contact.tasks(config, uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb', cursor='aabbcc', per_page=20) +chartmogul.Contact.createTask(config, uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={}) +chartmogul.Contact.entityNotes(config, uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb', cursor='aabbcc', per_page=20) +chartmogul.Contact.createEntityNote(config, uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={}) ``` -#### [Customer Notes](https://dev.chartmogul.com/reference/notes-and-call-logs/) +`Contact.createTask` and `Contact.createEntityNote` add an `associated_object_identifier` pointing at the contact unless the payload already contains `customer_uuid` or `associated_object_identifier`. + +#### [Notes](https://dev.chartmogul.com/reference/notes-and-call-logs/) + +Notes and call logs attached to a customer or a contact. Replaces Customer Notes. + +```python +chartmogul.EntityNote.create(config, data={ + "customer_uuid": "cus_5915ee5a-babd-406b-b8ce-d207133fb4cb", + "type": "note", + "text": "This is a note", + "author_email": "john@example.com" +}) +chartmogul.EntityNote.create(config, data={ + "associated_object_identifier": { + "associated_object": "contact", + "method": "uuid", + "value": "con_5915ee5a-babd-406b-b8ce-d207133fb4cb" + }, + "type": "call", + "call_duration": 60, + "text": "This is a call log" +}) +chartmogul.EntityNote.all(config, cursor='aabbcc', per_page=20, customer_uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb') +chartmogul.EntityNote.all(config, contact_uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb', type='call', author_email='john@example.com') +chartmogul.EntityNote.retrieve(config, uuid='note_5915ee5a-babd-406b-b8ce-d207133fb4cb') +chartmogul.EntityNote.patch(config, uuid='note_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={"text": "Updated text"}) +chartmogul.EntityNote.destroy(config, uuid='note_5915ee5a-babd-406b-b8ce-d207133fb4cb') +``` + +`EntityNote.patch` returns `None` when the API reports nothing was updated (HTTP 304). + +#### [Customer Notes (deprecated)](https://dev.chartmogul.com/reference/notes-and-call-logs/) ```python +# DEPRECATED: use chartmogul.EntityNote instead chartmogul.CustomerNote.create(config, data={}) chartmogul.CustomerNote.all(config, cursor='aabbcc', per_page=20, customer_uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb') chartmogul.CustomerNote.retrieve(config, uuid='note_5915ee5a-babd-406b-b8ce-d207133fb4cb') @@ -221,13 +263,31 @@ chartmogul.Opportunity.destroy(config, uuid='5915ee5a-babd-406b-b8ce-d207133fb4c #### [Tasks](https://dev.chartmogul.com/reference/tasks/) ```python -chartmogul.Task.create(config, data={}) +chartmogul.Task.create(config, data={ + "customer_uuid": "cus_5915ee5a-babd-406b-b8ce-d207133fb4cb", + "task_details": "Follow up on renewal", + "assignee": "john@example.com", + "due_date": "2025-04-30T00:00:00Z" +}) +chartmogul.Task.create(config, data={ + "associated_object_identifier": { + "associated_object": "contact", + "method": "uuid", + "value": "con_5915ee5a-babd-406b-b8ce-d207133fb4cb" + }, + "task_details": "Follow up on renewal", + "assignee": "john@example.com", + "due_date": "2025-04-30T00:00:00Z" +}) chartmogul.Task.all(config, cursor='aabbcc', per_page=20, customer_uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb') +chartmogul.Task.all(config, contact_uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb', assignee='john@example.com', due_date_on_or_after='2025-04-01T00:00:00Z', due_date_on_or_before='2025-04-30T00:00:00Z', completed=False) chartmogul.Task.retrieve(config, uuid='5915ee5a-babd-406b-b8ce-d207133fb4cb') -chartmogul.Task.patch(config, uuid='5915ee5a-babd-406b-b8ce-d207133fb4cb') +chartmogul.Task.patch(config, uuid='5915ee5a-babd-406b-b8ce-d207133fb4cb', data={"completed_at": "2025-04-20T00:00:00Z"}) chartmogul.Task.destroy(config, uuid='5915ee5a-babd-406b-b8ce-d207133fb4cb') ``` +Date filters take ISO 8601 strings. `Task.patch` returns `None` when the API reports nothing was updated (HTTP 304). + #### [Customer Attributes](https://dev.chartmogul.com/reference/customers/attributes/) Note that the returned attributes of type date are not parsed and stay in string. diff --git a/chartmogul/__init__.py b/chartmogul/__init__.py index 517cdf0..818324d 100644 --- a/chartmogul/__init__.py +++ b/chartmogul/__init__.py @@ -12,6 +12,7 @@ from .api.custom_attrs import CustomAttributes from .api.customer import Customer from .api.customer_note import CustomerNote +from .api.entity_note import EntityNote from .api.contact import Contact from .api.data_source import DataSource from .api.invoice import Invoice diff --git a/chartmogul/api/contact.py b/chartmogul/api/contact.py index 92b0e24..7853a20 100644 --- a/chartmogul/api/contact.py +++ b/chartmogul/api/contact.py @@ -1,6 +1,8 @@ from marshmallow import Schema, fields, post_load, EXCLUDE -from ..resource import Resource +from ..resource import ASSOCIATED_OBJECT_CONTACT, Resource from collections import namedtuple +from .entity_note import EntityNote +from .task import Task class Contact(Resource): @@ -29,6 +31,7 @@ class _Schema(Schema): # load_default=None ensures this attribute is always present on the Contact # object even when the API omits the field (e.g. older responses) external_id = fields.String(allow_none=True, load_default=None) + last_seen = fields.DateTime(allow_none=True) custom = fields.Dict(allow_none=True) @post_load @@ -39,3 +42,15 @@ def make(self, data, **kwargs): Contact.merge = Contact._method("merge", "post", "/contacts/{into_uuid}/merge/{from_uuid}") +Contact.tasks = Task._method("all", "get", "/tasks?contact_uuid={uuid}", useCallerClass=True) +Contact.createTask = Task._method( + "create", "post", "/tasks", useCallerClass=True, + useAssociatedObjectFor=ASSOCIATED_OBJECT_CONTACT +) +Contact.entityNotes = EntityNote._method( + "all", "get", "/notes?contact_uuid={uuid}", useCallerClass=True +) +Contact.createEntityNote = EntityNote._method( + "create", "post", "/notes", useCallerClass=True, + useAssociatedObjectFor=ASSOCIATED_OBJECT_CONTACT +) diff --git a/chartmogul/api/customer.py b/chartmogul/api/customer.py index 9282781..f4bff27 100644 --- a/chartmogul/api/customer.py +++ b/chartmogul/api/customer.py @@ -6,11 +6,12 @@ Validation is done on server. """ from marshmallow import Schema, fields, post_load, EXCLUDE -from ..resource import DataObject, Resource +from ..resource import DataObject, Resource, _deprecated_method from collections import namedtuple from .attributes import Attributes from .contact import Contact from .customer_note import CustomerNote +from .entity_note import EntityNote from .opportunity import Opportunity from .task import Task from .customers.subscription import CustomerSubscription @@ -94,8 +95,16 @@ def make(self, data, **kwargs): Customer.createContact = Contact._method( "create", "post", "/customers/{uuid}/contacts", useCallerClass=True ) -Customer.notes = CustomerNote._method("all", "get", "/customer_notes?customer_uuid={uuid}", useCallerClass=True) -Customer.createNote = CustomerNote._method("create", "post", "/customer_notes", useCallerClass=True, useUUIDFor="customer_uuid") +Customer.notes = _deprecated_method( + CustomerNote._method("all", "get", "/customer_notes?customer_uuid={uuid}", useCallerClass=True), + "Customer.notes() is deprecated. Use Customer.entityNotes() instead.", +) +Customer.createNote = _deprecated_method( + CustomerNote._method("create", "post", "/customer_notes", useCallerClass=True, useUUIDFor="customer_uuid"), + "Customer.createNote() is deprecated. Use Customer.createEntityNote() instead.", +) +Customer.entityNotes = EntityNote._method("all", "get", "/notes?customer_uuid={uuid}", useCallerClass=True) +Customer.createEntityNote = EntityNote._method("create", "post", "/notes", useCallerClass=True, useUUIDFor="customer_uuid") Customer.opportunities = Opportunity._method("all", "get", "/opportunities?customer_uuid={uuid}", useCallerClass=True) Customer.createOpportunity = Opportunity._method("create", "post", "/opportunities", useCallerClass=True, useUUIDFor="customer_uuid") Customer.tasks = Task._method("all", "get", "/tasks?customer_uuid={uuid}", useCallerClass=True) diff --git a/chartmogul/api/customer_note.py b/chartmogul/api/customer_note.py index d122cf1..b8fd6b4 100644 --- a/chartmogul/api/customer_note.py +++ b/chartmogul/api/customer_note.py @@ -1,11 +1,13 @@ from marshmallow import Schema, fields, post_load, EXCLUDE -from ..resource import Resource +from ..resource import Resource, _deprecated_method from collections import namedtuple class CustomerNote(Resource): """ https://dev.chartmogul.com/v1.0/reference#customer-notes + + DEPRECATED: use EntityNote (/v1/notes) instead. """ _path = "/customer_notes{/uuid}" @@ -27,3 +29,21 @@ def make(self, data, **kwargs): return CustomerNote(**data) _schema = _Schema(unknown=EXCLUDE) + + +for _name, _verb in [ + ("all", "get"), + ("create", "post"), + ("retrieve", "get"), + ("modify", "patch"), + ("patch", "patch"), + ("destroy", "delete"), +]: + setattr( + CustomerNote, + _name, + _deprecated_method( + CustomerNote._method(_name, _verb), + "CustomerNote.%s() is deprecated. Use EntityNote.%s() instead." % (_name, _name), + ), + ) diff --git a/chartmogul/api/entity_note.py b/chartmogul/api/entity_note.py new file mode 100644 index 0000000..b17e0ed --- /dev/null +++ b/chartmogul/api/entity_note.py @@ -0,0 +1,35 @@ +from marshmallow import Schema, fields, post_load, EXCLUDE +from ..resource import Resource +from collections import namedtuple + + +class EntityNote(Resource): + """ + https://dev.chartmogul.com/reference/notes-and-call-logs/ + + Notes and call logs attached to a customer or a contact (/v1/notes). + Supersedes CustomerNote (/v1/customer_notes). + """ + + _path = "/notes{/uuid}" + _root_key = "entries" + _many = namedtuple("EntityNotes", [_root_key, "has_more", "cursor"], defaults=[None, None]) + + class _Schema(Schema): + uuid = fields.String() + customer_uuid = fields.String(allow_none=True) + # load_default=None keeps these attributes present even when the API omits them + associated_object = fields.String(allow_none=True, load_default=None) + associated_object_uuid = fields.String(allow_none=True, load_default=None) + type = fields.String() + text = fields.String() + author = fields.String() + call_duration = fields.Int() + created_at = fields.DateTime() + updated_at = fields.DateTime() + + @post_load + def make(self, data, **kwargs): + return EntityNote(**data) + + _schema = _Schema(unknown=EXCLUDE) diff --git a/chartmogul/api/task.py b/chartmogul/api/task.py index 9f8b21d..8ff6363 100644 --- a/chartmogul/api/task.py +++ b/chartmogul/api/task.py @@ -11,11 +11,15 @@ class Task(Resource): _path = "/tasks{/uuid}" _root_key = "entries" _many = namedtuple("Tasks", [_root_key, "has_more", "cursor"]) + _bool_query_params = ["completed"] class _Schema(Schema): task_uuid = fields.String() - customer_uuid = fields.String() + customer_uuid = fields.String(allow_none=True) + # load_default=None keeps these attributes present even when the API omits them + associated_object = fields.String(allow_none=True, load_default=None) + associated_object_uuid = fields.String(allow_none=True, load_default=None) assignee = fields.String() task_details = fields.String() due_date = fields.DateTime() diff --git a/chartmogul/resource.py b/chartmogul/resource.py index 2d877da..3d70499 100644 --- a/chartmogul/resource.py +++ b/chartmogul/resource.py @@ -1,3 +1,4 @@ +import warnings from datetime import date, datetime from json import dumps @@ -36,6 +37,10 @@ LIST_PARAMS = ["has_more", "summary", "customer_uuid", "data_source_uuid", "cursor"] ESCAPED_QUERY_KEYS = {"start_date": "start-date", "end_date": "end-date"} +EMPTY_BODY_STATUS_CODES = (202, 204, 304) + +ASSOCIATED_OBJECT_CONTACT = "contact" +ASSOCIATED_OBJECT_IDENTIFIER_METHOD_UUID = "uuid" class DataObject(object): @@ -96,7 +101,7 @@ def _load(cls, response): (immutable, therefore all set on creation by generator) """ response.raise_for_status() - if response.status_code == 204 or response.status_code == 202: + if response.status_code in EMPTY_BODY_STATUS_CODES: return None try: jsonObj = response.json() @@ -200,7 +205,8 @@ def _validate_arguments(cls, method, kwargs): ) @classmethod - def _method(callerClass, method, http_verb, path=None, useCallerClass=False, useUUIDFor=None): + def _method(callerClass, method, http_verb, path=None, useCallerClass=False, useUUIDFor=None, + useAssociatedObjectFor=None): @classmethod def fc(calleeClass, config, **kwargs): if config is None or not isinstance(config, Config): @@ -244,6 +250,9 @@ def fc(calleeClass, config, **kwargs): if useUUIDFor is not None and 'data' in kwargs.keys(): kwargs["data"][useUUIDFor] = kwargs["uuid"] + if useAssociatedObjectFor is not None and 'data' in kwargs.keys(): + _inject_associated_object(kwargs["data"], useAssociatedObjectFor, kwargs["uuid"]) + # UUID is always path parameter only. if "uuid" in kwargs: del kwargs["uuid"] @@ -268,6 +277,33 @@ def _add_method(cls, method, http_verb, path=None): _add_method(Resource, method, http_verb) +def _deprecated_method(original, message): + """ + Wraps a method built by Resource._method so it emits a DeprecationWarning + before delegating to the original. + """ + @classmethod + def fc(cls, config, **kwargs): + warnings.warn(message, DeprecationWarning, stacklevel=2) + return original.__func__(cls, config, **kwargs) + + return fc + + +def _inject_associated_object(data, associated_object, uuid): + """ + Points a create payload at its parent object unless the caller already + identified it via customer_uuid or associated_object_identifier. + """ + if "customer_uuid" in data or "associated_object_identifier" in data: + return + data["associated_object_identifier"] = { + "associated_object": associated_object, + "method": ASSOCIATED_OBJECT_IDENTIFIER_METHOD_UUID, + "value": uuid, + } + + def _build_ext_id_params(kwargs): """Build query params dict for external-id-based operations. diff --git a/test/api/test_common.py b/test/api/test_common.py index e8131e6..80170ba 100644 --- a/test/api/test_common.py +++ b/test/api/test_common.py @@ -87,6 +87,18 @@ def test_page_parameter_deprecated(self, mock_requests): else: self.fail("DeprecatedArgumentError not raised") + @requests_mock.mock() + def test_not_modified_returns_none(self, mock_requests): + mock_requests.register_uri( + "PATCH", + "https://api.chartmogul.com/v1/data_sources/my_uuid", + status_code=304, + ) + + config = Config("token") + result = DataSource.modify(config, uuid="my_uuid", data={}).get() + self.assertEqual(result, None) + @requests_mock.mock() def test_api_incorrect(self, mock_requests): mock_requests.register_uri( diff --git a/test/api/test_contact.py b/test/api/test_contact.py index 9295414..74fd2f6 100644 --- a/test/api/test_contact.py +++ b/test/api/test_contact.py @@ -1,5 +1,6 @@ import unittest -from chartmogul import Contact, Config +from datetime import datetime +from chartmogul import Contact, Config, EntityNote, Task import requests_mock from pprint import pprint @@ -19,6 +20,7 @@ "linked_in": "https://linkedin.com/not_found", "twitter": "https://twitter.com/not_found", "notes": "Heading\nBody\nFooter", + "last_seen": "2025-04-01T12:00:00Z", "custom": {"MyStringAttribute": "Test", "MyIntegerAttribute": 123}, } @@ -36,6 +38,7 @@ "linked_in": "https://linkedin.com/not_found", "twitter": "https://twitter.com/not_found", "notes": "Heading\nBody\nFooter", + "last_seen": "2025-04-01T12:00:00Z", "custom": [ {"key": "MyStringAttribute", "value": "Test"}, {"key": "MyIntegerAttribute", "value": 123}, @@ -56,6 +59,66 @@ "external_id": None, } +standaloneContact = { + **contact, + "customer_uuid": None, + "customer_external_id": None, + "data_source_uuid": None, +} + +createStandaloneContact = { + k: v for k, v in createContact.items() + if k not in ("uuid", "customer_uuid", "data_source_uuid") +} + +contactTask = { + "task_uuid": "00000000-0000-0000-0000-000000000000", + "customer_uuid": None, + "associated_object": "contact", + "associated_object_uuid": "con_00000000-0000-0000-0000-000000000000", + "assignee": "customer@example.com", + "task_details": "This is some task details text.", + "due_date": "2025-04-30T00:00:00Z", + "completed_at": None, + "created_at": "2025-04-01T12:00:00.000Z", + "updated_at": "2025-04-01T12:00:00.000Z", +} + +createContactTask = { + "assignee": "customer@example.com", + "task_details": "This is some task details text.", + "due_date": "2025-04-30T00:00:00Z", +} + +contactAssociatedObjectIdentifier = { + "associated_object": "contact", + "method": "uuid", + "value": "con_00000000-0000-0000-0000-000000000000", +} + +allContactTasks = {"entries": [contactTask], "cursor": "cursor==", "has_more": False} + +contactNote = { + "uuid": "note_00000000-0000-0000-0000-000000000000", + "customer_uuid": None, + "associated_object": "contact", + "associated_object_uuid": "con_00000000-0000-0000-0000-000000000000", + "type": "note", + "text": "This is a note", + "call_duration": 0, + "author": "John Doe (john@example.com)", + "created_at": "2015-06-09T13:16:00-04:00", + "updated_at": "2015-06-09T13:16:00-04:00", +} + +createContactNote = { + "type": "note", + "text": "This is a note", + "author_email": "john@example.com", +} + +allContactNotes = {"entries": [contactNote], "cursor": "cursor==", "has_more": False} + class ContactTestCase(unittest.TestCase): """ @@ -95,6 +158,37 @@ def test_all(self, mock_requests): self.assertFalse(contacts.has_more) self.assertEqual(contacts.cursor, "cursor==") + @requests_mock.mock() + def test_all_with_filters(self, mock_requests): + mock_requests.register_uri( + "GET", + "https://api.chartmogul.com/v1/contacts?customer_uuid=cus_00000000-0000-0000-0000-000000000000&data_source_uuid=ds_00000000-0000-0000-0000-000000000000&email=test@example.com&customer_external_id=external_001&external_id=contact_external_id_001", + status_code=200, + json=allContacts, + ) + + config = Config("token") + Contact.all( + config, + customer_uuid="cus_00000000-0000-0000-0000-000000000000", + data_source_uuid="ds_00000000-0000-0000-0000-000000000000", + email="test@example.com", + customer_external_id="external_001", + external_id="contact_external_id_001", + ).get() + + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual( + mock_requests.last_request.qs, + { + "customer_uuid": ["cus_00000000-0000-0000-0000-000000000000"], + "data_source_uuid": ["ds_00000000-0000-0000-0000-000000000000"], + "email": ["test@example.com"], + "customer_external_id": ["external_001"], + "external_id": ["contact_external_id_001"], + }, + ) + @requests_mock.mock() def test_create(self, mock_requests): mock_requests.register_uri( @@ -131,6 +225,19 @@ def test_create_without_external_id(self, mock_requests): self.assertEqual(mock_requests.last_request.json(), createContactWithNullExternalId) self.assertIsNone(result.external_id) + @requests_mock.mock() + def test_create_standalone(self, mock_requests): + mock_requests.register_uri( + "POST", "https://api.chartmogul.com/v1/contacts", status_code=200, json=standaloneContact + ) + + config = Config("token") + result = Contact.create(config, data=createStandaloneContact).get() + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual(mock_requests.last_request.json(), createStandaloneContact) + self.assertIsNone(result.customer_uuid) + self.assertIsNone(result.data_source_uuid) + @requests_mock.mock() def test_merge(self, mock_requests): mock_requests.register_uri( @@ -160,7 +267,7 @@ def test_modify(self, mock_requests): json=contact, ) - jsonRequest = {"email": "test2@example.com"} + jsonRequest = {"email": "test2@example.com", "last_seen": "2025-04-01T12:00:00Z"} config = Config("token") expected = Contact.modify( config, uuid="con_00000000-0000-0000-0000-000000000000", data=jsonRequest @@ -170,6 +277,7 @@ def test_modify(self, mock_requests): self.assertEqual(mock_requests.last_request.qs, {}) self.assertEqual(mock_requests.last_request.json(), jsonRequest) self.assertTrue(isinstance(expected, Contact)) + self.assertTrue(isinstance(expected.last_seen, datetime)) @requests_mock.mock() def test_modify_with_null_external_id(self, mock_requests): @@ -204,6 +312,152 @@ def test_retrieve(self, mock_requests): self.assertEqual(mock_requests.call_count, 1, "expected call") self.assertEqual(mock_requests.last_request.qs, {}) self.assertTrue(isinstance(expected, Contact)) + self.assertEqual(expected.email, "test@example.com") + self.assertTrue(isinstance(expected.last_seen, datetime)) + + @requests_mock.mock() + def test_tasks(self, mock_requests): + mock_requests.register_uri( + "GET", + "https://api.chartmogul.com/v1/tasks?contact_uuid=con_00000000-0000-0000-0000-000000000000&cursor=df431387&per_page=1", + status_code=200, + json=allContactTasks, + ) + + config = Config("token") + tasks = Contact.tasks( + config, + uuid="con_00000000-0000-0000-0000-000000000000", + cursor="df431387", + per_page=1, + ).get() + expected = Task._many(**allContactTasks) + + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual( + mock_requests.last_request.qs, + { + "contact_uuid": ["con_00000000-0000-0000-0000-000000000000"], + "cursor": ["df431387"], + "per_page": ["1"], + }, + ) + self.assertEqual(mock_requests.last_request.text, None) + self.assertEqual(sorted(dir(tasks)), sorted(dir(expected))) + self.assertTrue(isinstance(tasks.entries[0], Task)) + self.assertIsNone(tasks.entries[0].customer_uuid) + self.assertEqual(tasks.cursor, "cursor==") + self.assertFalse(tasks.has_more) + + @requests_mock.mock() + def test_createTask(self, mock_requests): + mock_requests.register_uri( + "POST", "https://api.chartmogul.com/v1/tasks", status_code=200, json=contactTask + ) + + config = Config("token") + result = Contact.createTask( + config, uuid="con_00000000-0000-0000-0000-000000000000", data=dict(createContactTask) + ).get() + + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual(mock_requests.last_request.qs, {}) + self.assertEqual( + mock_requests.last_request.json(), + {**createContactTask, "associated_object_identifier": contactAssociatedObjectIdentifier}, + ) + self.assertTrue(isinstance(result, Task)) + self.assertEqual(result.associated_object, "contact") + + @requests_mock.mock() + def test_createTask_with_customer_uuid(self, mock_requests): + mock_requests.register_uri( + "POST", "https://api.chartmogul.com/v1/tasks", status_code=200, json=contactTask + ) + + data = {**createContactTask, "customer_uuid": "cus_00000000-0000-0000-0000-000000000000"} + config = Config("token") + Contact.createTask( + config, uuid="con_00000000-0000-0000-0000-000000000000", data=data + ).get() + + self.assertEqual(mock_requests.last_request.json(), data) + self.assertNotIn("associated_object_identifier", mock_requests.last_request.json()) + + @requests_mock.mock() + def test_createTask_with_explicit_identifier(self, mock_requests): + mock_requests.register_uri( + "POST", "https://api.chartmogul.com/v1/tasks", status_code=200, json=contactTask + ) + + explicit_identifier = { + "associated_object": "customer", + "method": "uuid", + "value": "cus_00000000-0000-0000-0000-000000000000", + } + data = {**createContactTask, "associated_object_identifier": explicit_identifier} + config = Config("token") + Contact.createTask( + config, uuid="con_00000000-0000-0000-0000-000000000000", data=data + ).get() + + self.assertEqual( + mock_requests.last_request.json()["associated_object_identifier"], explicit_identifier + ) + + @requests_mock.mock() + def test_entityNotes(self, mock_requests): + mock_requests.register_uri( + "GET", + "https://api.chartmogul.com/v1/notes?contact_uuid=con_00000000-0000-0000-0000-000000000000&cursor=ym9vewfo&per_page=1", + status_code=200, + json=allContactNotes, + ) + + config = Config("token") + notes = Contact.entityNotes( + config, + uuid="con_00000000-0000-0000-0000-000000000000", + cursor="ym9vewfo", + per_page=1, + ).get() + expected = EntityNote._many(**allContactNotes) + + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual( + mock_requests.last_request.qs, + { + "contact_uuid": ["con_00000000-0000-0000-0000-000000000000"], + "cursor": ["ym9vewfo"], + "per_page": ["1"], + }, + ) + self.assertEqual(mock_requests.last_request.text, None) + self.assertEqual(sorted(dir(notes)), sorted(dir(expected))) + self.assertTrue(isinstance(notes.entries[0], EntityNote)) + self.assertIsNone(notes.entries[0].customer_uuid) + self.assertEqual(notes.cursor, "cursor==") + self.assertFalse(notes.has_more) + + @requests_mock.mock() + def test_createEntityNote(self, mock_requests): + mock_requests.register_uri( + "POST", "https://api.chartmogul.com/v1/notes", status_code=200, json=contactNote + ) + + config = Config("token") + result = Contact.createEntityNote( + config, uuid="con_00000000-0000-0000-0000-000000000000", data=dict(createContactNote) + ).get() + + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual(mock_requests.last_request.qs, {}) + self.assertEqual( + mock_requests.last_request.json(), + {**createContactNote, "associated_object_identifier": contactAssociatedObjectIdentifier}, + ) + self.assertTrue(isinstance(result, EntityNote)) + self.assertEqual(result.associated_object, "contact") @requests_mock.mock() def test_destroy(self, mock_requests): diff --git a/test/api/test_customer.py b/test/api/test_customer.py index ec7700e..7cf6f2e 100644 --- a/test/api/test_customer.py +++ b/test/api/test_customer.py @@ -1,5 +1,5 @@ import unittest -from chartmogul import Customer, Contact, Config, CustomerNote, Opportunity, Task +from chartmogul import Customer, Contact, Config, CustomerNote, EntityNote, Opportunity, Task from chartmogul.api.customer import Attributes, Address from chartmogul.api.customers.subscription import CustomerSubscription from datetime import datetime @@ -313,6 +313,27 @@ allNotes = {"entries": [noteEntry], "cursor": "cursor==", "has_more": True} +entityNote = { + "uuid": "note_00000000-0000-0000-0000-000000000000", + "customer_uuid": "cus_00000000-0000-0000-0000-000000000000", + "associated_object": "customer", + "associated_object_uuid": "cus_00000000-0000-0000-0000-000000000000", + "type": "note", + "text": "This is a note", + "call_duration": 0, + "author": "John Doe (john@example.com)", + "created_at": "2015-06-09T13:16:00-04:00", + "updated_at": "2015-06-09T13:16:00-04:00" +} + +createEntityNote = { + "type": "note", + "text": "This is a note", + "author_email": "john@example.com" +} + +allEntityNotes = {"entries": [entityNote], "cursor": "cursor==", "has_more": True} + opportunity = { "uuid": "00000000-0000-0000-0000-000000000000", "customer_uuid": "cus_00000000-0000-0000-0000-000000000000", @@ -734,12 +755,13 @@ def test_notes(self, mock_requests): ) config = Config("token") - notes = Customer.notes( - config, - uuid="cus_00000000-0000-0000-0000-000000000000", - cursor="ym9vewfo", - per_page=1, - ).get() + with self.assertWarns(DeprecationWarning): + notes = Customer.notes( + config, + uuid="cus_00000000-0000-0000-0000-000000000000", + cursor="ym9vewfo", + per_page=1, + ).get() expected = Customer._many(**allNotes) self.assertEqual(mock_requests.call_count, 1, "expected call") @@ -760,15 +782,64 @@ def test_createNote(self, mock_requests): ) config = Config("token") - expected = Customer.createNote( - config, uuid="cus_00000000-0000-0000-0000-000000000000", data=createNote - ).get() + with self.assertWarns(DeprecationWarning): + expected = Customer.createNote( + config, uuid="cus_00000000-0000-0000-0000-000000000000", data=createNote + ).get() self.assertEqual(mock_requests.call_count, 1, "expected call") self.assertEqual(mock_requests.last_request.qs, {}) self.assertEqual(mock_requests.last_request.json(), createNote) self.assertTrue(isinstance(expected, CustomerNote)) + @requests_mock.mock() + def test_entityNotes(self, mock_requests): + mock_requests.register_uri( + "GET", + "https://api.chartmogul.com/v1/notes?customer_uuid=cus_00000000-0000-0000-0000-000000000000&cursor=ym9vewfo&per_page=1", + status_code=200, + json=allEntityNotes, + ) + + config = Config("token") + notes = Customer.entityNotes( + config, + uuid="cus_00000000-0000-0000-0000-000000000000", + cursor="ym9vewfo", + per_page=1, + ).get() + expected = EntityNote._many(**allEntityNotes) + + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual(mock_requests.last_request.qs, {'customer_uuid': ['cus_00000000-0000-0000-0000-000000000000'], 'cursor': ['ym9vewfo'], 'per_page': ['1']}) + self.assertEqual(mock_requests.last_request.text, None) + self.assertEqual(sorted(dir(notes)), sorted(dir(expected))) + self.assertTrue(isinstance(notes.entries[0], EntityNote)) + self.assertEqual(notes.cursor, "cursor==") + self.assertTrue(notes.has_more) + + @requests_mock.mock() + def test_createEntityNote(self, mock_requests): + mock_requests.register_uri( + "POST", + "https://api.chartmogul.com/v1/notes", + status_code=200, + json=entityNote, + ) + + config = Config("token") + result = Customer.createEntityNote( + config, uuid="cus_00000000-0000-0000-0000-000000000000", data=dict(createEntityNote) + ).get() + + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual(mock_requests.last_request.qs, {}) + self.assertEqual( + mock_requests.last_request.json(), + {**createEntityNote, "customer_uuid": "cus_00000000-0000-0000-0000-000000000000"}, + ) + self.assertTrue(isinstance(result, EntityNote)) + @requests_mock.mock() def test_opportunities(self, mock_requests): mock_requests.register_uri( diff --git a/test/api/test_customer_note.py b/test/api/test_customer_note.py index 1fbd34e..fde9124 100644 --- a/test/api/test_customer_note.py +++ b/test/api/test_customer_note.py @@ -40,12 +40,13 @@ def test_all(self, mock_requests): ) config = Config("token") - notes = CustomerNote.all( - config, - customer_uuid="cus_00000000-0000-0000-0000-000000000000", - cursor="ym9vewfo", - per_page=1, - ).get() + with self.assertWarns(DeprecationWarning): + notes = CustomerNote.all( + config, + customer_uuid="cus_00000000-0000-0000-0000-000000000000", + cursor="ym9vewfo", + per_page=1, + ).get() expected = CustomerNote._many(**allNotes) self.assertEqual(mock_requests.call_count, 1, "expected call") @@ -70,7 +71,8 @@ def test_create(self, mock_requests): ) config = Config("token") - expected = CustomerNote.create(config, data=createNote).get() + with self.assertWarns(DeprecationWarning): + expected = CustomerNote.create(config, data=createNote).get() self.assertEqual(mock_requests.call_count, 1, "expected call") self.assertEqual(mock_requests.last_request.qs, {}) self.assertEqual(mock_requests.last_request.json(), createNote) @@ -96,9 +98,10 @@ def test_patch(self, mock_requests): new_text = {"text": "new text"} config = Config("token") - expected = CustomerNote.patch( - config, uuid="note_00000000-0000-0000-0000-000000000000", data=new_text - ).get() + with self.assertWarns(DeprecationWarning): + expected = CustomerNote.patch( + config, uuid="note_00000000-0000-0000-0000-000000000000", data=new_text + ).get() self.assertEqual(mock_requests.call_count, 1, "expected call") self.assertEqual(mock_requests.last_request.qs, {}) self.assertEqual(mock_requests.last_request.json(), new_text) @@ -114,9 +117,10 @@ def test_retrieve(self, mock_requests): ) config = Config("token") - expected = CustomerNote.retrieve( - config, uuid="note_00000000-0000-0000-0000-000000000000" - ).get() + with self.assertWarns(DeprecationWarning): + expected = CustomerNote.retrieve( + config, uuid="note_00000000-0000-0000-0000-000000000000" + ).get() self.assertEqual(mock_requests.call_count, 1, "expected call") self.assertEqual(mock_requests.last_request.qs, {}) @@ -132,9 +136,10 @@ def test_destroy(self, mock_requests): ) config = Config("token") - expected = CustomerNote.destroy( - config, uuid="note_00000000-0000-0000-0000-000000000000" - ).get() + with self.assertWarns(DeprecationWarning): + expected = CustomerNote.destroy( + config, uuid="note_00000000-0000-0000-0000-000000000000" + ).get() self.assertEqual(mock_requests.call_count, 1, "expected call") self.assertEqual(mock_requests.last_request.qs, {}) self.assertTrue(expected, {}) diff --git a/test/api/test_entity_note.py b/test/api/test_entity_note.py new file mode 100644 index 0000000..24dce95 --- /dev/null +++ b/test/api/test_entity_note.py @@ -0,0 +1,244 @@ +import unittest +from chartmogul import EntityNote, Config +import requests_mock + +note = { + "uuid": "note_00000000-0000-0000-0000-000000000000", + "customer_uuid": "cus_00000000-0000-0000-0000-000000000000", + "associated_object": "customer", + "associated_object_uuid": "cus_00000000-0000-0000-0000-000000000000", + "type": "note", + "text": "This is a note", + "call_duration": 0, + "author": "John Doe (john@example.com)", + "created_at": "2015-06-09T13:16:00-04:00", + "updated_at": "2015-06-09T13:16:00-04:00" +} + +contactNote = { + "uuid": "note_00000000-0000-0000-0000-000000000001", + "customer_uuid": None, + "associated_object": "contact", + "associated_object_uuid": "con_00000000-0000-0000-0000-000000000000", + "type": "call", + "text": "This is a call log", + "call_duration": 60, + "author": "John Doe (john@example.com)", + "created_at": "2015-06-09T13:16:00-04:00", + "updated_at": "2015-06-09T13:16:00-04:00" +} + +noteWithoutAssociatedObject = { + k: v for k, v in note.items() + if k not in ("associated_object", "associated_object_uuid") +} + +createNote = { + "customer_uuid": "cus_00000000-0000-0000-0000-000000000000", + "type": "note", + "text": "This is a note", + "author_email": "john@example.com" +} + +createContactNote = { + "associated_object_identifier": { + "associated_object": "contact", + "method": "uuid", + "value": "con_00000000-0000-0000-0000-000000000000", + }, + "type": "call", + "text": "This is a call log", + "call_duration": 60, + "author_email": "john@example.com" +} + +allNotes = {"entries": [note], "cursor": "cursor==", "has_more": False} +allContactNotes = {"entries": [contactNote], "cursor": "cursor==", "has_more": False} + + +class EntityNoteTestCase(unittest.TestCase): + + @requests_mock.mock() + def test_all(self, mock_requests): + mock_requests.register_uri( + "GET", + "https://api.chartmogul.com/v1/notes?cursor=ym9vewfo&per_page=1&customer_uuid=cus_00000000-0000-0000-0000-000000000000", + status_code=200, + json=allNotes, + ) + + config = Config("token") + notes = EntityNote.all( + config, + customer_uuid="cus_00000000-0000-0000-0000-000000000000", + cursor="ym9vewfo", + per_page=1, + ).get() + expected = EntityNote._many(**allNotes) + + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual( + mock_requests.last_request.qs, + { + "cursor": ["ym9vewfo"], + "per_page": ["1"], + "customer_uuid": ["cus_00000000-0000-0000-0000-000000000000"], + }, + ) + self.assertEqual(mock_requests.last_request.text, None) + self.assertEqual(dir(notes), dir(expected)) + self.assertTrue(isinstance(notes.entries[0], EntityNote)) + self.assertEqual(notes.entries[0].associated_object, "customer") + self.assertFalse(notes.has_more) + self.assertEqual(notes.cursor, "cursor==") + + @requests_mock.mock() + def test_all_with_contact_filters(self, mock_requests): + mock_requests.register_uri( + "GET", + "https://api.chartmogul.com/v1/notes?contact_uuid=con_00000000-0000-0000-0000-000000000000&type=call&author_email=john@example.com", + status_code=200, + json=allContactNotes, + ) + + config = Config("token") + notes = EntityNote.all( + config, + contact_uuid="con_00000000-0000-0000-0000-000000000000", + type="call", + author_email="john@example.com", + ).get() + + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual( + mock_requests.last_request.qs, + { + "contact_uuid": ["con_00000000-0000-0000-0000-000000000000"], + "type": ["call"], + "author_email": ["john@example.com"], + }, + ) + self.assertIsNone(notes.entries[0].customer_uuid) + self.assertEqual(notes.entries[0].associated_object, "contact") + self.assertEqual( + notes.entries[0].associated_object_uuid, "con_00000000-0000-0000-0000-000000000000" + ) + + @requests_mock.mock() + def test_create(self, mock_requests): + mock_requests.register_uri( + "POST", "https://api.chartmogul.com/v1/notes", status_code=200, json=note + ) + + config = Config("token") + result = EntityNote.create(config, data=createNote).get() + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual(mock_requests.last_request.qs, {}) + self.assertEqual(mock_requests.last_request.json(), createNote) + self.assertTrue(isinstance(result, EntityNote)) + self.assertEqual(result.uuid, note["uuid"]) + + @requests_mock.mock() + def test_create_with_associated_object_identifier(self, mock_requests): + mock_requests.register_uri( + "POST", "https://api.chartmogul.com/v1/notes", status_code=200, json=contactNote + ) + + config = Config("token") + result = EntityNote.create(config, data=createContactNote).get() + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual(mock_requests.last_request.json(), createContactNote) + self.assertTrue(isinstance(result, EntityNote)) + self.assertIsNone(result.customer_uuid) + self.assertEqual(result.associated_object, "contact") + + @requests_mock.mock() + def test_patch(self, mock_requests): + new_note = {**note, "text": "new text"} + mock_requests.register_uri( + "PATCH", + "https://api.chartmogul.com/v1/notes/note_00000000-0000-0000-0000-000000000000", + status_code=200, + json=new_note, + ) + + new_text = {"text": "new text"} + + config = Config("token") + result = EntityNote.patch( + config, uuid="note_00000000-0000-0000-0000-000000000000", data=new_text + ).get() + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual(mock_requests.last_request.qs, {}) + self.assertEqual(mock_requests.last_request.json(), new_text) + self.assertTrue(isinstance(result, EntityNote)) + self.assertEqual(result.text, "new text") + + @requests_mock.mock() + def test_patch_not_modified(self, mock_requests): + mock_requests.register_uri( + "PATCH", + "https://api.chartmogul.com/v1/notes/note_00000000-0000-0000-0000-000000000000", + status_code=304, + ) + + config = Config("token") + result = EntityNote.patch( + config, uuid="note_00000000-0000-0000-0000-000000000000", data={} + ).get() + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual(result, None) + + @requests_mock.mock() + def test_retrieve(self, mock_requests): + mock_requests.register_uri( + "GET", + "https://api.chartmogul.com/v1/notes/note_00000000-0000-0000-0000-000000000000", + status_code=200, + json=note, + ) + + config = Config("token") + result = EntityNote.retrieve( + config, uuid="note_00000000-0000-0000-0000-000000000000" + ).get() + + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual(mock_requests.last_request.qs, {}) + self.assertTrue(isinstance(result, EntityNote)) + self.assertEqual(result.customer_uuid, "cus_00000000-0000-0000-0000-000000000000") + + @requests_mock.mock() + def test_retrieve_without_associated_object(self, mock_requests): + mock_requests.register_uri( + "GET", + "https://api.chartmogul.com/v1/notes/note_00000000-0000-0000-0000-000000000000", + status_code=200, + json=noteWithoutAssociatedObject, + ) + + config = Config("token") + result = EntityNote.retrieve( + config, uuid="note_00000000-0000-0000-0000-000000000000" + ).get() + + self.assertTrue(isinstance(result, EntityNote)) + self.assertIsNone(result.associated_object) + self.assertIsNone(result.associated_object_uuid) + + @requests_mock.mock() + def test_destroy(self, mock_requests): + mock_requests.register_uri( + "DELETE", + "https://api.chartmogul.com/v1/notes/note_00000000-0000-0000-0000-000000000000", + status_code=202, + json={"message": "Note has been deleted."}, + ) + + config = Config("token") + result = EntityNote.destroy( + config, uuid="note_00000000-0000-0000-0000-000000000000" + ).get() + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual(mock_requests.last_request.qs, {}) + self.assertEqual(result, None) diff --git a/test/api/test_task.py b/test/api/test_task.py index 59934a7..21056e8 100644 --- a/test/api/test_task.py +++ b/test/api/test_task.py @@ -7,6 +7,8 @@ task = { "task_uuid": "00000000-0000-0000-0000-000000000000", "customer_uuid": "cus_00000000-0000-0000-0000-000000000000", + "associated_object": "customer", + "associated_object_uuid": "cus_00000000-0000-0000-0000-000000000000", "assignee": "customer@example.com", "task_details": "This is some task details text.", "due_date": "2025-04-30T00:00:00Z", @@ -23,7 +25,27 @@ "completed_at": "2025-04-20T00:00:00Z", } +contactTask = { + **task, + "task_uuid": "00000000-0000-0000-0000-000000000001", + "customer_uuid": None, + "associated_object": "contact", + "associated_object_uuid": "con_00000000-0000-0000-0000-000000000000", +} + +createContactTask = { + "associated_object_identifier": { + "associated_object": "contact", + "method": "uuid", + "value": "con_00000000-0000-0000-0000-000000000000", + }, + "assignee": "customer@example.com", + "task_details": "This is some task details text.", + "due_date": "2025-04-30T00:00:00Z", +} + allTasks = {"entries": [task], "cursor": "cursor==", "has_more": False} +allContactTasks = {"entries": [contactTask], "cursor": "cursor==", "has_more": False} class TaskTestCase(unittest.TestCase): @@ -64,6 +86,42 @@ def test_all(self, mock_requests): self.assertFalse(tasks.has_more) self.assertEqual(tasks.cursor, "cursor==") + @requests_mock.mock() + def test_all_with_filters(self, mock_requests): + mock_requests.register_uri( + "GET", + "https://api.chartmogul.com/v1/tasks?contact_uuid=con_00000000-0000-0000-0000-000000000000&assignee=customer@example.com&due_date_on_or_after=2025-04-01T00:00:00Z&due_date_on_or_before=2025-04-30T00:00:00Z&completed=false", + status_code=200, + json=allContactTasks, + ) + + config = Config("token") + tasks = Task.all( + config, + contact_uuid="con_00000000-0000-0000-0000-000000000000", + assignee="customer@example.com", + due_date_on_or_after="2025-04-01T00:00:00Z", + due_date_on_or_before="2025-04-30T00:00:00Z", + completed=False, + ).get() + + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual( + mock_requests.last_request.qs, + { + "contact_uuid": ["con_00000000-0000-0000-0000-000000000000"], + "assignee": ["customer@example.com"], + "due_date_on_or_after": ["2025-04-01t00:00:00z"], + "due_date_on_or_before": ["2025-04-30t00:00:00z"], + "completed": ["false"], + }, + ) + self.assertIsNone(tasks.entries[0].customer_uuid) + self.assertEqual(tasks.entries[0].associated_object, "contact") + self.assertEqual( + tasks.entries[0].associated_object_uuid, "con_00000000-0000-0000-0000-000000000000" + ) + @requests_mock.mock() def test_create(self, mock_requests): mock_requests.register_uri( @@ -77,6 +135,35 @@ def test_create(self, mock_requests): self.assertEqual(mock_requests.last_request.json(), createTask) self.assertTrue(expected, task) + @requests_mock.mock() + def test_create_with_associated_object_identifier(self, mock_requests): + mock_requests.register_uri( + "POST", "https://api.chartmogul.com/v1/tasks", status_code=200, json=contactTask + ) + + config = Config("token") + result = Task.create(config, data=createContactTask).get() + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual(mock_requests.last_request.json(), createContactTask) + self.assertTrue(isinstance(result, Task)) + self.assertIsNone(result.customer_uuid) + self.assertEqual(result.associated_object, "contact") + + @requests_mock.mock() + def test_patch_not_modified(self, mock_requests): + mock_requests.register_uri( + "PATCH", + "https://api.chartmogul.com/v1/tasks/00000000-0000-0000-0000-000000000000", + status_code=304, + ) + + config = Config("token") + result = Task.patch( + config, uuid="00000000-0000-0000-0000-000000000000", data={} + ).get() + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual(result, None) + @requests_mock.mock() def test_patch(self, mock_requests): new_task = { @@ -122,6 +209,29 @@ def test_retrieve(self, mock_requests): self.assertEqual(mock_requests.call_count, 1, "expected call") self.assertEqual(mock_requests.last_request.qs, {}) self.assertTrue(isinstance(expected, Task)) + self.assertEqual(expected.associated_object, "customer") + + @requests_mock.mock() + def test_retrieve_without_associated_object(self, mock_requests): + task_without_associated_object = { + k: v for k, v in task.items() + if k not in ("associated_object", "associated_object_uuid") + } + mock_requests.register_uri( + "GET", + "https://api.chartmogul.com/v1/tasks/00000000-0000-0000-0000-000000000000", + status_code=200, + json=task_without_associated_object, + ) + + config = Config("token") + result = Task.retrieve( + config, uuid="00000000-0000-0000-0000-000000000000" + ).get() + + self.assertTrue(isinstance(result, Task)) + self.assertIsNone(result.associated_object) + self.assertIsNone(result.associated_object_uuid) @requests_mock.mock() def test_destroy(self, mock_requests):